Skip to content

Commit

Permalink
Merge pull request #1540 from jefer94/development
Browse files Browse the repository at this point in the history
fix changing plan financing
  • Loading branch information
jefer94 authored Jan 28, 2025
2 parents 5ede2e8 + def948b commit dbd03bf
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def sort_schema(table):

for field in schema:
if field.field_type == "RECORD":
field._fields = sorted(field._fields, key=lambda v: v.name)
field._properties["fields"].sort(key=lambda v: v["name"])

return schema

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from unittest.mock import MagicMock, PropertyMock, call, patch
from urllib.parse import urlencode

import pytest
import requests

import breathecode.certificate.signals as signals
Expand All @@ -28,6 +29,11 @@
)


@pytest.fixture(autouse=True)
def setup(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("SCREENSHOT_MACHINE_KEY", "00000")


class ActionCertificateScreenshotTestCase(CertificateTestCase):
"""Tests action certificate_screenshot"""

Expand Down Expand Up @@ -122,24 +128,19 @@ def test_certificate_screenshot__with_invalid_preview_url__equal_to_empty_string
],
)

self.assertEqual(
requests.get.call_args_list,
[
call(f"https://api.screenshotmachine.com?{query_string}", stream=True),
],
)
self.assertEqual(
signals.user_specialty_saved.send_robust.call_args_list,
[
# Mixer
call(instance=model.user_specialty, sender=model.user_specialty.__class__),
# Save
call(instance=model.user_specialty, sender=model.user_specialty.__class__),
],
)
assert requests.get.call_args_list == [
call(f"https://api.screenshotmachine.com?{query_string}", stream=True),
]

self.assertEqual(File.upload.call_args_list, [call(b"mailgun response", public=True)])
self.assertEqual(File.url.call_args_list, [call()])
assert signals.user_specialty_saved.send_robust.call_args_list == [
# Mixer
call(instance=model.user_specialty, sender=model.user_specialty.__class__),
# Save
call(instance=model.user_specialty, sender=model.user_specialty.__class__),
]

assert File.upload.call_args_list == [call(b"mailgun response", public=True)]
assert File.url.call_args_list == [call()]

"""
🔽🔽🔽 Invalid preview_url, equal to None
Expand Down Expand Up @@ -188,24 +189,19 @@ def test_certificate_screenshot__with_invalid_preview_url__equal_to_none(self):
],
)

self.assertEqual(
requests.get.call_args_list,
[
call(f"https://api.screenshotmachine.com?{query_string}", stream=True),
],
)
self.assertEqual(
signals.user_specialty_saved.send_robust.call_args_list,
[
# Mixer
call(instance=model.user_specialty, sender=model.user_specialty.__class__),
# Save
call(instance=model.user_specialty, sender=model.user_specialty.__class__),
],
)
assert requests.get.call_args_list == [
call(f"https://api.screenshotmachine.com?{query_string}", stream=True),
]

self.assertEqual(File.upload.call_args_list, [call(b"mailgun response", public=True)])
self.assertEqual(File.url.call_args_list, [call()])
assert signals.user_specialty_saved.send_robust.call_args_list == [
# Mixer
call(instance=model.user_specialty, sender=model.user_specialty.__class__),
# Save
call(instance=model.user_specialty, sender=model.user_specialty.__class__),
]

assert File.upload.call_args_list == [call(b"mailgun response", public=True)]
assert File.url.call_args_list == [call()]

"""
🔽🔽🔽 Invalid preview_url, the object exists in gcloud
Expand Down
10 changes: 8 additions & 2 deletions breathecode/payments/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,13 @@ def build_subscription(

@task(bind=True, priority=TaskPriority.WEB_SERVICE_PAYMENT.value)
def build_plan_financing(
self, bag_id: int, invoice_id: int, is_free: bool = False, conversion_info: Optional[str] = "", **_: Any
self,
bag_id: int,
invoice_id: int,
is_free: bool = False,
conversion_info: Optional[str] = "",
price: Optional[float] = None,
**_: Any,
):
logger.info(f"Starting build_plan_financing for bag {bag_id}")

Expand Down Expand Up @@ -876,7 +882,7 @@ def build_plan_financing(
selected_mentorship_service_set=mentorship_service_set,
valid_until=invoice.paid_at + relativedelta(months=months - 1),
plan_expires_at=invoice.paid_at + delta,
monthly_price=invoice.amount,
monthly_price=price or invoice.amount,
status="ACTIVE",
conversion_info=parsed_conversion_info,
)
Expand Down
82 changes: 82 additions & 0 deletions breathecode/payments/tests/tasks/tests_build_plan_financing.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,88 @@ def test_subscription_was_created(self):
],
)

"""
🔽🔽🔽 With Bag and Invoice with amount, passing the price
"""

