Исправлено двойное списание товаров при смене статуса заказа

Проблема:
- При изменении статуса заказа на 'Выполнен' товар списывался дважды
- Заказ на 10 шт создавал Sale на 10 шт, но со склада уходило 20 шт

Найдено ДВЕ причины:

1. Повторное обновление резервов через .save() (inventory/signals.py)
   - Резервы обновлялись через res.save() каждый раз при сохранении заказа
   - Это вызывало сигнал update_stock_on_reservation_change
   - При повторном сохранении заказа происходило двойное срабатывание

   Решение:
   - Проверка дубликатов ПЕРЕД обновлением резервов
   - Замена .save() на .update() для массового обновления без вызова сигналов
   - Ручное обновление Stock после .update()

2. Двойное FIFO-списание (inventory/services/sale_processor.py)
   - Sale создавалась с processed=False
   - Сигнал process_sale_fifo срабатывал и списывал товар (1-й раз)
   - Затем SaleProcessor.create_sale() тоже списывал товар (2-й раз)

   Решение:
   - Sale создаётся сразу с processed=True
   - Сигнал не срабатывает, списание только в сервисе

Дополнительно:
- Ограничен выбор статусов при создании заказа только промежуточными
- Статус 'Черновик' установлен по умолчанию
- Убран пустой выбор '-------' из поля статуса

Изменённые файлы:
- myproject/orders/forms.py - настройки статусов для формы заказа
- myproject/inventory/signals.py - исправление сигнала create_sale_on_order_completion
- myproject/inventory/services/sale_processor.py - исправление create_sale
- myproject/test_order_status_default.py - обновлён тест
- DOUBLE_SALE_FIX.md - документация по исправлению
This commit is contained in:
2025-12-01 00:56:26 +03:00
parent 4e66f03957
commit e0437cdb5a
8 changed files with 1284 additions and 70 deletions

View File

@@ -115,6 +115,32 @@ class OrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Ограничиваем выбор статусов при создании заказа только промежуточными
# (исключаем финальные положительные и отрицательные статусы)
if not self.instance.pk:
from .models import OrderStatus
# Только промежуточные статусы (не финальные)
intermediate_statuses = OrderStatus.objects.filter(
is_positive_end=False,
is_negative_end=False
).order_by('order', 'name')
self.fields['status'].queryset = intermediate_statuses
# Устанавливаем статус "Черновик" по умолчанию
try:
draft_status = OrderStatus.objects.get(code='draft', is_system=True)
self.fields['status'].initial = draft_status.pk
except OrderStatus.DoesNotExist:
pass
else:
# При редактировании заказа доступны все статусы
from .models import OrderStatus
self.fields['status'].queryset = OrderStatus.objects.all().order_by('order', 'name')
# Делаем поле status обязательным и убираем пустой выбор "-------"
self.fields['status'].required = True
self.fields['status'].empty_label = None
# Добавляем Bootstrap классы ко всем полям
for field_name, field in self.fields.items():
if isinstance(field.widget, forms.CheckboxInput):