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

Add budget and end date check for opportunity claims #142

Merged
merged 3 commits into from
Oct 4, 2023
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
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"