Проблема: - При изменении статуса заказа на 'Выполнен' товар списывался дважды - Заказ на 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 - документация по исправлению
126 lines
5.9 KiB
Python
126 lines
5.9 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Тест для проверки настройки статусов при создании и редактировании заказа.
|
||
"""
|
||
import os
|
||
import django
|
||
|
||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||
django.setup()
|
||
|
||
from orders.models import OrderStatus, Order
|
||
from orders.forms import OrderForm
|
||
|
||
|
||
def test_default_status():
|
||
"""Проверяет настройки статусов для новых и существующих заказов"""
|
||
|
||
print("\n=== ТЕСТ: Настройки статусов заказов ===\n")
|
||
|
||
# Проверяем наличие статуса "Черновик"
|
||
try:
|
||
draft_status = OrderStatus.objects.get(code='draft', is_system=True)
|
||
print(f"✓ Статус 'Черновик' найден: {draft_status.name} (ID: {draft_status.pk})")
|
||
except OrderStatus.DoesNotExist:
|
||
print("✗ ОШИБКА: Статус 'draft' не найден в БД!")
|
||
print(" Создайте системные статусы командой:")
|
||
print(" python manage.py shell -c \"from orders.services.order_status_service import OrderStatusService; OrderStatusService.create_default_statuses()\"")
|
||
return False
|
||
|
||
# Проверяем финальные статусы
|
||
final_positive = OrderStatus.objects.filter(is_positive_end=True)
|
||
final_negative = OrderStatus.objects.filter(is_negative_end=True)
|
||
intermediate = OrderStatus.objects.filter(is_positive_end=False, is_negative_end=False)
|
||
|
||
print(f"\n📊 Статистика статусов:")
|
||
print(f" - Промежуточные (доступны при создании): {intermediate.count()}")
|
||
print(f" - Положительные финальные: {final_positive.count()}")
|
||
print(f" - Отрицательные финальные: {final_negative.count()}")
|
||
|
||
print(f"\n📋 Промежуточные статусы:")
|
||
for status in intermediate.order_by('order'):
|
||
print(f" - {status.name} ({status.code})")
|
||
|
||
print(f"\n🚫 Финальные статусы (недоступны при создании):")
|
||
for status in final_positive:
|
||
print(f" - {status.name} ({status.code}) [положительный]")
|
||
for status in final_negative:
|
||
print(f" - {status.name} ({status.code}) [отрицательный]")
|
||
|
||
# === ТЕСТ 1: Создание нового заказа ===
|
||
print("\n=== ТЕСТ 1: Создание нового заказа ===\n")
|
||
|
||
form_new = OrderForm()
|
||
status_field = form_new.fields['status']
|
||
|
||
# Проверяем начальное значение
|
||
initial_value = status_field.initial
|
||
print(f"✓ Начальное значение поля 'status': {initial_value}")
|
||
print(f"✓ Ожидаемое значение (ID статуса 'Черновик'): {draft_status.pk}")
|
||
|
||
if initial_value == draft_status.pk:
|
||
print("✓ УСПЕХ: Статус 'Черновик' установлен по умолчанию!")
|
||
else:
|
||
print(f"✗ ОШИБКА: Начальное значение {initial_value} != {draft_status.pk}")
|
||
return False
|
||
|
||
# Проверяем queryset (только промежуточные статусы)
|
||
available_statuses = list(status_field.queryset)
|
||
print(f"\n✓ Доступно статусов при создании: {len(available_statuses)}")
|
||
|
||
has_final_positive = any(s.is_positive_end for s in available_statuses)
|
||
has_final_negative = any(s.is_negative_end for s in available_statuses)
|
||
|
||
if not has_final_positive and not has_final_negative:
|
||
print("✓ УСПЕХ: Финальные статусы исключены из выбора!")
|
||
else:
|
||
print("✗ ОШИБКА: В списке есть финальные статусы!")
|
||
return False
|
||
|
||
# Проверяем, что поле обязательно
|
||
if status_field.required:
|
||
print("✓ Поле 'status' обязательно (required=True)")
|
||
else:
|
||
print("✗ ОШИБКА: Поле 'status' не обязательно!")
|
||
return False
|
||
|
||
# Проверяем, что пустой выбор убран
|
||
if status_field.empty_label is None:
|
||
print("✓ Пустой выбор '-------' убран (empty_label=None)")
|
||
else:
|
||
print(f"✗ ОШИБКА: Пустой выбор не убран! empty_label={status_field.empty_label}")
|
||
return False
|
||
|
||
# === ТЕСТ 2: Редактирование существующего заказа ===
|
||
print("\n=== ТЕСТ 2: Редактирование существующего заказа ===\n")
|
||
|
||
# Создаем мок-объект заказа с pk (чтобы форма считала его существующим)
|
||
class MockOrder:
|
||
pk = 999
|
||
status = draft_status
|
||
customer = None
|
||
delivery_address = None
|
||
|
||
form_edit = OrderForm(instance=MockOrder())
|
||
status_field_edit = form_edit.fields['status']
|
||
|
||
# Проверяем queryset (все статусы)
|
||
all_statuses = list(status_field_edit.queryset)
|
||
print(f"✓ Доступно статусов при редактировании: {len(all_statuses)}")
|
||
|
||
has_final_in_edit = any(s.is_positive_end or s.is_negative_end for s in all_statuses)
|
||
|
||
if has_final_in_edit:
|
||
print("✓ УСПЕХ: При редактировании доступны ВСЕ статусы (включая финальные)!")
|
||
else:
|
||
print("⚠ ВНИМАНИЕ: При редактировании финальные статусы отсутствуют")
|
||
|
||
print("\n=== ВСЕ ТЕСТЫ ПРОЙДЕНЫ! ===\n")
|
||
return True
|
||
|
||
|
||
if __name__ == '__main__':
|
||
success = test_default_status()
|
||
exit(0 if success else 1)
|