Files
octopus/myproject/inventory/migrations/0002_initial.py
Andrey Smakotin 7132d2c910 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>
2025-11-15 15:30:23 +03:00

311 lines
14 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.
# Generated by Django 5.0.10 on 2025-11-15 11:57
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('inventory', '0001_initial'),
('orders', '0001_initial'),
('products', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='incoming',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incomings', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='incoming',
name='batch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='inventory.incomingbatch', verbose_name='Партия'),
),
migrations.AddField(
model_name='inventoryline',
name='inventory',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lines', to='inventory.inventory', verbose_name='Инвентаризация'),
),
migrations.AddField(
model_name='inventoryline',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='reservation',
name='order_item',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='orders.orderitem', verbose_name='Позиция заказа'),
),
migrations.AddField(
model_name='reservation',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='sale',
name='order',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sales', to='orders.order', verbose_name='Заказ'),
),
migrations.AddField(
model_name='sale',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='salebatchallocation',
name='sale',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='batch_allocations', to='inventory.sale', verbose_name='Продажа'),
),
migrations.AddField(
model_name='stock',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stocks', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='stockbatch',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stock_batches', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='salebatchallocation',
name='batch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_allocations', to='inventory.stockbatch', verbose_name='Партия'),
),
migrations.AddField(
model_name='incoming',
name='stock_batch',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incomings', to='inventory.stockbatch', verbose_name='Складская партия'),
),
migrations.AddField(
model_name='stockmovement',
name='order',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_movements', to='orders.order', verbose_name='Заказ'),
),
migrations.AddField(
model_name='stockmovement',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='movements', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='transfer',
name='batch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers', to='inventory.stockbatch', verbose_name='Партия'),
),
migrations.AddField(
model_name='transfer',
name='new_batch',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transfer_sources', to='inventory.stockbatch', verbose_name='Новая партия'),
),
migrations.AddField(
model_name='transferitem',
name='batch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_items', to='inventory.stockbatch', verbose_name='Исходная партия (FIFO)'),
),
migrations.AddField(
model_name='transferitem',
name='new_batch',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transfer_items_created', to='inventory.stockbatch', verbose_name='Созданная партия на целевом складе'),
),
migrations.AddField(
model_name='transferitem',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_items', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='transferitem',
name='transfer_batch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='inventory.transferbatch', verbose_name='Документ перемещения'),
),
migrations.AddIndex(
model_name='warehouse',
index=models.Index(fields=['is_active'], name='inventory_w_is_acti_3ddeac_idx'),
),
migrations.AddIndex(
model_name='warehouse',
index=models.Index(fields=['is_default'], name='inventory_w_is_defa_4b7615_idx'),
),
migrations.AddIndex(
model_name='warehouse',
index=models.Index(fields=['is_pickup_point'], name='inventory_w_is_pick_e86268_idx'),
),
migrations.AddField(
model_name='transferbatch',
name='from_warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_batches_from', to='inventory.warehouse', verbose_name='Склад-отгрузки'),
),
migrations.AddField(
model_name='transferbatch',
name='to_warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_batches_to', to='inventory.warehouse', verbose_name='Склад-приемки'),
),
migrations.AddField(
model_name='transfer',
name='from_warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_from', to='inventory.warehouse', verbose_name='Из склада'),
),
migrations.AddField(
model_name='transfer',
name='to_warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_to', to='inventory.warehouse', verbose_name='На склад'),
),
migrations.AddField(
model_name='stockbatch',
name='warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stock_batches', to='inventory.warehouse', verbose_name='Склад'),
),
migrations.AddField(
model_name='stock',
name='warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stocks', to='inventory.warehouse', verbose_name='Склад'),
),
migrations.AddField(
model_name='sale',
name='warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.warehouse', verbose_name='Склад'),
),
migrations.AddField(
model_name='reservation',
name='warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.warehouse', verbose_name='Склад'),
),
migrations.AddField(
model_name='inventory',
name='warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventories', to='inventory.warehouse', verbose_name='Склад'),
),
migrations.AddField(
model_name='incomingbatch',
name='warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incoming_batches', to='inventory.warehouse', verbose_name='Склад'),
),
migrations.AddField(
model_name='writeoff',
name='batch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='writeoffs', to='inventory.stockbatch', verbose_name='Партия'),
),
migrations.AddIndex(
model_name='incoming',
index=models.Index(fields=['batch'], name='inventory_i_batch_i_c50b63_idx'),
),
migrations.AddIndex(
model_name='incoming',
index=models.Index(fields=['product'], name='inventory_i_product_39b00d_idx'),
),
migrations.AddIndex(
model_name='incoming',
index=models.Index(fields=['-created_at'], name='inventory_i_created_563ec0_idx'),
),
migrations.AlterUniqueTogether(
name='incoming',
unique_together={('batch', 'product')},
),
migrations.AddIndex(
model_name='stockmovement',
index=models.Index(fields=['product'], name='inventory_s_product_cbdc37_idx'),
),
migrations.AddIndex(
model_name='stockmovement',
index=models.Index(fields=['created_at'], name='inventory_s_created_05ebf5_idx'),
),
migrations.AddIndex(
model_name='transferitem',
index=models.Index(fields=['transfer_batch'], name='inventory_t_transfe_f7479b_idx'),
),
migrations.AddIndex(
model_name='transferitem',
index=models.Index(fields=['product'], name='inventory_t_product_0e0ec9_idx'),
),
migrations.AlterUniqueTogether(
name='transferitem',
unique_together={('transfer_batch', 'batch')},
),
migrations.AddIndex(
model_name='transferbatch',
index=models.Index(fields=['document_number'], name='inventory_t_documen_143275_idx'),
),
migrations.AddIndex(
model_name='transferbatch',
index=models.Index(fields=['from_warehouse', 'to_warehouse'], name='inventory_t_from_wa_2a41f1_idx'),
),
migrations.AddIndex(
model_name='transferbatch',
index=models.Index(fields=['-created_at'], name='inventory_t_created_b6fd05_idx'),
),
migrations.AddIndex(
model_name='transfer',
index=models.Index(fields=['from_warehouse', 'to_warehouse'], name='inventory_t_from_wa_578feb_idx'),
),
migrations.AddIndex(
model_name='transfer',
index=models.Index(fields=['date'], name='inventory_t_date_e1402d_idx'),
),
migrations.AddIndex(
model_name='stockbatch',
index=models.Index(fields=['product', 'warehouse'], name='inventory_s_product_022460_idx'),
),
migrations.AddIndex(
model_name='stockbatch',
index=models.Index(fields=['created_at'], name='inventory_s_created_10279b_idx'),
),
migrations.AddIndex(
model_name='stockbatch',
index=models.Index(fields=['is_active'], name='inventory_s_is_acti_0dd559_idx'),
),
migrations.AddIndex(
model_name='stock',
index=models.Index(fields=['product', 'warehouse'], name='inventory_s_product_112b63_idx'),
),
migrations.AlterUniqueTogether(
name='stock',
unique_together={('product', 'warehouse')},
),
migrations.AddIndex(
model_name='sale',
index=models.Index(fields=['product', 'warehouse'], name='inventory_s_product_084314_idx'),
),
migrations.AddIndex(
model_name='sale',
index=models.Index(fields=['date'], name='inventory_s_date_8972d4_idx'),
),
migrations.AddIndex(
model_name='sale',
index=models.Index(fields=['order'], name='inventory_s_order_i_7d13ea_idx'),
),
migrations.AddIndex(
model_name='reservation',
index=models.Index(fields=['product', 'warehouse'], name='inventory_r_product_fa0d33_idx'),
),
migrations.AddIndex(
model_name='reservation',
index=models.Index(fields=['status'], name='inventory_r_status_806333_idx'),
),
migrations.AddIndex(
model_name='reservation',
index=models.Index(fields=['order_item'], name='inventory_r_order_i_ae991f_idx'),
),
migrations.AddIndex(
model_name='incomingbatch',
index=models.Index(fields=['document_number'], name='inventory_i_documen_679096_idx'),
),
migrations.AddIndex(
model_name='incomingbatch',
index=models.Index(fields=['warehouse'], name='inventory_i_warehou_cc3a73_idx'),
),
migrations.AddIndex(
model_name='incomingbatch',
index=models.Index(fields=['-created_at'], name='inventory_i_created_59ee8b_idx'),
),
migrations.AddIndex(
model_name='writeoff',
index=models.Index(fields=['batch'], name='inventory_w_batch_i_b098ce_idx'),
),
migrations.AddIndex(
model_name='writeoff',
index=models.Index(fields=['date'], name='inventory_w_date_70c7e3_idx'),
),
]