Files
octopus/myproject/discounts/models/discount.py
Andrey Smakotin 241625eba7 feat(discounts): добавлено приложение скидок
Создано новое Django приложение для управления скидками:

Модели:
- BaseDiscount: абстрактный базовый класс с общими полями
- Discount: основная модель скидки (процент/фикс, на заказ/товар/категорию)
- PromoCode: промокоды для активации скидок
- DiscountApplication: история применения скидок

Сервисы:
- DiscountCalculator: расчёт скидок для корзины и заказов
- DiscountApplier: применение скидок к заказам (атомарно)
- DiscountValidator: валидация промокодов и условий

Админ-панель:
- DiscountAdmin: управление скидками
- PromoCodeAdmin: управление промокодами
- DiscountApplicationAdmin: история применения (только чтение)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 00:30:14 +03:00

116 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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="Применяется автоматически при выполнении условий"
)
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()