Витринные комплекты остаются на витрине при отмене заказа
Проблема: при отмене заказа с витринным временным комплектом резервы освобождались
(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:
@@ -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,27 +416,54 @@ 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:
|
||||
reservation.status = reservation_target_status
|
||||
if reservation_target_status == 'released':
|
||||
reservation.released_at = timezone.now()
|
||||
# converted_at оставляем (для истории)
|
||||
# Обычные резервы (все остальные)
|
||||
normal_reservations = reservations.exclude(
|
||||
id__in=showcase_kit_reservations.values_list('id', flat=True)
|
||||
)
|
||||
|
||||
# Используем save() с указанием измененных полей
|
||||
update_fields = ['status']
|
||||
if reservation_target_status == 'released':
|
||||
update_fields.append('released_at')
|
||||
reservation.save(update_fields=update_fields)
|
||||
showcase_count = showcase_kit_reservations.count()
|
||||
normal_count = normal_reservations.count()
|
||||
total_count = showcase_count + normal_count
|
||||
|
||||
logger.info(
|
||||
f"✓ Обновлено {reservations_count} резервов: "
|
||||
f"converted_to_sale → {reservation_target_status}"
|
||||
)
|
||||
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()
|
||||
# converted_at оставляем (для истории)
|
||||
|
||||
# Используем save() с указанием измененных полей
|
||||
update_fields = ['status']
|
||||
if reservation_target_status == 'released':
|
||||
update_fields.append('released_at')
|
||||
reservation.save(update_fields=update_fields)
|
||||
|
||||
logger.info(
|
||||
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'"
|
||||
|
||||
Reference in New Issue
Block a user