Добавлена защита от удаления дефолтных Склада и Витрины
Проблема: при создании тенанта автоматически создаются дефолтные Склад и Витрина. Если пользователь удалит их, система может сломаться: POS, создание заказов и резервирование перестанут работать. Решение: реализована строгая валидация + мягкое удаление для витрин. Изменения в inventory/views/warehouse.py: - Добавлена валидация перед деактивацией склада: * Блокировка деактивации последнего активного склада * Проверка ненулевых остатков товаров * Проверка активных резервов * Предупреждение при деактивации дефолтного склада Изменения в inventory/views/showcase.py: - ShowcaseListView: по умолчанию показывает только активные витрины - ShowcaseDeleteView: изменена логика с жесткого на мягкое удаление - Добавлена валидация перед деактивацией витрины: * Блокировка деактивации последней активной витрины склада * Проверка активных резервов * Проверка физических экземпляров комплектов (ShowcaseItem) * Предупреждение при деактивации дефолтной витрины Изменения в inventory/forms_showcase.py: - Проверка уникальности названия витрины учитывает только активные Изменения в inventory/admin.py: - ShowcaseAdmin: добавлены методы delete_model() и delete_queryset() для блокировки удаления последней витрины через админку - WarehouseAdmin: добавлены методы delete_model() и delete_queryset() для блокировки удаления последнего склада через админку Преимущества: ✅ Система не сломается - всегда есть хотя бы один активный склад/витрина ✅ Данные в безопасности - мягкое удаление для обеих сущностей ✅ Понятные сообщения об ошибках для пользователя ✅ Защита работает как в UI, так и в Django Admin 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,16 +66,70 @@ class WarehouseDeleteView(LoginRequiredMixin, DeleteView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Переопределяем POST метод чтобы использовать мягкое удаление
|
||||
вместо стандартного физического удаления Django
|
||||
вместо стандартного физического удаления Django.
|
||||
Включает строгую валидацию для защиты от случайного удаления.
|
||||
"""
|
||||
self.object = self.get_object()
|
||||
warehouse_name = self.object.name
|
||||
|
||||
# Мягкое удаление - просто деактивируем склад
|
||||
# 1. Проверка: это последний активный склад?
|
||||
active_warehouses_count = Warehouse.objects.filter(is_active=True).count()
|
||||
if active_warehouses_count <= 1:
|
||||
messages.error(
|
||||
request,
|
||||
'Невозможно деактивировать последний активный склад. '
|
||||
'Система требует наличия хотя бы одного активного склада для работы. '
|
||||
'Создайте новый склад перед деактивацией этого.'
|
||||
)
|
||||
return HttpResponseRedirect(reverse_lazy('inventory:warehouse-list'))
|
||||
|
||||
# 2. Проверка: есть ли ненулевые остатки товаров?
|
||||
from inventory.models import Stock
|
||||
has_stock = Stock.objects.filter(
|
||||
warehouse=self.object,
|
||||
available__gt=0
|
||||
).exists()
|
||||
if has_stock:
|
||||
messages.error(
|
||||
request,
|
||||
f'Невозможно деактивировать склад "{warehouse_name}": '
|
||||
'на складе есть товары с ненулевыми остатками. '
|
||||
'Переместите все товары на другой склад перед деактивацией.'
|
||||
)
|
||||
return HttpResponseRedirect(reverse_lazy('inventory:warehouse-list'))
|
||||
|
||||
# 3. Проверка: есть ли активные резервы?
|
||||
from inventory.models import Reservation
|
||||
active_reservations_count = Reservation.objects.filter(
|
||||
warehouse=self.object,
|
||||
status='reserved'
|
||||
).count()
|
||||
if active_reservations_count > 0:
|
||||
messages.error(
|
||||
request,
|
||||
f'Невозможно деактивировать склад "{warehouse_name}": '
|
||||
f'на складе есть {active_reservations_count} активных резервирований. '
|
||||
'Завершите все заказы с резервами перед деактивацией склада.'
|
||||
)
|
||||
return HttpResponseRedirect(reverse_lazy('inventory:warehouse-list'))
|
||||
|
||||
# 4. Предупреждение: если это дефолтный склад
|
||||
if self.object.is_default:
|
||||
messages.warning(
|
||||
request,
|
||||
f'Внимание: вы деактивируете склад по умолчанию "{warehouse_name}". '
|
||||
'Рекомендуется сначала назначить другой склад по умолчанию.'
|
||||
)
|
||||
|
||||
# Мягкое удаление - деактивируем склад
|
||||
self.object.is_active = False
|
||||
self.object.save()
|
||||
|
||||
messages.success(request, f'Склад "{warehouse_name}" архивирован и скрыт из списка.')
|
||||
messages.success(
|
||||
request,
|
||||
f'Склад "{warehouse_name}" успешно архивирован и скрыт из списка. '
|
||||
'Все связанные данные сохранены.'
|
||||
)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user