from django.db import models from django.core.exceptions import ValidationError class BaseDiscount(models.Model): """ Абстрактный базовый класс для всех типов скидок. Содержит общие поля и логику валидации. """ DISCOUNT_TYPE_CHOICES = [ ('percentage', 'Процент'), ('fixed_amount', 'Фиксированная сумма'), ] SCOPE_CHOICES = [ ('order', 'На весь заказ'), ('product', 'На товар'), ('category', 'На категорию товаров'), ] COMBINE_MODE_CHOICES = [ ('stack', 'Складывать (суммировать)'), ('max_only', 'Только максимум'), ('exclusive', 'Исключающая (отменяет остальные)'), ] name = models.CharField( max_length=200, verbose_name="Название скидки" ) description = models.TextField( blank=True, verbose_name="Описание" ) discount_type = models.CharField( max_length=20, choices=DISCOUNT_TYPE_CHOICES, verbose_name="Тип скидки" ) value = models.DecimalField( max_digits=10, decimal_places=2, verbose_name="Значение", help_text="Процент (0-100) или сумма в рублях" ) scope = models.CharField( max_length=20, choices=SCOPE_CHOICES, default='order', verbose_name="Уровень применения" ) is_active = models.BooleanField( default=True, verbose_name="Активна", db_index=True ) start_date = models.DateTimeField( null=True, blank=True, verbose_name="Дата начала действия" ) end_date = models.DateTimeField( null=True, blank=True, verbose_name="Дата окончания действия" ) max_usage_count = models.PositiveIntegerField( null=True, blank=True, verbose_name="Макс. количество использований", help_text="Оставьте пустым для безлимитного использования" ) current_usage_count = models.PositiveIntegerField( default=0, verbose_name="Текущее количество использований" ) priority = models.PositiveIntegerField( default=0, verbose_name="Приоритет", help_text="Более высокий приоритет применяется первым" ) created_at = models.DateTimeField( auto_now_add=True, verbose_name="Дата создания" ) created_by = models.ForeignKey( 'accounts.CustomUser', on_delete=models.SET_NULL, null=True, blank=True, related_name='created_discounts', verbose_name="Создал" ) class Meta: abstract = True ordering = ['-priority', '-created_at'] indexes = [ models.Index(fields=['is_active']), models.Index(fields=['scope']), models.Index(fields=['discount_type']), ] def __str__(self): if self.discount_type == 'percentage': return f"{self.name} ({self.value}%)" return f"{self.name} (-{self.value} руб.)" def clean(self): """Валидация значений скидки""" if self.discount_type == 'percentage': if self.value < 0 or self.value > 100: raise ValidationError({ 'value': 'Процентная скидка должна быть от 0 до 100' }) elif self.discount_type == 'fixed_amount': if self.value < 0: raise ValidationError({ 'value': 'Фиксированная скидка не может быть отрицательной' }) if self.start_date and self.end_date and self.start_date > self.end_date: raise ValidationError({ 'end_date': 'Дата окончания не может быть раньше даты начала' }) def calculate_discount_amount(self, base_amount): """ Вычислить сумму скидки для заданной базовой суммы. Args: base_amount: Десятичное число - базовая сумма Returns: Decimal: Сумма скидки (не может превышать base_amount) """ if self.discount_type == 'percentage': return base_amount * self.value / 100 else: # fixed_amount return min(self.value, base_amount) # Скидка не может превышать сумму def is_valid_now(self): """ Проверить, что скидка активна в текущий момент времени. Returns: bool: True если скидка активна """ from django.utils import timezone if not self.is_active: return False now = timezone.now() if self.start_date and now < self.start_date: return False if self.end_date and now > self.end_date: return False if self.max_usage_count and self.current_usage_count >= self.max_usage_count: return False return True