diff --git a/payments/managers.py b/payments/managers.py new file mode 100644 index 00000000..c4684f87 --- /dev/null +++ b/payments/managers.py @@ -0,0 +1,44 @@ +from django.db.models import BooleanField, Case, F, Manager, Q, QuerySet, Sum, Value, When +from django.db.models.functions import Coalesce +from django.utils.timezone import now + + +class GatewayQuerySet(QuerySet): + def with_usage_sums(self) -> QuerySet: + today = now().date() + current_month_start = today.replace(day=1) + return self.annotate( + daily_sum=Coalesce(Sum("transactions__amount", filter=Q(transactions__created__date=today)), Value(0.0)), + monthly_sum=Coalesce( + Sum("transactions__amount", filter=Q(transactions__created__gte=current_month_start)), Value(0.0) + ), + ) + + def can_be_used(self) -> QuerySet: + qs = self.with_usage_sums() + qs = qs.annotate( + daily_ok=Case( + When(daily_limit=0, then=Value(True)), + When(daily_sum__lt=F("daily_limit"), then=Value(True)), + default=Value(False), + output_field=BooleanField(), + ), + monthly_ok=Case( + When(monthly_limit=0, then=Value(True)), + When(monthly_sum__lt=F("monthly_limit"), then=Value(True)), + default=Value(False), + output_field=BooleanField(), + ), + ) + return qs.annotate( + can_be_used=Case( + When(daily_ok=True, monthly_ok=True, is_active=True, then=Value(True)), + default=Value(False), + output_field=BooleanField(), + ) + ).order_by("-priority") + + +class GatewayManager(Manager.from_queryset(GatewayQuerySet)): + def get_queryset(self) -> QuerySet: + return super().get_queryset().can_be_used() diff --git a/payments/models.py b/payments/models.py index 78fc9c68..398e3525 100644 --- a/payments/models.py +++ b/payments/models.py @@ -20,6 +20,7 @@ from django.utils.translation import gettext_lazy as _ from core.abstract import NiceModel from evibes.utils.misc import create_object from payments.gateways import AbstractGateway +from payments.managers import GatewayManager class Transaction(NiceModel): @@ -87,6 +88,7 @@ class Balance(NiceModel): class Gateway(NiceModel): + objects = GatewayManager() name = CharField(max_length=20, null=False, blank=False, verbose_name=_("name")) default_currency = CharField( max_length=4, @@ -138,6 +140,9 @@ class Gateway(NiceModel): @property def can_be_used(self) -> bool: + if not self.is_active: + return False + today = now().date() current_month_start = today.replace(day=1) diff --git a/payments/signals.py b/payments/signals.py index 32e3dd59..d108edac 100644 --- a/payments/signals.py +++ b/payments/signals.py @@ -5,7 +5,7 @@ from typing import Any from django.db.models.signals import post_save from django.dispatch import receiver -from payments.models import Balance, Transaction +from payments.models import Balance, Transaction, Gateway from payments.utils.emailing import balance_deposit_email from vibes_auth.models import User @@ -24,7 +24,7 @@ def create_balance_on_user_creation_signal(instance: User, created: bool, **kwar def process_transaction_changes(instance: Transaction, created: bool, **kwargs: dict[Any, Any]) -> None: if created: if not instance.gateway: - raise ValueError("gateway is required to process a transaction") + instance.gateway = Gateway.objects.can_be_used().first() try: gateway = instance.gateway.get_integration_class_object() gateway.process_transaction(instance)