Исправлена критическая проблема с резервами при смене статуса заказа

Проблема:
- При смене статуса заказа на 'Выполнен' товар списывался со склада
- Резервы обновлялись на статус 'converted_to_sale'
- НО Stock.quantity_reserved не обновлялся автоматически
- В результате резервы продолжали 'держать' товар, хотя он уже продан

Решение:
1. Изменен сигнал create_sale_on_order_completion:
   - Используется .save(update_fields=[...]) вместо .update()
   - Это вызывает сигнал update_stock_on_reservation_change
   - Убран костыль с ручным вызовом refresh_from_batches()

2. Оптимизирован сигнал update_stock_on_reservation_change:
   - Stock обновляется ТОЛЬКО при изменении status или quantity
   - При изменении других полей (даты и т.д.) Stock НЕ пересчитывается
   - Предотвращены лишние пересчёты и улучшена производительность

3. Добавлены диагностические инструменты:
   - check_stock_103.py - для диагностики проблем с Stock
   - fix_stock_after_sale.py - команда для исправления старых заказов
   - diagnose_reservation_issue.py - универсальная диагностика

Результат:
- Элегантное решение без дублирования логики
- Stock автоматически обновляется при изменении резервов
- Работает везде, не только в заказах
- Оптимизировано для производительности
This commit is contained in:
2025-12-01 02:34:54 +03:00
parent 490e5d5401
commit e4cb175db2
5 changed files with 512 additions and 34 deletions

View File

@@ -132,6 +132,8 @@ class TenantCreationIntegrationTest(TransactionTestCase):
def test_new_tenant_gets_order_statuses(self):
"""
Тест: Новый тенант получает системные статусы заказов.
КРИТИЧЕСКИ ВАЖНО: Должен быть минимум ОДИН позитивный и ОДИН негативный финальный статус.
"""
# Создаём и активируем тенант
registration = TenantRegistration.objects.create(
@@ -165,9 +167,48 @@ class TenantCreationIntegrationTest(TransactionTestCase):
self.assertIsNotNone(draft_status, "Статус 'draft' не создан")
self.assertTrue(draft_status.is_system)
# КРИТИЧЕСКАЯ ПРОВЕРКА #1: Позитивный финальный статус
positive_statuses = OrderStatus.objects.filter(is_positive_end=True)
self.assertGreaterEqual(
positive_statuses.count(),
1,
"Должен быть минимум ОДИН позитивный финальный статус (is_positive_end=True)"
)
# Проверяем что есть 'completed' (основной позитивный статус)
completed_status = OrderStatus.objects.filter(code='completed').first()
self.assertIsNotNone(completed_status, "Статус 'completed' не создан")
self.assertTrue(completed_status.is_system)
self.assertIsNotNone(
completed_status,
"Статус 'completed' не создан (основной позитивный статус)"
)
self.assertTrue(
completed_status.is_system,
"'completed' должен быть системным"
)
self.assertTrue(
completed_status.is_positive_end,
"'completed' должен быть позитивным финальным статусом"
)
# КРИТИЧЕСКАЯ ПРОВЕРКА #2: Негативный финальный статус
negative_statuses = OrderStatus.objects.filter(is_negative_end=True)
self.assertGreaterEqual(
negative_statuses.count(),
1,
"Должен быть минимум ОДИН негативный финальный статус (is_negative_end=True)"
)
# Проверяем что есть 'cancelled' или другой негативный статус
# Проверяем первый найденный негативный статус
first_negative_status = negative_statuses.first()
self.assertTrue(
first_negative_status.is_system,
f"Негативный статус '{first_negative_status.code}' должен быть системным"
)
self.assertTrue(
first_negative_status.is_negative_end,
f"Статус '{first_negative_status.code}' должен быть негативным финальным"
)
def test_new_tenant_gets_system_customer(self):
"""