Добавлено поле combine_mode с тремя режимами: - stack - складывать с другими скидками - max_only - применять только максимальную - exclusive - отменяет все остальные скидки Изменения: - Модель Discount: добавлено поле combine_mode - Calculator: новый класс DiscountCombiner, методы возвращают списки скидок - Applier: создание нескольких DiscountApplication записей - Admin: отображение combine_mode с иконками - POS API: возвращает списки применённых скидок - POS UI: отображение нескольких скидок с иконками режимов Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
125 lines
4.7 KiB
Python
125 lines
4.7 KiB
Python
from django.db import models
|
||
from .base import BaseDiscount
|
||
|
||
|
||
class Discount(BaseDiscount):
|
||
"""
|
||
Основная модель скидки.
|
||
Наследует все поля из BaseDiscount и добавляет специфические параметры.
|
||
"""
|
||
|
||
# Для scope='order' - минимальная сумма заказа
|
||
min_order_amount = models.DecimalField(
|
||
max_digits=10,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="Мин. сумма заказа",
|
||
help_text="Скидка применяется только если сумма заказа >= этого значения"
|
||
)
|
||
|
||
# Для scope='product' и scope='category' - товары и категории
|
||
products = models.ManyToManyField(
|
||
'products.Product',
|
||
blank=True,
|
||
related_name='discounts',
|
||
verbose_name="Товары"
|
||
)
|
||
|
||
categories = models.ManyToManyField(
|
||
'products.ProductCategory',
|
||
blank=True,
|
||
related_name='discounts',
|
||
verbose_name="Категории"
|
||
)
|
||
|
||
# Исключения (товары, к которым скидка НЕ применяется)
|
||
excluded_products = models.ManyToManyField(
|
||
'products.Product',
|
||
blank=True,
|
||
related_name='excluded_from_discounts',
|
||
verbose_name="Исключенные товары"
|
||
)
|
||
|
||
# Автоматическая скидка (не требует промокода)
|
||
is_auto = models.BooleanField(
|
||
default=False,
|
||
verbose_name="Автоматическая",
|
||
help_text="Применяется автоматически при выполнении условий"
|
||
)
|
||
|
||
# Режим объединения с другими скидками
|
||
combine_mode = models.CharField(
|
||
max_length=20,
|
||
choices=BaseDiscount.COMBINE_MODE_CHOICES,
|
||
default='max_only',
|
||
verbose_name="Режим объединения",
|
||
help_text="stack = суммировать с другими, max_only = применить максимальную, exclusive = отменить остальные"
|
||
)
|
||
|
||
class Meta:
|
||
verbose_name = "Скидка"
|
||
verbose_name_plural = "Скидки"
|
||
indexes = [
|
||
models.Index(fields=['is_active']),
|
||
models.Index(fields=['scope']),
|
||
models.Index(fields=['discount_type']),
|
||
models.Index(fields=['is_auto']),
|
||
]
|
||
|
||
def applies_to_product(self, product):
|
||
"""
|
||
Проверить, применяется ли скидка к товару.
|
||
|
||
Args:
|
||
product: Объект Product
|
||
|
||
Returns:
|
||
bool: True если скидка применяется к товару
|
||
"""
|
||
# Проверяем исключения
|
||
if self.excluded_products.filter(id=product.id).exists():
|
||
return False
|
||
|
||
# Если scope='product', проверяем прямое соответствие
|
||
if self.scope == 'product':
|
||
return self.products.filter(id=product.id).exists()
|
||
|
||
# Если scope='category', проверяем категории товара
|
||
if self.scope == 'category':
|
||
if not self.categories.exists():
|
||
return False
|
||
product_categories = product.categories.all()
|
||
return self.categories.filter(id__in=product_categories).exists()
|
||
|
||
return False
|
||
|
||
def get_applicable_products(self):
|
||
"""
|
||
Получить queryset товаров, к которым применяется эта скидка.
|
||
|
||
Returns:
|
||
QuerySet: Товары, к которым применяется скидка
|
||
"""
|
||
from products.models import Product
|
||
|
||
if self.scope == 'product':
|
||
qs = self.products.all()
|
||
# Исключаем исключенные товары
|
||
if self.excluded_products.exists():
|
||
qs = qs.exclude(id__in=self.excluded_products.values_list('id', flat=True))
|
||
return qs
|
||
|
||
if self.scope == 'category':
|
||
# Товары из указанных категорий
|
||
product_ids = Product.objects.filter(
|
||
categories__in=self.categories.all()
|
||
).values_list('id', flat=True).distinct()
|
||
# Исключаем исключенные товары
|
||
if self.excluded_products.exists():
|
||
excluded_ids = self.excluded_products.values_list('id', flat=True)
|
||
product_ids = set(product_ids) - set(excluded_ids)
|
||
return Product.objects.filter(id__in=product_ids)
|
||
|
||
return Product.objects.none()
|