Features: 1) Add support for linking Gateway to transactions including constraints and limits; 2) Implement new Gateway model with transactional rules and currency configurations; 3) Add integration_path field to Vendor for dynamic integrations; 4) Improve task logic to dynamically use vendor integrations for stock updates and order statuses.

Fixes: 1) Replace `IntegerField` with `PositiveIntegerField` for product image priority to enforce positive values.

Extra: 1) Optimize imports for consistent formatting; 2) Clarify logging messages in tasks; 3) Minor docstring and help text updates.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-10-15 14:25:58 +03:00
parent 6fa037390c
commit 20d5f5db21
3 changed files with 90 additions and 13 deletions

View file

@ -143,6 +143,13 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel): # type: ignore [
verbose_name=_("response file"),
help_text=_("vendor's last processing response"),
)
integration_path = CharField(
null=True,
blank=True,
max_length=255,
help_text=_("vendor's integration file path"),
verbose_name=_("integration path"),
)
def __str__(self) -> str:
return self.name
@ -739,9 +746,8 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel): # t
verbose_name=_("product image"),
upload_to=get_product_uuid_as_path,
)
priority = IntegerField(
priority = PositiveIntegerField(
default=1,
validators=[MinValueValidator(1)],
help_text=_("determines the order in which images are displayed"),
verbose_name=_("display priority"),
)

View file

@ -4,7 +4,7 @@ import shutil
import uuid
from datetime import date, timedelta
from time import sleep
from typing import Any
from typing import Any, Type
import requests
from celery.app import shared_task
@ -14,7 +14,8 @@ from django.core.cache import cache
from core.models import Product, Promotion
from core.utils.caching import set_default_cache
from core.vendors import VendorInactiveError, delete_stale
from core.utils.vendors import get_vendors_integrations
from core.vendors import VendorInactiveError, delete_stale, AbstractVendor
from evibes.settings import MEDIA_ROOT
logger = get_task_logger(__name__)
@ -39,16 +40,15 @@ def update_products_task() -> tuple[bool, str]:
if not update_products_task_running:
cache.set("update_products_task_running", True, 86400)
vendors_classes: list[Any] = []
vendors: list[Type[AbstractVendor]] = get_vendors_integrations()
for vendor_class in vendors_classes:
vendor = vendor_class()
for vendor in vendors:
try:
vendor.update_stock()
except VendorInactiveError:
logger.info(f"Skipping {vendor_class} due to inactivity")
logger.info(f"Skipping {vendor.__str__} due to inactivity")
except Exception as e:
logger.warning(f"Skipping {vendor_class} due to error: {e!s}")
logger.warning(f"Skipping {vendor.__str__} due to error: {e!s}")
delete_stale()
@ -70,10 +70,9 @@ def update_orderproducts_task() -> tuple[bool, str]:
message confirming the successful execution of the task.
:rtype: Tuple[bool, str]
"""
vendors_classes: list[Any] = []
vendors: list[Type[AbstractVendor]] = get_vendors_integrations()
for vendor_class in vendors_classes:
vendor = vendor_class()
for vendor in vendors:
vendor.update_order_products_statuses()
return True, "Success"

View file

@ -1,6 +1,18 @@
from constance import config
from django.conf import settings
from django.contrib.postgres.indexes import GinIndex
from django.db.models import CASCADE, CharField, FloatField, ForeignKey, JSONField, OneToOneField, QuerySet
from django.db.models import (
CASCADE,
CharField,
FloatField,
ForeignKey,
JSONField,
OneToOneField,
PositiveIntegerField,
QuerySet,
Sum,
)
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from core.abstract import NiceModel
@ -20,6 +32,7 @@ class Transaction(NiceModel):
related_name="payments_transactions",
)
process = JSONField(verbose_name=_("processing details"), default=dict)
gateway = ForeignKey("payments.Gateway", on_delete=CASCADE, blank=True, null=True, related_name="transactions")
def __str__(self):
return (
@ -67,3 +80,62 @@ class Balance(NiceModel):
if self.amount != 0.0 and len(str(self.amount).split(".")[1]) > 2:
self.amount = round(self.amount, 2)
super().save(**kwargs)
class Gateway(NiceModel):
name = CharField(max_length=20, null=False, blank=False, verbose_name=_("name"))
default_currency = CharField(
max_length=3, null=False, blank=False, verbose_name=_("default currency"), choices=settings.CURRENCIES
)
currencies = CharField(
max_length=3,
null=False,
blank=False,
verbose_name=_("currencies"),
help_text=_(f"comma separated list of currencies supported by this gateway, choose from {settings.CURRENCIES}"),
)
integration_path = CharField(max_length=255, null=True, blank=True)
minimum_transaction_amount = FloatField(
null=False, blank=False, default=0, verbose_name=_("minimum transaction amount")
)
maximum_transaction_amount = FloatField(
null=False, blank=False, default=0, verbose_name=_("maximum transaction amount")
)
daily_limit = PositiveIntegerField(
null=False,
blank=False,
default=0,
verbose_name=_("daily limit"),
help_text=_("daily sum limit of transactions' amounts. 0 means no limit"),
)
monthly_limit = PositiveIntegerField(
null=False,
blank=False,
default=0,
verbose_name=_("monthly limit"),
help_text=_("monthly sum limit of transactions' amounts. 0 means no limit"),
)
priority = PositiveIntegerField(null=False, blank=False, default=10, verbose_name=_("priority"), unique=True)
def __str__(self):
return self.name
class Meta:
verbose_name = _("payment gateway")
verbose_name_plural = _("payment gateways")
@property
def can_be_used(self) -> bool:
today = now().date()
current_month_start = today.replace(day=1)
daily_sum = self.transactions.filter(created__date=today).aggregate(total=Sum("amount"))["total"] or 0
monthly_sum = (
self.transactions.filter(created__gte=current_month_start).aggregate(total=Sum("amount"))["total"] or 0
)
daily_ok = self.daily_limit == 0 or daily_sum < self.daily_limit
monthly_ok = self.monthly_limit == 0 or monthly_sum < self.monthly_limit
return daily_ok and monthly_ok