Создано новое Django приложение для управления скидками: Модели: - BaseDiscount: абстрактный базовый класс с общими полями - Discount: основная модель скидки (процент/фикс, на заказ/товар/категорию) - PromoCode: промокоды для активации скидок - DiscountApplication: история применения скидок Сервисы: - DiscountCalculator: расчёт скидок для корзины и заказов - DiscountApplier: применение скидок к заказам (атомарно) - DiscountValidator: валидация промокодов и условий Админ-панель: - DiscountAdmin: управление скидками - PromoCodeAdmin: управление промокодами - DiscountApplicationAdmin: история применения (только чтение) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
from decimal import Decimal
|
||
from django.core.exceptions import ValidationError
|
||
|
||
|
||
class DiscountValidator:
|
||
"""
|
||
Сервис для валидации скидок и промокодов.
|
||
"""
|
||
|
||
@staticmethod
|
||
def validate_promo_code(code, customer=None, order_subtotal=None):
|
||
"""
|
||
Валидировать промокод.
|
||
|
||
Args:
|
||
code: Код промокода
|
||
customer: Customer для проверки использований
|
||
order_subtotal: Сумма заказа для проверки min_order_amount
|
||
|
||
Returns:
|
||
tuple: (is_valid, promo_code_or_none, error_message)
|
||
"""
|
||
from discounts.models import PromoCode
|
||
|
||
if not code or not code.strip():
|
||
return False, None, "Промокод не указан"
|
||
|
||
try:
|
||
promo = PromoCode.objects.get(
|
||
code__iexact=code.strip().upper(),
|
||
is_active=True
|
||
)
|
||
except PromoCode.DoesNotExist:
|
||
return False, None, "Промокод не найден"
|
||
|
||
# Проверяем валидность промокода
|
||
is_valid, error = promo.is_valid(customer)
|
||
if not is_valid:
|
||
return False, None, error
|
||
|
||
# Проверяем мин. сумму заказа
|
||
if order_subtotal is not None and promo.discount.min_order_amount:
|
||
if Decimal(order_subtotal) < promo.discount.min_order_amount:
|
||
return False, None, f"Минимальная сумма заказа: {promo.discount.min_order_amount} руб."
|
||
|
||
# Проверяем scope (только заказ, не товары)
|
||
if promo.discount.scope not in ('order', 'product', 'category'):
|
||
return False, None, "Этот тип промокода не поддерживается"
|
||
|
||
return True, promo, None
|
||
|
||
@staticmethod
|
||
def validate_discount_for_order(discount, order):
|
||
"""
|
||
Проверить, можно ли применить скидку к заказу.
|
||
|
||
Args:
|
||
discount: Discount
|
||
order: Order
|
||
|
||
Returns:
|
||
tuple: (is_valid, error_message)
|
||
"""
|
||
if not discount.is_active:
|
||
return False, "Скидка неактивна"
|
||
|
||
# Проверяем даты
|
||
if not discount.is_valid_now():
|
||
return False, "Скидка недействительна"
|
||
|
||
# Проверяем мин. сумму заказа
|
||
if discount.scope == 'order' and discount.min_order_amount:
|
||
if order.subtotal < discount.min_order_amount:
|
||
return False, f"Минимальная сумма заказа: {discount.min_order_amount} руб."
|
||
|
||
return True, None
|
||
|
||
@staticmethod
|
||
def validate_auto_discount_for_cart(discount, cart_subtotal, customer=None):
|
||
"""
|
||
Проверить, можно ли применить автоматическую скидку к корзине.
|
||
|
||
Args:
|
||
discount: Discount
|
||
cart_subtotal: Десятичное число - сумма корзины
|
||
customer: Customer (опционально)
|
||
|
||
Returns:
|
||
bool: True если скидка применима
|
||
"""
|
||
if not discount.is_auto:
|
||
return False
|
||
|
||
if not discount.is_valid_now():
|
||
return False
|
||
|
||
if discount.scope == 'order' and discount.min_order_amount:
|
||
if cart_subtotal < discount.min_order_amount:
|
||
return False
|
||
|
||
return True
|