From 92db9231cdefe3a3b6523535009d332e41927aef Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 19 Jul 2024 18:58:47 -0500 Subject: [PATCH 1/2] add balance to void services --- breathecode/payments/actions.py | 51 +++++ .../tests/urls/tests_me_service_consumable.py | 181 ++++++++++++++++++ breathecode/payments/views.py | 6 +- 3 files changed, 236 insertions(+), 2 deletions(-) diff --git a/breathecode/payments/actions.py b/breathecode/payments/actions.py index ec42423ce..e00175682 100644 --- a/breathecode/payments/actions.py +++ b/breathecode/payments/actions.py @@ -679,6 +679,57 @@ def filter_consumables( return queryset +def filter_void_consumable_balance(request: WSGIRequest, items: QuerySet[Consumable]): + consumables = items.filter(service_item__service__type="VOID") + + if ids := request.GET.get("service"): + try: + ids = [int(x) for x in ids.split(",")] + except Exception: + raise ValidationException("service param must be integer") + + consumables = consumables.filter(service_item__service__id__in=ids) + + if slugs := request.GET.get("service_slug"): + slugs = slugs.split(",") + + consumables = consumables.filter(service_item__service__slug__in=slugs) + + if not consumables: + return [] + + result = {} + + for consumable in consumables: + service = consumable.service_item.service + if service.id not in result: + result[service.id] = { + "balance": { + "unit": 0, + }, + "id": service.id, + "slug": service.slug, + "items": [], + } + + if consumable.how_many <= 0: + result[service.id]["balance"]["unit"] = -1 + + elif result[service.id]["balance"]["unit"] != -1: + result[service.id]["balance"]["unit"] += consumable.how_many + + result[service.id]["items"].append( + { + "id": consumable.id, + "how_many": consumable.how_many, + "unit_type": consumable.unit_type, + "valid_until": consumable.valid_until, + } + ) + + return result.values() + + def get_balance_by_resource(queryset: QuerySet, key: str): result = [] diff --git a/breathecode/payments/tests/urls/tests_me_service_consumable.py b/breathecode/payments/tests/urls/tests_me_service_consumable.py index 3136ab93d..3522a093b 100644 --- a/breathecode/payments/tests/urls/tests_me_service_consumable.py +++ b/breathecode/payments/tests/urls/tests_me_service_consumable.py @@ -142,6 +142,7 @@ def test__without_consumables(self): "mentorship_service_sets": [], "cohort_sets": [], "event_type_sets": [], + "voids": [], } assert json == expected @@ -166,6 +167,7 @@ def test__one_consumable__how_many_is_zero(self): "mentorship_service_sets": [], "cohort_sets": [], "event_type_sets": [], + "voids": [], } assert json == expected @@ -229,6 +231,7 @@ def test__nine_consumables__random_how_many__related_to_three_cohorts__without_c }, ], "event_type_sets": [], + "voids": [], } assert json == expected @@ -256,6 +259,7 @@ def test__nine_consumables__random_how_many__related_to_three_cohorts__with_wron "mentorship_service_sets": [], "cohort_sets": [], "event_type_sets": [], + "voids": [], } assert json == expected @@ -313,6 +317,7 @@ def test__nine_consumables__random_how_many__related_to_three_cohorts__with_coho }, ], "event_type_sets": [], + "voids": [], } assert json == expected @@ -377,6 +382,7 @@ def test__nine_consumables__related_to_three_mentorship_services__without_cohort ], "cohort_sets": [], "event_type_sets": [], + "voids": [], } assert json == expected @@ -404,6 +410,7 @@ def test__nine_consumables__related_to_three_mentorship_services__with_wrong_coh "cohort_sets": [], "mentorship_service_sets": [], "event_type_sets": [], + "voids": [], } assert json == expected @@ -463,6 +470,7 @@ def test__nine_consumables__related_to_three_mentorship_services__with_cohorts_i }, ], "event_type_sets": [], + "voids": [], } assert json == expected @@ -537,6 +545,7 @@ def test__nine_consumables__related_to_three_event_types__without_cohorts_in_que "items": [serialize_consumable(model.consumable[n]) for n in range(9)], }, ], + "voids": [], } assert json == expected @@ -574,6 +583,7 @@ def test__nine_consumables__related_to_three_event_types__with_wrong_cohorts_in_ "cohort_sets": [], "event_type_sets": [], "mentorship_service_sets": [], + "voids": [], } assert json == expected @@ -643,6 +653,7 @@ def test__nine_consumables__related_to_three_event_types__with_cohorts_in_querys }, ], "mentorship_service_sets": [], + "voids": [], } assert json == expected @@ -707,6 +718,7 @@ def test__nine_consumables__random_how_many__related_to_three_cohorts__without_c }, ], "event_type_sets": [], + "voids": [], } assert json == expected @@ -734,6 +746,7 @@ def test__nine_consumables__random_how_many__related_to_three_cohorts__with_wron "mentorship_service_sets": [], "cohort_sets": [], "event_type_sets": [], + "voids": [], } assert json == expected @@ -796,6 +809,7 @@ def test__nine_consumables__random_how_many__related_to_three_cohorts__with_coho }, ], "event_type_sets": [], + "voids": [], } assert json == expected @@ -859,6 +873,7 @@ def test__nine_consumables__related_to_three_mentorship_services__without_cohort ], "cohort_sets": [], "event_type_sets": [], + "voids": [], } assert json == expected @@ -886,6 +901,7 @@ def test__nine_consumables__related_to_three_mentorship_services__with_wrong_coh "cohort_sets": [], "mentorship_service_sets": [], "event_type_sets": [], + "voids": [], } assert json == expected @@ -948,6 +964,7 @@ def test__nine_consumables__related_to_three_mentorship_services__with_cohort_sl }, ], "event_type_sets": [], + "voids": [], } assert json == expected @@ -1021,6 +1038,7 @@ def test__nine_consumables__related_to_three_event_types__without_cohort_slugs_i "items": [serialize_consumable(model.consumable[n]) for n in range(9)], }, ], + "voids": [], } assert json == expected @@ -1058,6 +1076,7 @@ def test__nine_consumables__related_to_three_event_types__with_wrong_cohort_slug "cohort_sets": [], "event_type_sets": [], "mentorship_service_sets": [], + "voids": [], } assert json == expected @@ -1130,6 +1149,7 @@ def test__nine_consumables__related_to_three_event_types__with_cohort_slugs_in_q }, ], "mentorship_service_sets": [], + "voids": [], } assert json == expected @@ -1138,3 +1158,164 @@ def test__nine_consumables__related_to_three_event_types__with_cohort_slugs_in_q self.bc.database.list_of("payments.Consumable"), self.bc.format.to_dict(model.consumable), ) + + """ + 🔽🔽🔽 Get with nine Consumable and three Services, random how_many + """ + + @patch("django.utils.timezone.now", MagicMock(return_value=UTC_NOW)) + def test__nine_consumables__related_to_three_services__without_cohort_slugs_in_querystring(self): + consumables = [{"how_many": random.randint(1, 30), "service_item_id": math.floor(n / 3) + 1} for n in range(9)] + service_items = [{"service_id": n + 1} for n in range(3)] + + belong_to1 = consumables[:3] + belong_to2 = consumables[3:6] + belong_to3 = consumables[6:] + + how_many_belong_to1 = sum([x["how_many"] for x in belong_to1]) + how_many_belong_to2 = sum([x["how_many"] for x in belong_to2]) + how_many_belong_to3 = sum([x["how_many"] for x in belong_to3]) + + model = self.bc.database.create( + user=1, + consumable=consumables, + service=(3, {"type": "VOID"}), + service_item=service_items, + ) + self.client.force_authenticate(model.user) + + url = reverse_lazy("payments:me_service_consumable") + response = self.client.get(url) + self.client.force_authenticate(model.user) + + json = response.json() + serialized_consumables = [serialize_consumable(model.consumable[n]) for n in range(9)] + expected = { + "mentorship_service_sets": [], + "cohort_sets": [], + "event_type_sets": [], + "voids": [ + { + "balance": { + "unit": how_many_belong_to1, + }, + "id": model.service[0].id, + "slug": model.service[0].slug, + "items": serialized_consumables[:3], + }, + { + "balance": { + "unit": how_many_belong_to2, + }, + "id": model.service[1].id, + "slug": model.service[1].slug, + "items": serialized_consumables[3:6], + }, + { + "balance": { + "unit": how_many_belong_to3, + }, + "id": model.service[2].id, + "slug": model.service[2].slug, + "items": serialized_consumables[6:9], + }, + ], + } + + assert json == expected + assert response.status_code == status.HTTP_200_OK + assert self.bc.database.list_of("payments.Consumable") == self.bc.format.to_dict(model.consumable) + + @patch("django.utils.timezone.now", MagicMock(return_value=UTC_NOW)) + def test__nine_consumables__related_to_three_services__with_wrong_cohort_slugs_in_querystring(self): + consumables = [{"how_many": random.randint(1, 30), "service_item_id": math.floor(n / 3) + 1} for n in range(9)] + service_items = [{"service_id": n + 1} for n in range(3)] + + model = self.bc.database.create( + user=1, + consumable=consumables, + service=(3, {"type": "VOID"}), + service_item=service_items, + ) + self.client.force_authenticate(model.user) + + url = reverse_lazy("payments:me_service_consumable") + f"?service_slug=blabla1,blabla2,blabla3" + response = self.client.get(url) + self.client.force_authenticate(model.user) + + json = response.json() + expected = { + "cohort_sets": [], + "event_type_sets": [], + "mentorship_service_sets": [], + "voids": [], + } + + assert json == expected + assert response.status_code == status.HTTP_200_OK + assert self.bc.database.list_of("payments.Consumable") == self.bc.format.to_dict(model.consumable) + + @patch("django.utils.timezone.now", MagicMock(return_value=UTC_NOW)) + def test__nine_consumables__related_to_three_services__with_cohort_slugs_in_querystring(self): + consumables = [{"how_many": random.randint(1, 30), "service_item_id": math.floor(n / 3) + 1} for n in range(9)] + service_items = [{"service_id": n + 1} for n in range(3)] + belong_to1 = consumables[:3] + belong_to2 = consumables[3:6] + belong_to3 = consumables[6:] + + how_many_belong_to1 = sum([x["how_many"] for x in belong_to1]) + how_many_belong_to2 = sum([x["how_many"] for x in belong_to2]) + how_many_belong_to3 = sum([x["how_many"] for x in belong_to3]) + + model = self.bc.database.create( + user=1, + consumable=consumables, + service=(3, {"type": "VOID"}), + service_item=service_items, + ) + self.client.force_authenticate(model.user) + + url = ( + reverse_lazy("payments:me_service_consumable") + + f'?service_slug={",".join([x.slug for x in model.service])}' + ) + response = self.client.get(url) + self.client.force_authenticate(model.user) + + json = response.json() + serialized_consumables = [serialize_consumable(model.consumable[n]) for n in range(9)] + expected = { + "cohort_sets": [], + "event_type_sets": [], + "mentorship_service_sets": [], + "voids": [ + { + "balance": { + "unit": how_many_belong_to1, + }, + "id": model.service[0].id, + "slug": model.service[0].slug, + "items": serialized_consumables[:3], + }, + { + "balance": { + "unit": how_many_belong_to2, + }, + "id": model.service[1].id, + "slug": model.service[1].slug, + "items": serialized_consumables[3:6], + }, + { + "balance": { + "unit": how_many_belong_to3, + }, + "id": model.service[2].id, + "slug": model.service[2].slug, + "items": serialized_consumables[6:9], + }, + ], + } + + assert json == expected + assert response.status_code == status.HTTP_200_OK + assert self.bc.database.list_of("payments.Consumable") == self.bc.format.to_dict(model.consumable) diff --git a/breathecode/payments/views.py b/breathecode/payments/views.py index edf631f8c..24a8b39da 100644 --- a/breathecode/payments/views.py +++ b/breathecode/payments/views.py @@ -21,6 +21,7 @@ PlanFinder, add_items_to_bag, filter_consumables, + filter_void_consumable_balance, get_amount, get_amount_by_chosen_period, get_available_coupons, @@ -41,13 +42,13 @@ FinancialReputation, Invoice, MentorshipServiceSet, + PaymentMethod, Plan, PlanFinancing, PlanOffer, Service, ServiceItem, Subscription, - PaymentMethod, ) from breathecode.payments.serializers import ( GetAcademyServiceSmallSerializer, @@ -59,6 +60,7 @@ GetInvoiceSmallSerializer, GetMentorshipServiceSetSerializer, GetMentorshipServiceSetSmallSerializer, + GetPaymentMethod, GetPlanFinancingSerializer, GetPlanOfferSerializer, GetPlanSerializer, @@ -69,7 +71,6 @@ POSTAcademyServiceSerializer, PUTAcademyServiceSerializer, ServiceSerializer, - GetPaymentMethod, ) from breathecode.payments.services.stripe import Stripe from breathecode.payments.signals import reimburse_service_units @@ -597,6 +598,7 @@ def get(self, request): "mentorship_service_sets": get_balance_by_resource(mentorship_services, "mentorship_service_set"), "cohort_sets": get_balance_by_resource(cohorts, "cohort_set"), "event_type_sets": get_balance_by_resource(event_types, "event_type_set"), + "voids": filter_void_consumable_balance(request, items), } return Response(balance) From 0e11a974b419d7eb54b8c49c4275af1be6ce7270 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 19 Jul 2024 19:05:09 -0500 Subject: [PATCH 2/2] fix checks.yml --- .github/workflows/checks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 84806ea96..461b4e924 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -5,6 +5,7 @@ name: Check on: push: {} + pull_request: {} env: PYTHON_VERSION: 3.12.3