Files
octopus/myproject/inventory/forms_showcase.py
Andrey Smakotin 0f09702094 Добавлена защита от удаления дефолтных Склада и Витрины
Проблема: при создании тенанта автоматически создаются дефолтные
Склад и Витрина. Если пользователь удалит их, система может сломаться:
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>
2026-01-03 19:52:01 +03:00

77 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
from django import forms
from django.core.exceptions import ValidationError
from .models import Showcase, Warehouse
class ShowcaseForm(forms.ModelForm):
"""
Форма для создания и редактирования витрин.
Витрина привязывается к складу и используется для выкладки готовых букетов.
"""
class Meta:
model = Showcase
fields = ['name', 'warehouse', 'description', 'is_active', 'is_default']
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Название витрины (например: Витрина №1, Витрина у входа)'
}),
'warehouse': forms.Select(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Описание витрины, её расположение или особенности'
}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'is_default': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
labels = {
'name': 'Название витрины',
'warehouse': 'Склад',
'description': 'Описание',
'is_active': 'Активна',
'is_default': 'Витрина по умолчанию',
}
help_texts = {
'warehouse': 'Склад, к которому привязана витрина',
'is_active': 'Неактивные витрины скрыты из списка выбора',
'is_default': 'Витрина будет автоматически выбрана при создании резерва на этом складе',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Фильтруем только активные склады
self.fields['warehouse'].queryset = Warehouse.objects.filter(is_active=True).order_by('name')
# Если создаём новую витрину и есть склад по умолчанию - предвыбираем его
if not self.instance.pk and not self.initial.get('warehouse'):
default_warehouse = Warehouse.objects.filter(
is_active=True,
is_default=True
).first()
if default_warehouse:
self.initial['warehouse'] = default_warehouse.id
def clean_name(self):
"""Проверка уникальности названия витрины в рамках склада (среди активных)"""
name = self.cleaned_data.get('name')
warehouse = self.cleaned_data.get('warehouse')
if name and warehouse:
# Проверяем уникальность названия в рамках склада (только среди активных)
queryset = Showcase.objects.filter(name=name, warehouse=warehouse, is_active=True)
# При редактировании исключаем текущий экземпляр
if self.instance and self.instance.pk:
queryset = queryset.exclude(pk=self.instance.pk)
if queryset.exists():
raise ValidationError(
f'Активная витрина с названием "{name}" уже существует на складе "{warehouse.name}". '
'Пожалуйста, выберите другое название.'
)
return name