diff --git a/breathecode/payments/actions.py b/breathecode/payments/actions.py index 156ce5ba5..bd9bcd67f 100644 --- a/breathecode/payments/actions.py +++ b/breathecode/payments/actions.py @@ -1063,6 +1063,17 @@ def validate_and_create_subscriptions( code=404, ) + if PlanFinancing.objects.filter(plans=plan, user=user, valid_until__gt=timezone.now()).exists(): + raise ValidationException( + translation( + lang, + en=f"User already has a valid subscription for this plan: {user_pk}", + es=f"Usuario ya tiene una suscripción válida para este plan: {user_pk}", + slug="user-already-has-valid-subscription", + ), + code=409, + ) + bag = Bag() bag.type = Bag.Type.BAG bag.user = user diff --git a/breathecode/payments/models.py b/breathecode/payments/models.py index 60f684f3c..8ca48582d 100644 --- a/breathecode/payments/models.py +++ b/breathecode/payments/models.py @@ -1516,17 +1516,12 @@ def __str__(self): return f"{self.user.email}: {self.service_item.service.slug} ({self.how_many})" -PENDING = "PENDING" -DONE = "DONE" -CANCELLED = "CANCELLED" -CONSUMPTION_SESSION_STATUS = [ - (PENDING, "Pending"), - (DONE, "Done"), - (CANCELLED, "Cancelled"), -] - - class ConsumptionSession(models.Model): + class Status(models.TextChoices): + PENDING = "PENDING", "Pending" + DONE = "DONE", "Done" + CANCELLED = "CANCELLED", "Cancelled" + operation_code = models.SlugField( default="default", help_text="Code that identifies the operation, it could be repeated" ) @@ -1535,9 +1530,7 @@ class ConsumptionSession(models.Model): eta = models.DateTimeField(help_text="Estimated time of arrival") duration = models.DurationField(blank=False, default=timedelta, help_text="Duration of the session") how_many = models.FloatField(default=0, help_text="How many units of this service can be used") - status = models.CharField( - max_length=12, choices=CONSUMPTION_SESSION_STATUS, default=PENDING, help_text="Status of the session" - ) + status = models.CharField(max_length=12, choices=Status, default=Status.PENDING, help_text="Status of the session") was_discounted = models.BooleanField(default=False, help_text="Was it discounted") request = models.JSONField( diff --git a/breathecode/payments/tests/actions/tests_validate_and_create_subscriptions.py b/breathecode/payments/tests/actions/tests_validate_and_create_subscriptions.py index a95a0b6b5..69386c4a8 100644 --- a/breathecode/payments/tests/actions/tests_validate_and_create_subscriptions.py +++ b/breathecode/payments/tests/actions/tests_validate_and_create_subscriptions.py @@ -2,7 +2,7 @@ Test /answer """ -from datetime import datetime +from datetime import datetime, timedelta from unittest.mock import MagicMock, call import pytest @@ -248,6 +248,47 @@ def test_no_payment_method(database: capy.Database, format: capy.Format, is_requ assert build_plan_financing.delay.call_args_list == [] +@pytest.mark.parametrize("is_request", [True, False]) +def test_plan_already_exists(database: capy.Database, format: capy.Format, is_request: bool, utc_now: datetime) -> None: + model = database.create( + user=1, + proof_of_payment=1, + plan={"time_of_life": None, "time_of_life_unit": None}, + financing_option={"how_many_months": 1}, + academy=1, + city=1, + country=1, + payment_method=1, + plan_financing={ + "valid_until": utc_now + timedelta(days=30), + "plan_expires_at": utc_now + timedelta(days=30), + "monthly_price": 100, + }, + ) + data = {"plans": [model.plan.slug], "user": model.user.id, "payment_method": 1} + academy = 1 + + if is_request: + data = get_request(data, user=model.user) + + with pytest.raises(ValidationException, match="user-already-has-valid-subscription"): + validate_and_create_subscriptions(data, model.user, model.proof_of_payment, academy, "en") + + assert database.list_of("payments.Bag") == [] + assert database.list_of("payments.Invoice") == [] + assert database.list_of("payments.ProofOfPayment") == [ + serialize_proof_of_payment( + data={ + "id": 1, + "created_by_id": 1, + "status": "PENDING", + } + ), + ] + + assert build_plan_financing.delay.call_args_list == [] + + @pytest.mark.parametrize("is_request", [True, False]) @pytest.mark.parametrize("field", ["id", "username", "email"]) def test_schedule_plan_financing( diff --git a/breathecode/payments/tests/urls/tests_me_service_slug_consumptionsession_id.py b/breathecode/payments/tests/urls/tests_me_service_slug_consumptionsession_id.py index 1c7a72e23..bcb16d604 100644 --- a/breathecode/payments/tests/urls/tests_me_service_slug_consumptionsession_id.py +++ b/breathecode/payments/tests/urls/tests_me_service_slug_consumptionsession_id.py @@ -182,6 +182,7 @@ def test_cancelled(bc: Breathecode, client: rfx.Client, utc_now, fake): data={ "duration": duration, "eta": utc_now + duration, + "status": "CANCELLED", }, ) ] diff --git a/breathecode/payments/views.py b/breathecode/payments/views.py index 49bb7b7a4..c7dfe75fe 100644 --- a/breathecode/payments/views.py +++ b/breathecode/payments/views.py @@ -1157,6 +1157,9 @@ def put(self, request, service_slug, consumptionsession_id): consumable = session.consumable reimburse_service_units.send_robust(instance=consumable, sender=consumable.__class__, how_many=how_many) + session.status = session.Status.CANCELLED + session.save() + return Response({"id": session.id, "status": "reversed"}, status=status.HTTP_200_OK)