From 0d72c36739c3ad139df48b9d2ac755c83a7e1211 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Thu, 11 Dec 2025 22:23:41 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B4=D0=B0=D0=B6=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B5=D1=81=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D0=B8=D1=85?= =?UTF-8?q?=20=D1=8D=D0=BA=D0=B7=D0=B5=D0=BC=D0=BF=D0=BB=D1=8F=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=B1=D1=83=D0=BA=D0=B5=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проблема: При попытке продажи 2+ экземпляров одного витринного букета возникала ошибка IntegrityError, так как поле sold_order_item было OneToOneField. Это означало что к одному OrderItem мог быть привязан только один ShowcaseItem, что делало невозможной продажу нескольких экземпляров в одной позиции заказа. Решение: 1. Изменен тип поля sold_order_item с OneToOneField на ForeignKey - Теперь несколько ShowcaseItem могут относиться к одному OrderItem - related_name изменен с 'sold_showcase_item' на 'sold_showcase_items' 2. Обновлен метод mark_sold в модели ShowcaseItem - Добавлена явная проверка статуса 'sold' перед продажей - Генерируется ValidationError если экземпляр уже продан - Удален комментарий про OneToOneField защиту 3. Обновлена обработка ошибок в ShowcaseManager.sell_showcase_items - Убрана обработка IntegrityError - Добавлена обработка ValidationError от mark_sold 4. Создана миграция 0012_change_sold_order_item_to_fk Теперь можно успешно продавать 2 и более экземпляров одного витринного букета в рамках одной позиции заказа. --- .../0012_change_sold_order_item_to_fk.py | 20 +++++++++++++++++++ myproject/inventory/models.py | 14 ++++++++----- .../inventory/services/showcase_manager.py | 16 +++++++-------- 3 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 myproject/inventory/migrations/0012_change_sold_order_item_to_fk.py diff --git a/myproject/inventory/migrations/0012_change_sold_order_item_to_fk.py b/myproject/inventory/migrations/0012_change_sold_order_item_to_fk.py new file mode 100644 index 0000000..889e541 --- /dev/null +++ b/myproject/inventory/migrations/0012_change_sold_order_item_to_fk.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.10 on 2025-12-11 19:23 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0011_add_writeoff_status_to_reservation'), + ('orders', '0006_transaction_delete_payment_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='showcaseitem', + name='sold_order_item', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sold_showcase_items', to='orders.orderitem', verbose_name='Позиция заказа (продажа)'), + ), + ] diff --git a/myproject/inventory/models.py b/myproject/inventory/models.py index 00fcbdb..7ce7ffe 100644 --- a/myproject/inventory/models.py +++ b/myproject/inventory/models.py @@ -542,13 +542,14 @@ class ShowcaseItem(models.Model): ) # === ЗАЩИТА ОТ ДВОЙНОЙ ПРОДАЖИ === - # OneToOneField гарантирует на уровне БД: 1 ShowcaseItem = max 1 OrderItem - sold_order_item = models.OneToOneField( + # ForeignKey позволяет привязать несколько ShowcaseItem к одному OrderItem + # (например, при продаже 2+ экземпляров одного букета) + sold_order_item = models.ForeignKey( 'orders.OrderItem', on_delete=models.SET_NULL, null=True, blank=True, - related_name='sold_showcase_item', + related_name='sold_showcase_items', verbose_name="Позиция заказа (продажа)" ) sold_at = models.DateTimeField( @@ -616,10 +617,13 @@ class ShowcaseItem(models.Model): def mark_sold(self, order_item): """ Пометить как проданный. - OneToOneField автоматически выбросит IntegrityError при повторной продаже. + Проверяет статус перед продажей чтобы избежать дублей. """ + if self.status == 'sold': + raise ValidationError(f'Экземпляр {self} уже продан') + self.status = 'sold' - self.sold_order_item = order_item # БД защита от дублей! + self.sold_order_item = order_item self.sold_at = timezone.now() self.locked_by_user = None self.cart_lock_expires_at = None diff --git a/myproject/inventory/services/showcase_manager.py b/myproject/inventory/services/showcase_manager.py index 384ba3d..bca2251 100644 --- a/myproject/inventory/services/showcase_manager.py +++ b/myproject/inventory/services/showcase_manager.py @@ -199,15 +199,13 @@ class ShowcaseManager: 'message': f'Продано {sold_count} экз.' } - except IntegrityError as e: - # Защита от двойной продажи сработала на уровне БД - if 'sold_order_item' in str(e) or 'UNIQUE' in str(e): - return { - 'success': False, - 'sold_count': 0, - 'message': 'Один из экземпляров уже был продан. Обновите список витринных букетов.' - } - raise + except ValidationError as e: + # Ошибка валидации (например, экземпляр уже продан) + return { + 'success': False, + 'sold_count': 0, + 'message': str(e) + } @staticmethod def sell_from_showcase(product_kit, showcase, customer, payment_method='cash_to_courier',