Витринные комплекты остаются на витрине при отмене заказа

Проблема: при отмене заказа с витринным временным комплектом резервы освобождались
(status='released'), и букет исчезал с витрины. Это неправильное поведение для временных
комплектов на витрине - они должны оставаться доступными для продажи.

Решение:
- В сигнале rollback_sale_on_status_change добавлено разделение резервов на:
  * Обычные резервы - работают как раньше (released при отмене, reserved при возврате)
  * Витринные временные комплекты (is_temporary=True, showcase!=null) - ВСЕГДА возвращаются
    в статус reserved, независимо от типа отката заказа

- Для витринных комплектов сохраняются привязки к showcase и product_kit
- Букеты остаются видимыми на витрине и доступны для повторной продажи

Бизнес-логика:
- При ВОЗВРАТЕ (completed → draft/in_delivery): букет возвращается на витрину
- При ОТМЕНЕ (completed → cancelled): букет ТАКЖЕ возвращается на витрину
- Букет можно убрать только вручную через функцию разбора комплекта
This commit is contained in:
2025-12-08 17:58:40 +03:00
parent 3ef2a19537
commit 5d24b1cd6e

View File

@@ -247,13 +247,15 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs):
Процесс:
1. Отслеживаем переход ОТ статуса 'completed'
2. Удаляем Sale и восстанавливаем StockBatch через SaleBatchAllocation
3. Обновляем резервы (reserved или released в зависимости от сценария)
3. Обновляем резервы:
- Обычные резервы: reserved или released в зависимости от сценария
- Витринные временные комплекты: ВСЕГДА reserved (остаются на витрине)
4. Обновляем Stock
5. Устанавливаем is_returned для отмены
Сценарии:
- А (ошибка): completed → draft/in_delivery → резервы возвращаются в 'reserved'
- Б (отмена): completed → cancelled → резервы освобождаются в 'released'
- А (ошибка/возврат): completed → draft/in_delivery → обычные резервы в 'reserved'
- Б (отмена): completed → cancelled → обычные резервы в 'released'
- В (витринные комплекты): любой уход от completed → резервы в 'reserved' (букет остаётся на витрине)
ПРИМЕЧАНИЕ: Этот сигнал ОБРАБАТЫВАЕТ ТОЛЬКО переход ОТ 'completed'!
Для перехода к 'cancelled' из любого статуса см. release_reservations_on_cancellation
@@ -414,12 +416,26 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs):
status='converted_to_sale'
)
reservations_count = reservations.count()
# Разделяем резервы на витринные временные комплекты и обычные
# Витринные временные комплекты: is_temporary=True и showcase не null
showcase_kit_reservations = reservations.filter(
product_kit__is_temporary=True,
product_kit__showcase__isnull=False
)
if reservations_count > 0:
# Обновляем резервы через .save() чтобы сработал сигнал обновления Stock
# Сигнал update_stock_on_reservation_change автоматически обновит Stock
for reservation in reservations:
# Обычные резервы (все остальные)
normal_reservations = reservations.exclude(
id__in=showcase_kit_reservations.values_list('id', flat=True)
)
showcase_count = showcase_kit_reservations.count()
normal_count = normal_reservations.count()
total_count = showcase_count + normal_count
if total_count > 0:
# Обновляем обычные резервы согласно сценарию (released при отмене, reserved при возврате)
if normal_count > 0:
for reservation in normal_reservations:
reservation.status = reservation_target_status
if reservation_target_status == 'released':
reservation.released_at = timezone.now()
@@ -432,9 +448,22 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs):
reservation.save(update_fields=update_fields)
logger.info(
f"✓ Обновлено {reservations_count} резервов: "
f"✓ Обновлено {normal_count} обычных резервов: "
f"converted_to_sale → {reservation_target_status}"
)
# Витринные временные комплекты ВСЕГДА возвращаются в reserved (остаются на витрине)
if showcase_count > 0:
for reservation in showcase_kit_reservations:
reservation.status = 'reserved'
# Не трогаем showcase и product_kit - они остаются привязанными
# converted_at оставляем (для истории)
reservation.save(update_fields=['status'])
logger.info(
f"✓ Обновлено {showcase_count} резервов витринных комплектов: "
f"converted_to_sale → reserved (возвращены на витрину)"
)
else:
logger.warning(
f"⚠ Для заказа {instance.order_number} нет резервов в статусе 'converted_to_sale'"