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