diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 3d9095e..3c4f907 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -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 + ) + + # Обычные резервы (все остальные) + 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 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 оставляем (для истории) + 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) - # Используем save() с указанием измененных полей - update_fields = ['status'] - if reservation_target_status == 'released': - update_fields.append('released_at') - reservation.save(update_fields=update_fields) - - logger.info( - f"✓ Обновлено {reservations_count} резервов: " - f"converted_to_sale → {reservation_target_status}" - ) + 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'"