@patch("logging.Logger.info", MagicMock())
@patch("logging.Logger.error", MagicMock())
@patch.object(timezone, "now", MagicMock(return_value=UTC_NOW))
@patch("breathecode.payments.tasks.build_service_stock_scheduler_from_plan_financing.delay", MagicMock())
def test_subscription_was_created__with_price(self):
amount = (random.random() * 99) + 1
price = (random.random() * 99) + 1
bag = {
"status": "PAID",
"was_delivered": False,
"chosen_period": random.choice(["MONTH", "QUARTER", "HALF", "YEAR"]),
}
invoice = {"status": "FULFILLED", "amount": amount}
plan = {"is_renewable": False}

model = self.bc.database.create(bag=bag, invoice=invoice, plan=plan)

# remove prints from mixer
logging.Logger.info.call_args_list = []
logging.Logger.error.call_args_list = []

months = model.bag.how_many_installments

build_plan_financing.delay(1, 1, price=price)

self.assertEqual(self.bc.database.list_of("admissions.Cohort"), [])

self.assertEqual(
logging.Logger.info.call_args_list,
[
call("Starting build_plan_financing for bag 1"),
call("PlanFinancing was created with id 1"),
],
)
self.assertEqual(logging.Logger.error.call_args_list, [])

self.assertEqual(
self.bc.database.list_of("payments.Bag"),
[
{
**self.bc.format.to_dict(model.bag),
"was_delivered": True,
},
],
)
self.assertEqual(
self.bc.database.list_of("payments.Invoice"),
[
{
**self.bc.format.to_dict(model.invoice),
# 'monthly_price': amount,
},
],
)
self.assertEqual(
self.bc.database.list_of("payments.PlanFinancing"),
[
plan_financing_item(
{
"conversion_info": None,
"monthly_price": price,
"valid_until": model.invoice.paid_at + relativedelta(months=months - 1),
"next_payment_at": model.invoice.paid_at + relativedelta(months=1),
"plan_expires_at": model.invoice.paid_at
+ calculate_relative_delta(model.plan.time_of_life, model.plan.time_of_life_unit),
}
),
],
)

self.assertEqual(tasks.build_service_stock_scheduler_from_plan_financing.delay.call_args_list, [call(1)])
self.bc.check.calls(
activity_tasks.add_activity.delay.call_args_list,
[
call(1, "bag_created", related_type="payments.Bag", related_id=1),
],
)

"""
🔽🔽🔽 With Bag with Cohort and Invoice with amount
"""
Expand Down
8 changes: 5 additions & 3 deletions breathecode/payments/tests/urls/tests_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,7 @@ def test_with_installments(bc: Breathecode, client: APIClient):
bc.check.queryset_with_pks(model.bag.plans.all(), [1])
bc.check.queryset_with_pks(model.bag.service_items.all(), [1])
assert tasks.build_subscription.delay.call_args_list == []
assert tasks.build_plan_financing.delay.call_args_list == [call(1, 1, conversion_info="")]
assert tasks.build_plan_financing.delay.call_args_list == [call(1, 1, conversion_info="", price=charge)]
assert tasks.build_free_subscription.delay.call_args_list == []

