From d2b49cca560f53e0b09f622337d99da080f8522c Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Fri, 2 Jan 2026 14:46:02 +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=BE:=20=D0=B0=D0=B3=D1=80=D0=B5=D0=B3=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B7=D0=B5=D1=80=D0=B2=D0=BE?= =?UTF-8?q?=D0=B2=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D1=82=20quantity=5Fb?= =?UTF-8?q?ase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit КРИТИЧНО: Все агрегации Reservation.quantity заменены на quantity_base Проблемы и решения: 🔴 КРИТИЧНО - BatchManager.write_off_by_fifo(): - Проблема: суммировал quantity вместо quantity_base - Влияние: FIFO расчет свободного товара был некорректен - Решение: aggregate(Sum('quantity_base')) в строках 118, 125 🟡 СРЕДНЯЯ ВАЖНОСТЬ - ShowcaseManager: - reserve_showcase_item(): обновление quantity и quantity_base (строка 403) - release_showcase_reservation(): обновление обоих полей (строка 481) - Теперь витринные резервы полностью консистентны 🟡 СРЕДНЯЯ ВАЖНОСТЬ - TransformationService: - confirm(): проверка доступности через quantity_base (строка 254) - Корректная валидация при трансформации товаров 🟢 НИЗКАЯ ВАЖНОСТЬ - WriteOffDocumentService: - update_item(): синхронизация quantity и quantity_base (строка 175) - Полнота данных в резервах документов списания 🟢 НИЗКАЯ ВАЖНОСТЬ - Сигналы (signals.py): - update_order_item_reservation(): обновление обоих полей для товаров - Для обычных товаров: quantity_base = quantity_in_base_units (строка 1081) - Для комплектов: quantity_base = quantity (компоненты в базовых) (строка 1107) - Добавлено обновление sales_unit при изменении OrderItem Архитектура: - Принцип: quantity_base ВСЕГДА содержит количество в базовых единицах - Все агрегации резервов используют quantity_base для корректных расчетов - quantity сохраняется для совместимости и отображения - sales_unit хранит ссылку на единицу продажи для аудита --- myproject/inventory/services/batch_manager.py | 4 ++-- myproject/inventory/services/showcase_manager.py | 6 ++++-- .../inventory/services/transformation_service.py | 2 +- .../inventory/services/writeoff_document_service.py | 3 ++- myproject/inventory/signals.py | 13 ++++++++++--- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/myproject/inventory/services/batch_manager.py b/myproject/inventory/services/batch_manager.py index d44d877..e7ac228 100644 --- a/myproject/inventory/services/batch_manager.py +++ b/myproject/inventory/services/batch_manager.py @@ -115,14 +115,14 @@ class StockBatchManager: status='reserved', transformation_input__transformation=exclude_transformation ) - transformation_reserved_qty = transformation_reservations.aggregate(total=Sum('quantity'))['total'] or Decimal('0') + transformation_reserved_qty = transformation_reservations.aggregate(total=Sum('quantity_base'))['total'] or Decimal('0') # Исключаем резервы трансформации из общего расчета резервов reservation_filter = reservation_filter.exclude(transformation_input__transformation=exclude_transformation) if exclude_order: reservation_filter = reservation_filter.exclude(order_item__order=exclude_order) - total_reserved = reservation_filter.aggregate(total=Sum('quantity'))['total'] or Decimal('0') + total_reserved = reservation_filter.aggregate(total=Sum('quantity_base'))['total'] or Decimal('0') # Получаем партии по FIFO batches = StockBatchManager.get_batches_for_fifo(product, warehouse) diff --git a/myproject/inventory/services/showcase_manager.py b/myproject/inventory/services/showcase_manager.py index 96d0726..7de836d 100644 --- a/myproject/inventory/services/showcase_manager.py +++ b/myproject/inventory/services/showcase_manager.py @@ -401,7 +401,8 @@ class ShowcaseManager: ) if not created: reservation.quantity = (reservation.quantity or Decimal('0')) + quantity_per_item - reservation.save(update_fields=['quantity']) + reservation.quantity_base = (reservation.quantity_base or Decimal('0')) + quantity_per_item + reservation.save(update_fields=['quantity', 'quantity_base']) return { 'success': True, @@ -479,7 +480,8 @@ class ShowcaseManager: if new_qty > 0: res.quantity = new_qty - res.save(update_fields=['quantity']) + res.quantity_base = new_qty + res.save(update_fields=['quantity', 'quantity_base']) released_amount = quantity_per_item else: # Полностью освобождаем резерв diff --git a/myproject/inventory/services/transformation_service.py b/myproject/inventory/services/transformation_service.py index 7a4da46..d12a27f 100644 --- a/myproject/inventory/services/transformation_service.py +++ b/myproject/inventory/services/transformation_service.py @@ -251,7 +251,7 @@ class TransformationService: status='reserved' ).exclude( transformation_input__transformation=transformation - ).aggregate(total=models.Sum('quantity'))['total'] or Decimal('0') + ).aggregate(total=models.Sum('quantity_base'))['total'] or Decimal('0') available = stock.quantity_available - reserved_qty if trans_input.quantity > available: diff --git a/myproject/inventory/services/writeoff_document_service.py b/myproject/inventory/services/writeoff_document_service.py index 201fc6e..854738d 100644 --- a/myproject/inventory/services/writeoff_document_service.py +++ b/myproject/inventory/services/writeoff_document_service.py @@ -173,7 +173,8 @@ class WriteOffDocumentService: # Обновляем резерв if item.reservation: item.reservation.quantity = quantity - item.reservation.save(update_fields=['quantity']) + item.reservation.quantity_base = quantity + item.reservation.save(update_fields=['quantity', 'quantity_base']) item.quantity = quantity diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 8bf8a31..2cb3776 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -1078,11 +1078,16 @@ def update_reservation_on_item_change(sender, instance, created, **kwargs): # Обычный товар - один резерв, обновляем количество напрямую reservation = reservations.first() old_quantity = reservation.quantity + + # Обновляем quantity и quantity_base reservation.quantity = Decimal(str(instance.quantity)) - reservation.save(update_fields=['quantity']) + reservation.quantity_base = instance.quantity_in_base_units or Decimal(str(instance.quantity)) + reservation.sales_unit = instance.sales_unit + reservation.save(update_fields=['quantity', 'quantity_base', 'sales_unit']) logger.info( - f"✓ Резерв #{reservation.id} обновлён: quantity {old_quantity} → {reservation.quantity} " + f"✓ Резерв #{reservation.id} обновлён: quantity {old_quantity} → {reservation.quantity}, " + f"quantity_base → {reservation.quantity_base} " f"(статус: {reservation.status}, OrderItem #{instance.id}, заказ {instance.order.order_number})" ) @@ -1104,8 +1109,10 @@ def update_reservation_on_item_change(sender, instance, created, **kwargs): expected_qty = product_quantities.get(reservation.product_id, Decimal('0')) if expected_qty > 0: old_quantity = reservation.quantity + # Компоненты комплекта всегда в базовых единицах reservation.quantity = expected_qty - reservation.save(update_fields=['quantity']) + reservation.quantity_base = expected_qty + reservation.save(update_fields=['quantity', 'quantity_base']) logger.info( f"✓ Резерв #{reservation.id} ({reservation.product.name}) обновлён: "