Skip to content

Commit

Permalink
Merge pull request #142 from dimagi/pkv/claim-endpoint-budget-end-dat…
Browse files Browse the repository at this point in the history
…e-check

Add budget and end date check for opportunity claims
  • Loading branch information
pxwxnvermx authored Oct 4, 2023
2 parents 5eee011 + 4cabd6b commit de69062
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 2 deletions.
11 changes: 9 additions & 2 deletions commcare_connect/opportunity/api/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import datetime

from rest_framework import viewsets
from rest_framework.generics import ListAPIView, get_object_or_404
from rest_framework.permissions import IsAuthenticated
Expand Down Expand Up @@ -54,14 +56,19 @@ def post(self, *args, **kwargs):
opportunity_access = get_object_or_404(OpportunityAccess, user=self.request.user, opportunity=kwargs.get("pk"))
opportunity = opportunity_access.opportunity

if opportunity.remaining_budget <= 0:
return Response(status=200, data="Opportunity cannot be claimed. (Budget Exhausted)")
if opportunity.end_date < datetime.date.today():
return Response(status=200, data="Opportunity cannot be claimed. (End date reached)")

max_payments = min(opportunity.remaining_budget, opportunity.daily_max_visits_per_user)
claim, created = OpportunityClaim.objects.get_or_create(
opportunity_access=opportunity_access,
defaults={
"max_payments": opportunity.daily_max_visits_per_user,
"max_payments": max_payments,
"end_date": opportunity.end_date,
},
)

if not created:
return Response(status=200, data="Opportunity is already claimed")

Expand Down
11 changes: 11 additions & 0 deletions commcare_connect/opportunity/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from uuid import uuid4

from django.db import models
from django.db.models import Sum
from django.utils.translation import gettext

from commcare_connect.organization.models import Organization
Expand Down Expand Up @@ -64,6 +65,16 @@ class Opportunity(BaseModel):
def __str__(self):
return self.name

@property
def remaining_budget(self) -> int:
opp_access = OpportunityAccess.objects.filter(opportunity=self)
used_budget = OpportunityClaim.objects.filter(opportunity_access__in=opp_access).aggregate(
Sum("max_payments")
)["max_payments__sum"]
if used_budget is None:
used_budget = 0
return self.total_budget - used_budget


class DeliverForm(models.Model):
app = models.ForeignKey(
Expand Down
8 changes: 8 additions & 0 deletions commcare_connect/opportunity/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,11 @@ class UserVisitFactory(DjangoModelFactory):

class Meta:
model = "opportunity.UserVisit"


class OpportunityAccessFactory(DjangoModelFactory):
opportunity = SubFactory(OpportunityFactory)
user = SubFactory("commcare_connect.users.tests.factories.UserFactory")

class Meta:
model = "opportunity.OpportunityAccess"
63 changes: 63 additions & 0 deletions commcare_connect/opportunity/tests/test_api_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import datetime

from rest_framework.test import APIClient

from commcare_connect.opportunity.models import OpportunityClaim
from commcare_connect.opportunity.tests.factories import OpportunityAccessFactory, OpportunityFactory
from commcare_connect.users.models import User
from commcare_connect.users.tests.factories import ConnectIdUserLinkFactory


def _setup_opportunity_and_access(mobile_user: User, total_budget, end_date):
opportunity = OpportunityFactory(total_budget=total_budget, max_visits_per_user=100, end_date=end_date)
opportunity_access = OpportunityAccessFactory(opportunity=opportunity, user=mobile_user)
ConnectIdUserLinkFactory(
user=mobile_user, commcare_username="[email protected]", domain=opportunity.deliver_app.cc_domain
)
return opportunity, opportunity_access


def test_claim_endpoint_success(mobile_user: User, api_client: APIClient):
opportunity, opportunity_access = _setup_opportunity_and_access(
mobile_user, total_budget=1000, end_date=datetime.date.today() + datetime.timedelta(days=100)
)
api_client.force_authenticate(mobile_user)
response = api_client.post(f"/api/opportunity/{opportunity.id}/claim")
assert response.status_code == 201
claim = OpportunityClaim.objects.filter(opportunity_access=opportunity_access)
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)
)
api_client.force_authenticate(mobile_user)
response = api_client.post(f"/api/opportunity/{opportunity.id}/claim")
assert response.status_code == 200
assert response.data == "Opportunity cannot be claimed. (Budget Exhausted)"


def test_claim_endpoint_end_date_exceeded(mobile_user: User, api_client: APIClient):
opportunity, opportunity_access = _setup_opportunity_and_access(
mobile_user, total_budget=1000, end_date=datetime.date.today() - datetime.timedelta(days=100)
)
api_client.force_authenticate(mobile_user)
response = api_client.post(f"/api/opportunity/{opportunity.id}/claim")
assert response.status_code == 200
assert response.data == "Opportunity cannot be claimed. (End date reached)"


def test_claim_endpoint_already_claimed_opportunity(mobile_user: User, api_client: APIClient):
opportunity, opportunity_access = _setup_opportunity_and_access(
mobile_user, total_budget=1000, end_date=datetime.date.today() + datetime.timedelta(days=100)
)
api_client.force_authenticate(mobile_user)
response = api_client.post(f"/api/opportunity/{opportunity.id}/claim")
assert response.status_code == 201
claim = OpportunityClaim.objects.filter(opportunity_access=opportunity_access)
assert claim.exists()

response = api_client.post(f"/api/opportunity/{opportunity.id}/claim")
assert response.status_code == 200
assert response.data == "Opportunity is already claimed"

0 comments on commit de69062

Please sign in to comment.