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:
parent
6fa037390c
commit
20d5f5db21
3 changed files with 90 additions and 13 deletions
|
|
@ -143,6 +143,13 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel): # type: ignore [
|
||||||
verbose_name=_("response file"),
|
verbose_name=_("response file"),
|
||||||
help_text=_("vendor's last processing response"),
|
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:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
@ -739,9 +746,8 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel): # t
|
||||||
verbose_name=_("product image"),
|
verbose_name=_("product image"),
|
||||||
upload_to=get_product_uuid_as_path,
|
upload_to=get_product_uuid_as_path,
|
||||||
)
|
)
|
||||||
priority = IntegerField(
|
priority = PositiveIntegerField(
|
||||||
default=1,
|
default=1,
|
||||||
validators=[MinValueValidator(1)],
|
|
||||||
help_text=_("determines the order in which images are displayed"),
|
help_text=_("determines the order in which images are displayed"),
|
||||||
verbose_name=_("display priority"),
|
verbose_name=_("display priority"),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import shutil
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any
|
from typing import Any, Type
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from celery.app import shared_task
|
from celery.app import shared_task
|
||||||
|
|
@ -14,7 +14,8 @@ from django.core.cache import cache
|
||||||
|
|
||||||
from core.models import Product, Promotion
|
from core.models import Product, Promotion
|
||||||
from core.utils.caching import set_default_cache
|
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
|
from evibes.settings import MEDIA_ROOT
|
||||||
|
|
||||||
logger = get_task_logger(__name__)
|
logger = get_task_logger(__name__)
|
||||||
|
|
@ -39,16 +40,15 @@ def update_products_task() -> tuple[bool, str]:
|
||||||
|
|
||||||
if not update_products_task_running:
|
if not update_products_task_running:
|
||||||
cache.set("update_products_task_running", True, 86400)
|
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:
|
for vendor in vendors:
|
||||||
vendor = vendor_class()
|
|
||||||
try:
|
try:
|
||||||
vendor.update_stock()
|
vendor.update_stock()
|
||||||
except VendorInactiveError:
|
except VendorInactiveError:
|
||||||
logger.info(f"Skipping {vendor_class} due to inactivity")
|
logger.info(f"Skipping {vendor.__str__} due to inactivity")
|
||||||
except Exception as e:
|
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()
|
delete_stale()
|
||||||
|
|
||||||
|
|
@ -70,10 +70,9 @@ def update_orderproducts_task() -> tuple[bool, str]:
|
||||||
message confirming the successful execution of the task.
|
message confirming the successful execution of the task.
|
||||||
:rtype: Tuple[bool, str]
|
:rtype: Tuple[bool, str]
|
||||||
"""
|
"""
|
||||||
vendors_classes: list[Any] = []
|
vendors: list[Type[AbstractVendor]] = get_vendors_integrations()
|
||||||
|
|
||||||
for vendor_class in vendors_classes:
|
for vendor in vendors:
|
||||||
vendor = vendor_class()
|
|
||||||
vendor.update_order_products_statuses()
|
vendor.update_order_products_statuses()
|
||||||
|
|
||||||
return True, "Success"
|
return True, "Success"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,18 @@
|
||||||
from constance import config
|
from constance import config
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.postgres.indexes import GinIndex
|
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 django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.abstract import NiceModel
|
from core.abstract import NiceModel
|
||||||
|
|
@ -20,6 +32,7 @@ class Transaction(NiceModel):
|
||||||
related_name="payments_transactions",
|
related_name="payments_transactions",
|
||||||
)
|
)
|
||||||
process = JSONField(verbose_name=_("processing details"), default=dict)
|
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):
|
def __str__(self):
|
||||||
return (
|
return (
|
||||||
|
|
@ -67,3 +80,62 @@ class Balance(NiceModel):
|
||||||
if self.amount != 0.0 and len(str(self.amount).split(".")[1]) > 2:
|
if self.amount != 0.0 and len(str(self.amount).split(".")[1]) > 2:
|
||||||
self.amount = round(self.amount, 2)
|
self.amount = round(self.amount, 2)
|
||||||
super().save(**kwargs)
|
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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue