From c070e42cabb2520021dd932ebffdfc801dd38590 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Sun, 11 Jan 2026 13:46:02 +0300 Subject: [PATCH] =?UTF-8?q?feat(discounts,=20orders):=20=D1=80=D0=B5=D1=84?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=81=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=B5=D0=BC=D1=8B=20=D1=81=D0=BA=D0=B8=D0=B4=D0=BE?= =?UTF-8?q?=D0=BA=20-=20=D0=B5=D0=B4=D0=B8=D0=BD=D1=8B=D0=B9=20=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=20=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен combine_mode в форму создания/редактирования скидок - Добавлена колонка "Объединение" в список скидок с иконками - Добавлен фильтр по режиму объединения скидок - Добавлена валидация: только одна exclusive скидка на заказ - Удалены дублирующие поля из Order и OrderItem: - applied_discount, applied_promo_code, discount_amount - Скидки теперь хранятся только в DiscountApplication - Добавлены свойства для обратной совместимости Co-Authored-By: Claude Opus 4.5 --- myproject/discounts/services/applier.py | 64 ++++-------------- .../templates/discounts/discount_form.html | 16 +++++ .../templates/discounts/discount_list.html | 27 ++++++-- myproject/discounts/views.py | 38 ++++++++++- myproject/orders/admin.py | 12 +--- .../0004_remove_old_discount_fields.py | 33 ++++++++++ myproject/orders/models/order.py | 66 ++++++++++++------- myproject/orders/models/order_item.py | 40 ++++++----- myproject/pos/views.py | 8 +-- 9 files changed, 192 insertions(+), 112 deletions(-) create mode 100644 myproject/orders/migrations/0004_remove_old_discount_fields.py diff --git a/myproject/discounts/services/applier.py b/myproject/discounts/services/applier.py index f4cc175..7e3fa91 100644 --- a/myproject/discounts/services/applier.py +++ b/myproject/discounts/services/applier.py @@ -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() diff --git a/myproject/discounts/templates/discounts/discount_form.html b/myproject/discounts/templates/discounts/discount_form.html index 7ea29db..01a0cc7 100644 --- a/myproject/discounts/templates/discounts/discount_form.html +++ b/myproject/discounts/templates/discounts/discount_form.html @@ -101,6 +101,22 @@ +
+ + +
Как эта скидка взаимодействует с другими активными скидками
+
+
Ограничения
diff --git a/myproject/discounts/templates/discounts/discount_list.html b/myproject/discounts/templates/discounts/discount_list.html index 7a2732a..f8f0bf6 100644 --- a/myproject/discounts/templates/discounts/discount_list.html +++ b/myproject/discounts/templates/discounts/discount_list.html @@ -60,6 +60,15 @@
+
+ + +