Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Claim Limit Remaining Budget Fix #445

Merged
merged 5 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion commcare_connect/opportunity/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ def claimed_budget(self):
opp_access = OpportunityAccess.objects.filter(opportunity=self)
opportunity_claim = OpportunityClaim.objects.filter(opportunity_access__in=opp_access)
claim_limits = OpportunityClaimLimit.objects.filter(opportunity_claim__in=opportunity_claim)
org_pay = 0
if self.managed:
org_pay = self.managedopportunity.org_pay_per_visit

payment_unit_counts = claim_limits.values("payment_unit").annotate(
visits_count=Sum("max_visits"), amount=F("payment_unit__amount")
Expand All @@ -123,7 +126,7 @@ def claimed_budget(self):
for count in payment_unit_counts:
visits_count = count["visits_count"]
amount = count["amount"]
claimed += visits_count * amount
claimed += visits_count * (amount + org_pay)

return claimed

Expand Down
36 changes: 31 additions & 5 deletions commcare_connect/opportunity/tests/test_api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
UserVisitFactory,
)
from commcare_connect.users.models import User
from commcare_connect.users.tests.factories import ConnectIdUserLinkFactory
from commcare_connect.users.tests.factories import ConnectIdUserLinkFactory, MobileUserFactory


def _setup_opportunity_and_access(mobile_user: User, total_budget, end_date, budget_per_visit=10):
Expand All @@ -56,11 +56,37 @@ def test_claim_endpoint_success(mobile_user: User, api_client: APIClient):
assert claim.exists()


def test_claim_endpoint_budget_exhausted(mobile_user: User, api_client: APIClient):
opportunity, opportunity_access = _setup_opportunity_and_access(
mobile_user, total_budget=0, end_date=datetime.date.today() + datetime.timedelta(days=100)
@pytest.mark.django_db
@pytest.mark.parametrize("opportunity", [{}, {"opp_options": {"managed": True}}], indirect=["opportunity"])
def test_claim_endpoint_budget_exhausted(opportunity: Opportunity, api_client: APIClient):
PaymentUnitFactory(opportunity=opportunity, amount=10, max_total=100)
opportunity.total_budget = 10 * 100
if opportunity.managed:
opportunity.total_budget += 100 * opportunity.managedopportunity.org_pay_per_visit
opportunity.end_date = datetime.date.today() + datetime.timedelta(days=100)
opportunity.save()

mobile_user_1 = MobileUserFactory()
opportunity_access_1 = OpportunityAccessFactory(opportunity=opportunity, user=mobile_user_1)
ConnectIdUserLinkFactory(
user=mobile_user_1,
commcare_username="[email protected]",
domain=opportunity.deliver_app.cc_domain,
)
api_client.force_authenticate(mobile_user)
api_client.force_authenticate(mobile_user_1)
response = api_client.post(f"/api/opportunity/{opportunity.id}/claim")
assert response.status_code == 201
claim = OpportunityClaim.objects.filter(opportunity_access=opportunity_access_1)
assert claim.exists()

mobile_user_2 = MobileUserFactory()
OpportunityAccessFactory(opportunity=opportunity, user=mobile_user_2)
ConnectIdUserLinkFactory(
user=mobile_user_2,
commcare_username="[email protected]",
domain=opportunity.deliver_app.cc_domain,
)
api_client.force_authenticate(mobile_user_2)
response = api_client.post(f"/api/opportunity/{opportunity.id}/claim")
assert response.status_code == 400
assert response.data == "Opportunity cannot be claimed. (Budget Exhausted)"
Expand Down
21 changes: 16 additions & 5 deletions commcare_connect/opportunity/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,37 @@ def test_learn_progress(opportunity: Opportunity):


@pytest.mark.django_db
@pytest.mark.parametrize("opportunity", [{}, {"opp_options": {"managed": True}}], indirect=True)
def test_opportunity_stats(opportunity: Opportunity, user: User):
payment_unit_sub = PaymentUnitFactory(
payment_unit_sub = PaymentUnitFactory.create(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this change do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not change anything about how these mock objects are created. Adding the create resolved some of the type errors I was getting in my editor.

opportunity=opportunity, max_total=100, max_daily=10, amount=5, parent_payment_unit=None
)
payment_unit1 = PaymentUnitFactory(
payment_unit1 = PaymentUnitFactory.create(
opportunity=opportunity,
max_total=100,
max_daily=10,
amount=3,
parent_payment_unit=payment_unit_sub,
)
payment_unit2 = PaymentUnitFactory(
payment_unit2 = PaymentUnitFactory.create(
opportunity=opportunity, max_total=100, max_daily=10, amount=5, parent_payment_unit=None
)
assert set(list(opportunity.paymentunit_set.values_list("id", flat=True))) == {
payment_unit1.id,
payment_unit2.id,
payment_unit_sub.id,
}
payment_units = [payment_unit_sub, payment_unit1, payment_unit2]
budget_per_user = sum(pu.max_total * pu.amount for pu in payment_units)
org_pay = 0
if opportunity.managed:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we know if the opportunity is managed or not in the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we have that information available, this line on the top of this test function creates 1 case with a normal opportunity and another with a managed opportunity.
@pytest.mark.parametrize("opportunity", [{}, {"opp_options": {"managed": True}}], indirect=True)

org_pay = opportunity.managedopportunity.org_pay_per_visit
budget_per_user += sum(pu.max_total * org_pay for pu in payment_units)
opportunity.total_budget = budget_per_user * 3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we modifying the opportunity in the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to update the opportunity budget based on the Payment Units created earlier in the test and the org_pay_per_visit in case of a managed opportunity.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like there should be a nicer way to do this, but seems fine


payment_units = [payment_unit1, payment_unit2, payment_unit_sub]
assert opportunity.budget_per_user == sum([p.amount * p.max_total for p in payment_units])
assert opportunity.number_of_users == opportunity.total_budget / opportunity.budget_per_user
assert opportunity.number_of_users == 3
assert opportunity.allotted_visits == sum([pu.max_total for pu in payment_units]) * opportunity.number_of_users
assert opportunity.max_visits_per_user_new == sum([pu.max_total for pu in payment_units])
assert opportunity.daily_max_visits_per_user_new == sum([pu.max_daily for pu in payment_units])
Expand All @@ -61,7 +69,10 @@ def test_opportunity_stats(opportunity: Opportunity, user: User):
ocl1 = OpportunityClaimLimitFactory(opportunity_claim=claim, payment_unit=payment_unit1)
ocl2 = OpportunityClaimLimitFactory(opportunity_claim=claim, payment_unit=payment_unit2)

opportunity.claimed_budget == (ocl1.max_visits * payment_unit1.amount) + (ocl2.max_visits * payment_unit2.amount)
assert opportunity.claimed_budget == (ocl1.max_visits * (payment_unit1.amount + org_pay)) + (
ocl2.max_visits * (payment_unit2.amount + org_pay)
)
assert opportunity.remaining_budget == opportunity.total_budget - opportunity.claimed_budget


@pytest.mark.django_db
Expand Down
Loading