Рефакторинг системы вариативных товаров и справочник атрибутов

Основные изменения:
- Переименование ConfigurableKitProduct → ConfigurableProduct
- Добавлена поддержка Product как варианта (не только ProductKit)
- Создан справочник атрибутов (ProductAttribute, ProductAttributeValue)
- CRUD для управления атрибутами с inline редактированием значений
- Пересозданы миграции с нуля для всех приложений
- Добавлена ссылка на атрибуты в навигацию

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 01:44:34 +03:00
parent 277a514a82
commit 79ff523adb
36 changed files with 1597 additions and 951 deletions

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.0.10 on 2025-12-23 20:38
# Generated by Django 5.0.10 on 2025-12-29 22:19
import django.db.models.deletion
from django.conf import settings
@@ -18,31 +18,6 @@ class Migration(migrations.Migration):
]
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='incomingdocument',
name='confirmed_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='confirmed_incoming_documents', to=settings.AUTH_USER_MODEL, verbose_name='Провёл'),
),
migrations.AddField(
model_name='incomingdocument',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_incoming_documents', to=settings.AUTH_USER_MODEL, verbose_name='Создал'),
),
migrations.AddField(
model_name='incomingdocumentitem',
name='document',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='inventory.incomingdocument', verbose_name='Документ'),
),
migrations.AddField(
model_name='incomingdocumentitem',
name='product',
@@ -149,49 +124,59 @@ class Migration(migrations.Migration):
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_allocations', to='inventory.stockbatch', verbose_name='Партия'),
),
migrations.AddField(
model_name='incoming',
model_name='transferdocumentitem',
name='batch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_document_items', to='inventory.stockbatch', verbose_name='Исходная партия (FIFO)'),
),
migrations.AddField(
model_name='transferdocumentitem',
name='new_batch',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transfer_document_items_created', to='inventory.stockbatch', verbose_name='Созданная партия на целевом складе'),
),
migrations.AddField(
model_name='transferdocumentitem',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_document_items', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='transferdocumentitem',
name='transfer_document',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='inventory.transferdocument', verbose_name='Документ перемещения'),
),
migrations.AddField(
model_name='transformation',
name='employee',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transformations', to=settings.AUTH_USER_MODEL, verbose_name='Сотрудник'),
),
migrations.AddField(
model_name='transformationinput',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transformation_inputs', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='transformationinput',
name='transformation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inputs', to='inventory.transformation', verbose_name='Трансформация'),
),
migrations.AddField(
model_name='reservation',
name='transformation_input',
field=models.ForeignKey(blank=True, help_text='Резерв для входного товара трансформации (черновик)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.transformationinput', verbose_name='Входной товар трансформации'),
),
migrations.AddField(
model_name='transformationoutput',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transformation_outputs', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='transformationoutput',
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='Складская партия'),
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transformation_outputs', 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='Документ перемещения'),
model_name='transformationoutput',
name='transformation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='outputs', to='inventory.transformation', verbose_name='Трансформация'),
),
migrations.AddIndex(
model_name='warehouse',
@@ -206,24 +191,19 @@ class Migration(migrations.Migration):
index=models.Index(fields=['is_pickup_point'], name='inventory_w_is_pick_e86268_idx'),
),
migrations.AddField(
model_name='transferbatch',
model_name='transformation',
name='warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transformations', to='inventory.warehouse', verbose_name='Склад'),
),
migrations.AddField(
model_name='transferdocument',
name='from_warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_batches_from', to='inventory.warehouse', verbose_name='Склад-отгрузки'),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_documents_from', to='inventory.warehouse', verbose_name='Склад-отгрузки'),
),
migrations.AddField(
model_name='transferbatch',
model_name='transferdocument',
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='На склад'),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_documents_to', to='inventory.warehouse', verbose_name='Склад-приемки'),
),
migrations.AddField(
model_name='stockbatch',
@@ -260,11 +240,6 @@ class Migration(migrations.Migration):
name='warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='incoming_documents', 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',
@@ -335,60 +310,40 @@ class Migration(migrations.Migration):
index=models.Index(fields=['locked_by_user', 'status'], name='inventory_s_locked__88eac9_idx'),
),
migrations.AddIndex(
model_name='incoming',
index=models.Index(fields=['batch'], name='inventory_i_batch_i_c50b63_idx'),
model_name='transferdocumentitem',
index=models.Index(fields=['transfer_document'], name='inventory_t_transfe_02e7fe_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'),
model_name='transferdocumentitem',
index=models.Index(fields=['product'], name='inventory_t_product_a5ed4b_idx'),
),
migrations.AlterUniqueTogether(
name='incoming',
unique_together={('batch', 'product')},
name='transferdocumentitem',
unique_together={('transfer_document', 'batch')},
),
migrations.AddIndex(
model_name='stockmovement',
index=models.Index(fields=['product'], name='inventory_s_product_cbdc37_idx'),
model_name='transformation',
index=models.Index(fields=['document_number'], name='inventory_t_documen_559778_idx'),
),
migrations.AddIndex(
model_name='stockmovement',
index=models.Index(fields=['created_at'], name='inventory_s_created_05ebf5_idx'),
model_name='transformation',
index=models.Index(fields=['warehouse', 'status'], name='inventory_t_warehou_934275_idx'),
),
migrations.AddIndex(
model_name='transferitem',
index=models.Index(fields=['transfer_batch'], name='inventory_t_transfe_f7479b_idx'),
model_name='transformation',
index=models.Index(fields=['-date'], name='inventory_t_date_65cfab_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')},
model_name='transferdocument',
index=models.Index(fields=['document_number'], name='inventory_t_documen_d9087d_idx'),
),
migrations.AddIndex(
model_name='transferbatch',
index=models.Index(fields=['document_number'], name='inventory_t_documen_143275_idx'),
model_name='transferdocument',
index=models.Index(fields=['from_warehouse', 'to_warehouse'], name='inventory_t_from_wa_118a47_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'),
model_name='transferdocument',
index=models.Index(fields=['-created_at'], name='inventory_t_created_5ad653_idx'),
),
migrations.AddIndex(
model_name='stockbatch',
@@ -458,22 +413,6 @@ class Migration(migrations.Migration):
model_name='incomingdocument',
index=models.Index(fields=['-created_at'], name='inventory_i_created_174930_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=['receipt_type'], name='inventory_i_receipt_ce70c1_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'),