bc.check.calls(admissions_tasks.build_cohort_user.delay.call_args_list, [])
Expand Down Expand Up @@ -1251,7 +1251,9 @@ def test_with_installments_with_conversion_info(bc: Breathecode, client: APIClie
bc.check.queryset_with_pks(model.bag.plans.all(), [1])
bc.check.queryset_with_pks(model.bag.service_items.all(), [1])
assert tasks.build_subscription.delay.call_args_list == []
assert tasks.build_plan_financing.delay.call_args_list == [call(1, 1, conversion_info="{'landing_url': '/home'}")]
assert tasks.build_plan_financing.delay.call_args_list == [
call(1, 1, conversion_info="{'landing_url': '/home'}", price=charge)
]
assert tasks.build_free_subscription.delay.call_args_list == []

bc.check.calls(admissions_tasks.build_cohort_user.delay.call_args_list, [])
Expand Down Expand Up @@ -1356,7 +1358,7 @@ def test_coupons__with_installments(bc: Breathecode, client: APIClient):
bc.check.queryset_with_pks(model.bag.plans.all(), [1])
bc.check.queryset_with_pks(model.bag.service_items.all(), [1])
assert tasks.build_subscription.delay.call_args_list == []
assert tasks.build_plan_financing.delay.call_args_list == [call(1, 1, conversion_info="")]
assert tasks.build_plan_financing.delay.call_args_list == [call(1, 1, conversion_info="", price=charge)]
assert tasks.build_free_subscription.delay.call_args_list == []

bc.check.calls(admissions_tasks.build_cohort_user.delay.call_args_list, [])
Expand Down
13 changes: 10 additions & 3 deletions breathecode/payments/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
from breathecode.payments.signals import reimburse_service_units
from breathecode.utils import APIViewExtensions, getLogger, validate_conversion_info
from breathecode.utils.decorators.capable_of import capable_of
from breathecode.utils.decorators.consume import discount_consumption_sessions
from breathecode.utils.redis import Lock

logger = getLogger(__name__)
Expand Down Expand Up @@ -1116,12 +1117,16 @@ def put(self, request, service_slug, hash=None):
if session:
return Response({"id": session.id, "status": "ok"}, status=status.HTTP_200_OK)

consumable = Consumable.get(user=request.user, lang=lang, service=service_slug, service_type="VOID")
if consumable is None:
consumables = Consumable.list(user=request.user, lang=lang, service=service_slug, service_type="VOID")

consumables = discount_consumption_sessions(consumables)
if consumables.count() == 0:
raise PaymentException(
translation(lang, en="Insuficient credits", es="Créditos insuficientes", slug="insufficient-credits")
)

consumable = consumables.first()

session_duration = consumable.service_item.service.session_duration or timedelta(minutes=1)
session = ConsumptionSession.build_session(
request,
Expand Down Expand Up @@ -2041,7 +2046,9 @@ def post(self, request):
tasks.build_free_subscription.delay(bag.id, invoice.id, conversion_info=conversion_info)

elif bag.how_many_installments > 0:
tasks.build_plan_financing.delay(bag.id, invoice.id, conversion_info=conversion_info)
tasks.build_plan_financing.delay(
bag.id, invoice.id, conversion_info=conversion_info, price=original_price
)

else:
tasks.build_subscription.delay(bag.id, invoice.id, conversion_info=conversion_info)
Expand Down
37 changes: 21 additions & 16 deletions breathecode/utils/decorators/consume.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import traceback
from datetime import datetime, timedelta
from typing import Any, Callable, Optional, TypedDict, Unpack
from typing import Any, Callable, Optional, TypedDict, TypeVar, Unpack

from adrf.requests import AsyncRequest
from asgiref.sync import sync_to_async
Expand All @@ -23,9 +23,10 @@

from ..exceptions import ProgrammingError

__all__ = ["consume", "Consumer", "ServiceContext"]
__all__ = ["consume", "Consumer", "ServiceContext", "discount_consumption_sessions", "adiscount_consumption_sessions"]

logger = logging.getLogger(__name__)
T = TypeVar("T")


class Flags(TypedDict):
Expand Down Expand Up @@ -183,6 +184,22 @@ def render_html_error(request, kwargs, service, e):
)


def discount_consumption_sessions(consumables: QuerySet[T]) -> QuerySet[T]:
# exclude consumables that is being used in a session.
for item in consumables.filter(consumptionsession__status="PENDING").exclude(how_many=0):
sum = item.consumptionsession_set.filter(status="PENDING").aggregate(Sum("how_many"))

if item.how_many - sum["how_many__sum"] == 0:
consumables = consumables.exclude(id=item.id)

return consumables


@sync_to_async
def adiscount_consumption_sessions(consumables: QuerySet[T]) -> QuerySet[T]:
return discount_consumption_sessions(consumables)


def consume(service: str, consumer: Optional[Consumer] = None, format: str = "json") -> callable:
"""Check if the current user can access to the resource through of permissions."""

Expand Down Expand Up @@ -271,13 +288,7 @@ def wrapper(*args, **kwargs):

# exclude consumables that is being used in a session.
if consumer and context["lifetime"]:
consumables = context["consumables"]
for item in consumables.filter(consumptionsession__status="PENDING").exclude(how_many=0):

sum = item.consumptionsession_set.filter(status="PENDING").aggregate(Sum("how_many"))

if item.how_many - sum["how_many__sum"] == 0:
context["consumables"] = context["consumables"].exclude(id=item.id)
context["consumables"] = discount_consumption_sessions(context["consumables"])

if context["price"] and context["consumables"].count() == 0:
raise PaymentException(
Expand Down Expand Up @@ -386,13 +397,7 @@ async def async_wrapper(*args, **kwargs):

# exclude consumables that is being used in a session.
if consumer and context["lifetime"]:
consumables: QuerySet[Consumable] = context["consumables"]
for item in consumables.filter(consumptionsession__status="PENDING").exclude(how_many=0):

sum = await item.consumptionsession_set.filter(status="PENDING").aaggregate(Sum("how_many"))

if item.how_many - sum["how_many__sum"] == 0:
context["consumables"] = context["consumables"].exclude(id=item.id)
context["consumables"] = await adiscount_consumption_sessions(context["consumables"])

if context["price"] and await context["consumables"].acount() == 0:
raise PaymentException(
Expand Down
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ def redirect_url(*args, **kwargs):
if "academy" in kwargs:
kwargs["academy"] = kwargs["academy"].id

return JsonResponse(kwargs, status=kwargs["status"])
return JsonResponse(kwargs, status=kwargs.get("status", 999))

monkeypatch.setattr(
shortcuts,
Expand Down

0 comments on commit dbd03bf

Please sign in to comment.