feat(discounts, orders): рефакторинг системы скидок - единый источник правды

- Добавлен combine_mode в форму создания/редактирования скидок
- Добавлена колонка "Объединение" в список скидок с иконками
- Добавлен фильтр по режиму объединения скидок
- Добавлена валидация: только одна exclusive скидка на заказ
- Удалены дублирующие поля из Order и OrderItem:
  - applied_discount, applied_promo_code, discount_amount
- Скидки теперь хранятся только в DiscountApplication
- Добавлены свойства для обратной совместимости

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-11 13:46:02 +03:00
parent cd758a0645
commit c070e42cab
9 changed files with 192 additions and 112 deletions

View File

@@ -34,8 +34,7 @@ class DiscountApplier:
from discounts.services.calculator import DiscountCalculator
# Удаляем предыдущую скидку на заказ
if order.applied_promo_code:
DiscountApplier._remove_order_discount_only(order)
DiscountApplier._remove_order_discount_only(order)
# Рассчитываем скидку
result = DiscountCalculator.calculate_order_discount(order, promo_code)
@@ -50,21 +49,7 @@ class DiscountApplier:
discounts_data = result['discounts']
total_amount = result['total_amount']
# Применяем первую скидку в applied_discount (для обратной совместимости с Order)
if discounts_data:
first_discount = discounts_data[0]['discount']
order.applied_discount = first_discount
order.applied_promo_code = promo.code
order.discount_amount = total_amount
order.save(update_fields=['applied_discount', 'applied_promo_code', 'discount_amount'])
# Пересчитываем total_amount
order.calculate_total()
# Регистрируем использование промокода
promo.record_usage(order.customer)
# Создаем записи о применении для каждой скидки
# Создаем записи о применении для каждой скидки в DiscountApplication
for disc_data in discounts_data:
discount = disc_data['discount']
amount = disc_data['amount']
@@ -85,6 +70,12 @@ class DiscountApplier:
discount.current_usage_count += 1
discount.save(update_fields=['current_usage_count'])
# Пересчитываем total_amount (использует DiscountApplication)
order.calculate_total()
# Регистрируем использование промокода
promo.record_usage(order.customer)
return {
'success': True,
'discounts': discounts_data,
@@ -124,12 +115,6 @@ class DiscountApplier:
if order_result['discounts'] and not order_result['error']:
total_order_amount = order_result['total_amount']
# Сохраняем первую скидку в applied_discount (для совместимости)
first_discount_data = order_result['discounts'][0]
order.applied_discount = first_discount_data['discount']
order.discount_amount = total_order_amount
order.save(update_fields=['applied_discount', 'discount_amount'])
# Создаем записи о применении для всех скидок
for disc_data in order_result['discounts']:
discount = disc_data['discount']
@@ -167,12 +152,6 @@ class DiscountApplier:
if item_result['discounts']:
total_item_amount = item_result['total_amount']
# Сохраняем первую скидку в applied_discount (для совместимости)
first_discount_data = item_result['discounts'][0]
item.applied_discount = first_discount_data['discount']
item.discount_amount = total_item_amount
item.save(update_fields=['applied_discount', 'discount_amount'])
# Создаем записи о применении для всех скидок
base_amount = item.price * item.quantity
for disc_data in item_result['discounts']:
@@ -186,7 +165,7 @@ class DiscountApplier:
target='order_item',
base_amount=base_amount,
discount_amount=amount,
final_amount=item.get_total_price(),
final_amount=base_amount - amount,
customer=order.customer,
applied_by=user
)
@@ -216,14 +195,6 @@ class DiscountApplier:
Args:
order: Order
"""
DiscountApplier._remove_order_discount_only(order)
# Удаляем скидки с позиций
order.items.update(
applied_discount=None,
discount_amount=Decimal('0')
)
# Удаляем записи о применении
from discounts.models import DiscountApplication
DiscountApplication.objects.filter(order=order).delete()
@@ -265,14 +236,6 @@ class DiscountApplier:
# Рассчитываем сумму
discount_amount = discount.calculate_discount_amount(Decimal(order.subtotal))
# Применяем к заказу
order.applied_discount = discount
order.discount_amount = discount_amount
order.save(update_fields=['applied_discount', 'discount_amount'])
# Пересчитываем total_amount
order.calculate_total()
# Создаем запись о применении
DiscountApplication.objects.create(
order=order,
@@ -289,6 +252,9 @@ class DiscountApplier:
discount.current_usage_count += 1
discount.save(update_fields=['current_usage_count'])
# Пересчитываем total_amount
order.calculate_total()
return {
'success': True,
'discount_amount': discount_amount
@@ -307,7 +273,5 @@ class DiscountApplier:
# Удаляем записи о применении скидок к заказу
DiscountApplication.objects.filter(order=order, target='order').delete()
order.applied_discount = None
order.applied_promo_code = None
order.discount_amount = Decimal('0')
order.save(update_fields=['applied_discount', 'applied_promo_code', 'discount_amount'])
# Пересчитываем (order.discount_amount теперь свойство, берущее из DiscountApplication)
order.calculate_total()