feat: Замена is_active на status для архивирования товаров

Реализована трёхуровневая система статусов товаров и комплектов:
- active (Активный) - товар доступен для продажи
- archived (Архивный) - скрыт, можно восстановить в следующем сезоне
- discontinued (Снят) - морально устарел, готов к удалению

Изменения:
1. Модели (BaseProductEntity, Product, ProductKit):
   - Заменено поле is_deleted (Boolean) на status (CharField)
   - Добавлены архивные метаданные (archived_at, archived_by)
   - Обновлены методы: archive(), restore(), discontinue(), delete()
   - Уникальное ограничение изменено на conditional (status='active')

2. Менеджеры (ActiveManager, SoftDeleteQuerySet):
   - Полиморфная поддержка обеих систем (status и is_active)
   - Использует hasattr() для совместимости с наследниками
   - Методы: archive(), restore(), discontinue(), archived_only(), active_only()

3. Формы (ProductForm, ProductKitForm):
   - Включены поле status в формы
   - Валидация уникальности по status='active'
   - CSS классы для статус-селектора

4. Admin панель:
   - DeletedFilter переименован в StatusFilter с тремя опциями
   - get_status_display() с цветным отображением статуса
   - Actions: restore_items, hard_delete_selected, delete_selected
   - Readonly поля для архивирования

5. Представления:
   - ProductListView: фильтр status вместо is_active
   - CombinedProductListView: поддержка фильтра status для товаров и комплектов
   - API views обновлены для работы со статусом

6. Шаблоны:
   - product_form.html: form.status вместо form.is_active
   - productkit_create.html: form.status вместо form.is_active
   - productkit_edit.html: form.status вместо form.is_active

7. Миграции:
   - Удалены все старые миграции (чистый перезапуск по требованию пользователя)
   - Создана новая миграция 0001_initial с полной структурой status-системы
   - Удален старый код преобразования is_deleted -> status

Проведённые проверки:
- Django system check passed ✓
- Полиморфные менеджеры работают с обеими системами
- Уникальные ограничения корректно работают с условиями
- История заказов сохраняется даже после архивирования товара (django-simple-history)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-15 15:30:23 +03:00
parent 079bd23829
commit 7132d2c910
26 changed files with 529 additions and 354 deletions

View File

@@ -26,7 +26,7 @@ class ProductForm(forms.ModelForm):
model = Product
fields = [
'name', 'sku', 'description', 'short_description', 'categories',
'tags', 'unit', 'cost_price', 'price', 'sale_price', 'is_active'
'tags', 'unit', 'cost_price', 'price', 'sale_price', 'status'
]
labels = {
'name': 'Название',
@@ -39,7 +39,7 @@ class ProductForm(forms.ModelForm):
'cost_price': 'Себестоимость',
'price': 'Основная цена',
'sale_price': 'Цена со скидкой',
'is_active': 'Активен'
'status': 'Статус'
}
def __init__(self, *args, **kwargs):
@@ -66,7 +66,7 @@ class ProductForm(forms.ModelForm):
self.fields['price'].widget.attrs.update({'class': 'form-control'})
self.fields['sale_price'].widget.attrs.update({'class': 'form-control'})
self.fields['unit'].widget.attrs.update({'class': 'form-control'})
self.fields['is_active'].widget.attrs.update({'class': 'form-check-input'})
self.fields['status'].widget.attrs.update({'class': 'form-control'})
def clean(self):
"""Валидация уникальности имени для активных товаров"""
@@ -78,7 +78,7 @@ class ProductForm(forms.ModelForm):
# Исключаем текущий товар при редактировании (self.instance.pk)
existing = Product.objects.filter(
name=name,
is_deleted=False
status='active'
)
if self.instance.pk:
@@ -116,7 +116,7 @@ class ProductKitForm(forms.ModelForm):
model = ProductKit
fields = [
'name', 'sku', 'description', 'short_description', 'categories',
'tags', 'sale_price', 'price_adjustment_type', 'price_adjustment_value', 'is_active'
'tags', 'sale_price', 'price_adjustment_type', 'price_adjustment_value', 'status'
]
labels = {
'name': 'Название',
@@ -128,7 +128,7 @@ class ProductKitForm(forms.ModelForm):
'sale_price': 'Цена со скидкой',
'price_adjustment_type': 'Как изменить итоговую цену',
'price_adjustment_value': 'Значение корректировки',
'is_active': 'Активен'
'status': 'Статус'
}
def __init__(self, *args, **kwargs):
@@ -158,7 +158,7 @@ class ProductKitForm(forms.ModelForm):
'step': '0.01',
'placeholder': '0'
})
self.fields['is_active'].widget.attrs.update({'class': 'form-check-input'})
self.fields['status'].widget.attrs.update({'class': 'form-control'})
def clean(self):
"""
@@ -174,7 +174,7 @@ class ProductKitForm(forms.ModelForm):
if name:
existing = ProductKit.objects.filter(
name=name,
is_deleted=False,
status='active',
is_temporary=False
)