Рефакторинг: отделение Delivery от Order, обязательные поля доставки, исправление доменов
- Отделена модель Delivery от Order (OneToOne связь) - Добавлены обязательные поля delivery_date, time_from, time_to в Delivery - Delivery обязательна при создании заказа (кроме черновиков) - Добавлены методы calculate_total() и reset_delivery_cost() в Order - Добавлена валидация полей доставки в OrderForm - Исправлено создание доменов - убран порт из домена в БД - Исправлен редирект после установки пароля (правильный формат URL) - Исправлена ошибка NoReverseMatch в navbar для public схемы - Удалены все старые миграции (база создается с нуля) - Обновлены views для работы с новой моделью Delivery
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-15 11:57
|
||||
# Generated by Django 5.0.10 on 2025-12-23 20:38
|
||||
|
||||
import phonenumber_field.modelfields
|
||||
from django.db import migrations, models
|
||||
@@ -16,7 +16,7 @@ class Migration(migrations.Migration):
|
||||
name='DocumentCounter',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('counter_type', models.CharField(choices=[('transfer', 'Перемещение товара')], max_length=20, unique=True, verbose_name='Тип счетчика')),
|
||||
('counter_type', models.CharField(choices=[('transfer', 'Перемещение товара'), ('writeoff', 'Списание товара'), ('incoming', 'Поступление товара'), ('inventory', 'Инвентаризация')], max_length=20, unique=True, verbose_name='Тип счетчика')),
|
||||
('current_value', models.IntegerField(default=0, verbose_name='Текущее значение')),
|
||||
],
|
||||
options={
|
||||
@@ -44,6 +44,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('document_number', models.CharField(db_index=True, max_length=100, unique=True, verbose_name='Номер документа')),
|
||||
('receipt_type', models.CharField(choices=[('supplier', 'Поступление от поставщика'), ('inventory', 'Оприходование при инвентаризации'), ('adjustment', 'Оприходование без инвентаризации')], db_index=True, default='supplier', max_length=20, verbose_name='Тип поступления')),
|
||||
('supplier_name', models.CharField(blank=True, max_length=200, null=True, verbose_name='Наименование поставщика')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
@@ -55,13 +56,49 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IncomingDocument',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('document_number', models.CharField(db_index=True, max_length=100, unique=True, verbose_name='Номер документа')),
|
||||
('status', models.CharField(choices=[('draft', 'Черновик'), ('confirmed', 'Проведён'), ('cancelled', 'Отменён')], db_index=True, default='draft', max_length=20, verbose_name='Статус')),
|
||||
('date', models.DateField(help_text='Дата, к которой относится поступление', verbose_name='Дата документа')),
|
||||
('receipt_type', models.CharField(choices=[('supplier', 'Поступление от поставщика'), ('inventory', 'Оприходование при инвентаризации'), ('adjustment', 'Оприходование без инвентаризации')], db_index=True, default='supplier', max_length=20, verbose_name='Тип поступления')),
|
||||
('supplier_name', models.CharField(blank=True, help_text="Заполняется для типа 'Поступление от поставщика'", max_length=200, null=True, verbose_name='Наименование поставщика')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('confirmed_at', models.DateTimeField(blank=True, null=True, verbose_name='Дата проведения')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлён')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Документ поступления',
|
||||
'verbose_name_plural': 'Документы поступления',
|
||||
'ordering': ['-date', '-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IncomingDocumentItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Количество')),
|
||||
('cost_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Закупочная цена')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлён')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Позиция документа поступления',
|
||||
'verbose_name_plural': 'Позиции документа поступления',
|
||||
'ordering': ['id'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Inventory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('document_number', models.CharField(blank=True, db_index=True, max_length=100, null=True, unique=True, verbose_name='Номер документа')),
|
||||
('date', models.DateTimeField(auto_now_add=True, verbose_name='Дата инвентаризации')),
|
||||
('status', models.CharField(choices=[('draft', 'Черновик'), ('processing', 'В обработке'), ('completed', 'Завершена')], default='draft', max_length=20, verbose_name='Статус')),
|
||||
('conducted_by', models.CharField(blank=True, max_length=200, null=True, verbose_name='Провел инвентаризацию')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
],
|
||||
options={
|
||||
@@ -75,9 +112,13 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity_system', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Количество в системе')),
|
||||
('quantity_fact', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Фактическое количество')),
|
||||
('difference', models.DecimalField(decimal_places=3, default=0, editable=False, max_digits=10, verbose_name='Разница (факт - система)')),
|
||||
('quantity_fact', models.DecimalField(decimal_places=3, help_text='Количество свободных товаров, подсчитанных физически', max_digits=10, verbose_name='Подсчитано (факт, свободные)')),
|
||||
('difference', models.DecimalField(decimal_places=3, default=0, editable=False, help_text='(Подсчитано + Зарезервировано) - Всего на складе', max_digits=10, verbose_name='Итоговая разница')),
|
||||
('processed', models.BooleanField(default=False, verbose_name='Обработана (создана операция)')),
|
||||
('snapshot_quantity_available', models.DecimalField(blank=True, decimal_places=3, help_text='Всего на складе на момент завершения инвентаризации', max_digits=10, null=True, verbose_name='Всего на складе (snapshot)')),
|
||||
('snapshot_quantity_reserved', models.DecimalField(blank=True, decimal_places=3, help_text='В резервах на момент завершения инвентаризации', max_digits=10, null=True, verbose_name='В резервах (snapshot)')),
|
||||
('snapshot_quantity_system', models.DecimalField(blank=True, decimal_places=3, help_text='В системе свободно на момент завершения инвентаризации', max_digits=10, null=True, verbose_name='В системе свободно (snapshot)')),
|
||||
('snapshot_difference', models.DecimalField(blank=True, decimal_places=3, help_text='Итоговая разница на момент завершения инвентаризации', max_digits=10, null=True, verbose_name='Итоговая разница (snapshot)')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Строка инвентаризации',
|
||||
@@ -89,10 +130,12 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Количество')),
|
||||
('status', models.CharField(choices=[('reserved', 'Зарезервирован'), ('released', 'Освобожден'), ('converted_to_sale', 'Преобразован в продажу')], default='reserved', max_length=20, verbose_name='Статус')),
|
||||
('status', models.CharField(choices=[('reserved', 'Зарезервирован'), ('released', 'Освобожден'), ('converted_to_sale', 'Преобразован в продажу'), ('converted_to_writeoff', 'Преобразован в списание')], default='reserved', max_length=25, verbose_name='Статус')),
|
||||
('reserved_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата резервирования')),
|
||||
('released_at', models.DateTimeField(blank=True, null=True, verbose_name='Дата освобождения')),
|
||||
('converted_at', models.DateTimeField(blank=True, null=True, verbose_name='Дата преобразования в продажу')),
|
||||
('converted_at', models.DateTimeField(blank=True, help_text='Дата преобразования в продажу или списание', null=True, verbose_name='Дата преобразования')),
|
||||
('cart_lock_expires_at', models.DateTimeField(blank=True, help_text='Время истечения блокировки в корзине (для витринных комплектов)', null=True, verbose_name='Блокировка корзины истекает')),
|
||||
('cart_session_id', models.CharField(blank=True, help_text='Дополнительная идентификация сессии для надежности', max_length=100, null=True, verbose_name='ID сессии корзины')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Резервирование',
|
||||
@@ -128,6 +171,39 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'Распределения продаж по партиям',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Showcase',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Активна')),
|
||||
('is_default', models.BooleanField(default=False, verbose_name='По умолчанию')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Витрина',
|
||||
'verbose_name_plural': 'Витрины',
|
||||
'ordering': ['warehouse', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ShowcaseItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('status', models.CharField(choices=[('available', 'Доступен'), ('in_cart', 'В корзине'), ('sold', 'Продан'), ('dismantled', 'Разобран')], db_index=True, default='available', max_length=20, verbose_name='Статус')),
|
||||
('sold_at', models.DateTimeField(blank=True, null=True, verbose_name='Дата продажи')),
|
||||
('cart_lock_expires_at', models.DateTimeField(blank=True, null=True, verbose_name='Блокировка истекает')),
|
||||
('cart_session_id', models.CharField(blank=True, max_length=100, null=True, verbose_name='ID сессии корзины')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлен')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Экземпляр на витрине',
|
||||
'verbose_name_plural': 'Экземпляры на витрине',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Stock',
|
||||
fields=[
|
||||
@@ -249,4 +325,38 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['-date'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WriteOffDocument',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('document_number', models.CharField(db_index=True, max_length=100, unique=True, verbose_name='Номер документа')),
|
||||
('status', models.CharField(choices=[('draft', 'Черновик'), ('confirmed', 'Проведён'), ('cancelled', 'Отменён')], db_index=True, default='draft', max_length=20, verbose_name='Статус')),
|
||||
('date', models.DateField(help_text='Дата, к которой относится списание', verbose_name='Дата документа')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('confirmed_at', models.DateTimeField(blank=True, null=True, verbose_name='Дата проведения')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлён')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Документ списания',
|
||||
'verbose_name_plural': 'Документы списания',
|
||||
'ordering': ['-date', '-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WriteOffDocumentItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Количество')),
|
||||
('reason', models.CharField(choices=[('damage', 'Повреждение'), ('spoilage', 'Порча'), ('shortage', 'Недостача'), ('inventory', 'Инвентаризационная недостача'), ('other', 'Другое')], default='damage', max_length=20, verbose_name='Причина списания')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Позиция документа списания',
|
||||
'verbose_name_plural': 'Позиции документа списания',
|
||||
'ordering': ['id'],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-15 11:57
|
||||
# Generated by Django 5.0.10 on 2025-12-23 20:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -12,6 +13,8 @@ class Migration(migrations.Migration):
|
||||
('inventory', '0001_initial'),
|
||||
('orders', '0001_initial'),
|
||||
('products', '0001_initial'),
|
||||
('user_roles', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -25,6 +28,36 @@ class Migration(migrations.Migration):
|
||||
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',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='incoming_document_items', to='products.product', verbose_name='Товар'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventory',
|
||||
name='conducted_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inventories', to='user_roles.userrole', verbose_name='Провел инвентаризацию'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='incomingdocument',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incoming_documents', to='inventory.inventory', verbose_name='Инвентаризация'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryline',
|
||||
name='inventory',
|
||||
@@ -35,6 +68,11 @@ class Migration(migrations.Migration):
|
||||
name='product',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product', verbose_name='Товар'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='locked_by_user',
|
||||
field=models.ForeignKey(blank=True, help_text='Кассир, который добавил комплект в корзину', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cart_locks', to=settings.AUTH_USER_MODEL, verbose_name='Заблокировано пользователем'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='order_item',
|
||||
@@ -45,6 +83,11 @@ class Migration(migrations.Migration):
|
||||
name='product',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='products.product', verbose_name='Товар'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='product_kit',
|
||||
field=models.ForeignKey(blank=True, help_text='Временный комплект, для которого создан резерв', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='products.productkit', verbose_name='Комплект'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='order',
|
||||
@@ -60,6 +103,36 @@ class Migration(migrations.Migration):
|
||||
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='reservation',
|
||||
name='showcase',
|
||||
field=models.ForeignKey(blank=True, help_text='Витрина, на которой выложен букет', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.showcase', verbose_name='Витрина'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='showcaseitem',
|
||||
name='locked_by_user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='locked_showcase_items', to=settings.AUTH_USER_MODEL, verbose_name='Заблокировано пользователем'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='showcaseitem',
|
||||
name='product_kit',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='showcase_items', to='products.productkit', verbose_name='Шаблон комплекта'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='showcaseitem',
|
||||
name='showcase',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='showcase_items', to='inventory.showcase', verbose_name='Витрина'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='showcaseitem',
|
||||
name='sold_order_item',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sold_showcase_items', to='orders.orderitem', verbose_name='Позиция заказа (продажа)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='showcase_item',
|
||||
field=models.ForeignKey(blank=True, help_text='Для какого физического экземпляра создан резерв', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.showcaseitem', verbose_name='Экземпляр на витрине'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stock',
|
||||
name='product',
|
||||
@@ -162,6 +235,11 @@ class Migration(migrations.Migration):
|
||||
name='warehouse',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stocks', to='inventory.warehouse', verbose_name='Склад'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='showcase',
|
||||
name='warehouse',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='showcases', to='inventory.warehouse', verbose_name='Склад'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='warehouse',
|
||||
@@ -177,6 +255,11 @@ class Migration(migrations.Migration):
|
||||
name='warehouse',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventories', to='inventory.warehouse', verbose_name='Склад'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='incomingdocument',
|
||||
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',
|
||||
@@ -187,6 +270,70 @@ class Migration(migrations.Migration):
|
||||
name='batch',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='writeoffs', to='inventory.stockbatch', verbose_name='Партия'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='writeoffdocument',
|
||||
name='confirmed_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='confirmed_writeoff_documents', to=settings.AUTH_USER_MODEL, verbose_name='Провёл'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='writeoffdocument',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_writeoff_documents', to=settings.AUTH_USER_MODEL, verbose_name='Создал'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='writeoffdocument',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='writeoff_documents', to='inventory.inventory', verbose_name='Инвентаризация'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='writeoffdocument',
|
||||
name='warehouse',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='writeoff_documents', to='inventory.warehouse', verbose_name='Склад'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='writeoffdocumentitem',
|
||||
name='document',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='inventory.writeoffdocument', verbose_name='Документ'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='writeoffdocumentitem',
|
||||
name='product',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='writeoff_document_items', to='products.product', verbose_name='Товар'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='writeoffdocumentitem',
|
||||
name='reservation',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='writeoff_document_item_reverse', to='inventory.reservation', verbose_name='Резерв'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='writeoff_document_item',
|
||||
field=models.ForeignKey(blank=True, help_text='Резерв для документа списания (черновик)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.writeoffdocumentitem', verbose_name='Позиция документа списания'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocumentitem',
|
||||
index=models.Index(fields=['document'], name='inventory_i_documen_96d470_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocumentitem',
|
||||
index=models.Index(fields=['product'], name='inventory_i_product_932432_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcaseitem',
|
||||
index=models.Index(fields=['showcase', 'status'], name='inventory_s_showcas_116f7f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcaseitem',
|
||||
index=models.Index(fields=['product_kit', 'status'], name='inventory_s_product_785870_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcaseitem',
|
||||
index=models.Index(fields=['status', 'cart_lock_expires_at'], name='inventory_s_status_6acf05_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcaseitem',
|
||||
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'),
|
||||
@@ -263,6 +410,18 @@ class Migration(migrations.Migration):
|
||||
name='stock',
|
||||
unique_together={('product', 'warehouse')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcase',
|
||||
index=models.Index(fields=['warehouse'], name='inventory_s_warehou_1e4a8a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcase',
|
||||
index=models.Index(fields=['is_active'], name='inventory_s_is_acti_387bfb_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcase',
|
||||
index=models.Index(fields=['is_default'], name='inventory_s_is_defa_bf9a7c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='sale',
|
||||
index=models.Index(fields=['product', 'warehouse'], name='inventory_s_product_084314_idx'),
|
||||
@@ -275,6 +434,78 @@ class Migration(migrations.Migration):
|
||||
model_name='sale',
|
||||
index=models.Index(fields=['order'], name='inventory_s_order_i_7d13ea_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='inventory',
|
||||
index=models.Index(fields=['document_number'], name='inventory_i_documen_8df782_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocument',
|
||||
index=models.Index(fields=['document_number'], name='inventory_i_documen_5b89ad_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocument',
|
||||
index=models.Index(fields=['warehouse', 'status'], name='inventory_i_warehou_8f141d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocument',
|
||||
index=models.Index(fields=['date'], name='inventory_i_date_8ace9b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocument',
|
||||
index=models.Index(fields=['receipt_type'], name='inventory_i_receipt_92f322_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
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'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoff',
|
||||
index=models.Index(fields=['date'], name='inventory_w_date_70c7e3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocument',
|
||||
index=models.Index(fields=['document_number'], name='inventory_w_documen_a9ae00_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocument',
|
||||
index=models.Index(fields=['warehouse', 'status'], name='inventory_w_warehou_69fbf6_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocument',
|
||||
index=models.Index(fields=['date'], name='inventory_w_date_a853cb_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocument',
|
||||
index=models.Index(fields=['-created_at'], name='inventory_w_created_02c298_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocumentitem',
|
||||
index=models.Index(fields=['document'], name='inventory_w_documen_d77c5e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocumentitem',
|
||||
index=models.Index(fields=['product'], name='inventory_w_product_6e32fc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['product', 'warehouse'], name='inventory_r_product_fa0d33_idx'),
|
||||
@@ -288,23 +519,27 @@ class Migration(migrations.Migration):
|
||||
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'),
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['showcase'], name='inventory_r_showcas_bd3508_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingbatch',
|
||||
index=models.Index(fields=['warehouse'], name='inventory_i_warehou_cc3a73_idx'),
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['product_kit'], name='inventory_r_product_70aed5_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingbatch',
|
||||
index=models.Index(fields=['-created_at'], name='inventory_i_created_59ee8b_idx'),
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['cart_lock_expires_at'], name='inventory_r_cart_lo_e9b52a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoff',
|
||||
index=models.Index(fields=['batch'], name='inventory_w_batch_i_b098ce_idx'),
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['locked_by_user'], name='inventory_r_locked__706cbf_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoff',
|
||||
index=models.Index(fields=['date'], name='inventory_w_date_70c7e3_idx'),
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['product_kit', 'cart_lock_expires_at'], name='inventory_r_product_5dacdf_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['showcase_item'], name='inventory_r_showcas_8cfff5_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-16 18:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0002_initial'),
|
||||
('orders', '0002_initial'),
|
||||
('products', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Showcase',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Активна')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='showcases', to='inventory.warehouse', verbose_name='Склад')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Витрина',
|
||||
'verbose_name_plural': 'Витрины',
|
||||
'ordering': ['warehouse', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='showcase',
|
||||
field=models.ForeignKey(blank=True, help_text='Витрина, на которой выложен букет', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.showcase', verbose_name='Витрина'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['showcase'], name='inventory_r_showcas_bd3508_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcase',
|
||||
index=models.Index(fields=['warehouse'], name='inventory_s_warehou_1e4a8a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcase',
|
||||
index=models.Index(fields=['is_active'], name='inventory_s_is_acti_387bfb_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-20 08:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0003_showcase_reservation_showcase_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='showcase',
|
||||
name='is_default',
|
||||
field=models.BooleanField(default=False, verbose_name='По умолчанию'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcase',
|
||||
index=models.Index(fields=['is_default'], name='inventory_s_is_defa_bf9a7c_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,25 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-20 12:09
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0004_showcase_is_default_and_more'),
|
||||
('orders', '0003_historicalorderitem_is_from_showcase_and_more'),
|
||||
('products', '0008_productkit_showcase_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='product_kit',
|
||||
field=models.ForeignKey(blank=True, help_text='Временный комплект, для которого создан резерв', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='products.productkit', verbose_name='Комплект'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['product_kit'], name='inventory_r_product_70aed5_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,45 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-20 20:20
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0005_reservation_product_kit_and_more'),
|
||||
('orders', '0003_historicalorderitem_is_from_showcase_and_more'),
|
||||
('products', '0008_productkit_showcase_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='cart_lock_expires_at',
|
||||
field=models.DateTimeField(blank=True, help_text='Время истечения блокировки в корзине (для витринных комплектов)', null=True, verbose_name='Блокировка корзины истекает'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='cart_session_id',
|
||||
field=models.CharField(blank=True, help_text='Дополнительная идентификация сессии для надежности', max_length=100, null=True, verbose_name='ID сессии корзины'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='locked_by_user',
|
||||
field=models.ForeignKey(blank=True, help_text='Кассир, который добавил комплект в корзину', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cart_locks', to=settings.AUTH_USER_MODEL, verbose_name='Заблокировано пользователем'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['cart_lock_expires_at'], name='inventory_r_cart_lo_e9b52a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['locked_by_user'], name='inventory_r_locked__706cbf_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['product_kit', 'cart_lock_expires_at'], name='inventory_r_product_5dacdf_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,63 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-09 04:19
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0006_reservation_cart_lock_expires_at_and_more'),
|
||||
('orders', '0006_transaction_delete_payment_and_more'),
|
||||
('products', '0010_alter_product_cost_price'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ShowcaseItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('status', models.CharField(choices=[('available', 'Доступен'), ('in_cart', 'В корзине'), ('sold', 'Продан'), ('dismantled', 'Разобран')], db_index=True, default='available', max_length=20, verbose_name='Статус')),
|
||||
('sold_at', models.DateTimeField(blank=True, null=True, verbose_name='Дата продажи')),
|
||||
('cart_lock_expires_at', models.DateTimeField(blank=True, null=True, verbose_name='Блокировка истекает')),
|
||||
('cart_session_id', models.CharField(blank=True, max_length=100, null=True, verbose_name='ID сессии корзины')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлен')),
|
||||
('locked_by_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='locked_showcase_items', to=settings.AUTH_USER_MODEL, verbose_name='Заблокировано пользователем')),
|
||||
('product_kit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='showcase_items', to='products.productkit', verbose_name='Шаблон комплекта')),
|
||||
('showcase', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='showcase_items', to='inventory.showcase', verbose_name='Витрина')),
|
||||
('sold_order_item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sold_showcase_item', to='orders.orderitem', verbose_name='Позиция заказа (продажа)')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Экземпляр на витрине',
|
||||
'verbose_name_plural': 'Экземпляры на витрине',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='showcase_item',
|
||||
field=models.ForeignKey(blank=True, help_text='Для какого физического экземпляра создан резерв', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.showcaseitem', verbose_name='Экземпляр на витрине'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='reservation',
|
||||
index=models.Index(fields=['showcase_item'], name='inventory_r_showcas_8cfff5_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcaseitem',
|
||||
index=models.Index(fields=['showcase', 'status'], name='inventory_s_showcas_116f7f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcaseitem',
|
||||
index=models.Index(fields=['product_kit', 'status'], name='inventory_s_product_785870_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcaseitem',
|
||||
index=models.Index(fields=['status', 'cart_lock_expires_at'], name='inventory_s_status_6acf05_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='showcaseitem',
|
||||
index=models.Index(fields=['locked_by_user', 'status'], name='inventory_s_locked__88eac9_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,64 +0,0 @@
|
||||
# Generated manually - Data migration for ShowcaseItem
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_showcase_kits_to_items(apps, schema_editor):
|
||||
"""
|
||||
Для каждого существующего витринного букета (ProductKit с is_temporary=True и showcase):
|
||||
1. Создать ShowcaseItem
|
||||
2. Привязать существующие Reservation к этому ShowcaseItem
|
||||
"""
|
||||
ProductKit = apps.get_model('products', 'ProductKit')
|
||||
ShowcaseItem = apps.get_model('inventory', 'ShowcaseItem')
|
||||
Reservation = apps.get_model('inventory', 'Reservation')
|
||||
|
||||
# Находим все витринные комплекты
|
||||
showcase_kits = ProductKit.objects.filter(
|
||||
is_temporary=True,
|
||||
showcase__isnull=False
|
||||
)
|
||||
|
||||
for kit in showcase_kits:
|
||||
# Создаём ShowcaseItem для каждого существующего витринного букета
|
||||
showcase_item = ShowcaseItem.objects.create(
|
||||
showcase=kit.showcase,
|
||||
product_kit=kit,
|
||||
status='available'
|
||||
)
|
||||
|
||||
# Привязываем существующие резервы к этому ShowcaseItem
|
||||
Reservation.objects.filter(
|
||||
product_kit=kit,
|
||||
showcase=kit.showcase,
|
||||
status='reserved'
|
||||
).update(showcase_item=showcase_item)
|
||||
|
||||
|
||||
def reverse_migration(apps, schema_editor):
|
||||
"""
|
||||
Откат: удаляем созданные ShowcaseItem и очищаем связи в Reservation
|
||||
"""
|
||||
ShowcaseItem = apps.get_model('inventory', 'ShowcaseItem')
|
||||
Reservation = apps.get_model('inventory', 'Reservation')
|
||||
|
||||
# Очищаем связи в резервах
|
||||
Reservation.objects.filter(showcase_item__isnull=False).update(showcase_item=None)
|
||||
|
||||
# Удаляем все ShowcaseItem
|
||||
ShowcaseItem.objects.all().delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0007_add_showcase_item_model'),
|
||||
('products', '0001_initial'), # Убедимся что ProductKit существует
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
migrate_showcase_kits_to_items,
|
||||
reverse_code=reverse_migration
|
||||
),
|
||||
]
|
||||
@@ -1,65 +0,0 @@
|
||||
# Generated manually - Fix ShowcaseItem status for already sold kits
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def fix_showcase_items_status(apps, schema_editor):
|
||||
"""
|
||||
Исправляем статус ShowcaseItem для уже проданных комплектов.
|
||||
|
||||
Логика:
|
||||
- Если у ShowcaseItem нет активных резервов (status='reserved') →
|
||||
это уже проданный/разобранный букет → удаляем ShowcaseItem
|
||||
"""
|
||||
ShowcaseItem = apps.get_model('inventory', 'ShowcaseItem')
|
||||
Reservation = apps.get_model('inventory', 'Reservation')
|
||||
|
||||
# Находим все ShowcaseItem в статусе 'available'
|
||||
available_items = ShowcaseItem.objects.filter(status='available')
|
||||
|
||||
items_to_delete = []
|
||||
|
||||
for item in available_items:
|
||||
# Проверяем есть ли активные резервы для этого экземпляра
|
||||
has_active_reservations = Reservation.objects.filter(
|
||||
showcase_item=item,
|
||||
status='reserved'
|
||||
).exists()
|
||||
|
||||
# Если резервы не привязаны к showcase_item, проверяем старым способом
|
||||
if not has_active_reservations:
|
||||
has_active_reservations = Reservation.objects.filter(
|
||||
product_kit=item.product_kit,
|
||||
showcase=item.showcase,
|
||||
status='reserved'
|
||||
).exists()
|
||||
|
||||
if not has_active_reservations:
|
||||
# Нет активных резервов - этот букет уже продан/разобран
|
||||
items_to_delete.append(item.id)
|
||||
|
||||
# Удаляем ShowcaseItem без активных резервов
|
||||
if items_to_delete:
|
||||
ShowcaseItem.objects.filter(id__in=items_to_delete).delete()
|
||||
|
||||
|
||||
def reverse_migration(apps, schema_editor):
|
||||
"""
|
||||
Откат невозможен - удалённые ShowcaseItem не восстановить.
|
||||
Но это безопасно - они относились к уже проданным букетам.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0008_migrate_showcase_kits_to_items'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
fix_showcase_items_status,
|
||||
reverse_code=reverse_migration
|
||||
),
|
||||
]
|
||||
@@ -1,91 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-10 19:21
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0009_fix_showcase_items_status'),
|
||||
('products', '0010_alter_product_cost_price'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='documentcounter',
|
||||
name='counter_type',
|
||||
field=models.CharField(choices=[('transfer', 'Перемещение товара'), ('writeoff', 'Списание товара')], max_length=20, unique=True, verbose_name='Тип счетчика'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WriteOffDocument',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('document_number', models.CharField(db_index=True, max_length=100, unique=True, verbose_name='Номер документа')),
|
||||
('status', models.CharField(choices=[('draft', 'Черновик'), ('confirmed', 'Проведён'), ('cancelled', 'Отменён')], db_index=True, default='draft', max_length=20, verbose_name='Статус')),
|
||||
('date', models.DateField(help_text='Дата, к которой относится списание', verbose_name='Дата документа')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('confirmed_at', models.DateTimeField(blank=True, null=True, verbose_name='Дата проведения')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлён')),
|
||||
('confirmed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='confirmed_writeoff_documents', to=settings.AUTH_USER_MODEL, verbose_name='Провёл')),
|
||||
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_writeoff_documents', to=settings.AUTH_USER_MODEL, verbose_name='Создал')),
|
||||
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='writeoff_documents', to='inventory.warehouse', verbose_name='Склад')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Документ списания',
|
||||
'verbose_name_plural': 'Документы списания',
|
||||
'ordering': ['-date', '-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WriteOffDocumentItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Количество')),
|
||||
('reason', models.CharField(choices=[('damage', 'Повреждение'), ('spoilage', 'Порча'), ('shortage', 'Недостача'), ('inventory', 'Инвентаризационная недостача'), ('other', 'Другое')], default='damage', max_length=20, verbose_name='Причина списания')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='inventory.writeoffdocument', verbose_name='Документ')),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='writeoff_document_items', to='products.product', verbose_name='Товар')),
|
||||
('reservation', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='writeoff_document_item_reverse', to='inventory.reservation', verbose_name='Резерв')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Позиция документа списания',
|
||||
'verbose_name_plural': 'Позиции документа списания',
|
||||
'ordering': ['id'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reservation',
|
||||
name='writeoff_document_item',
|
||||
field=models.ForeignKey(blank=True, help_text='Резерв для документа списания (черновик)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='inventory.writeoffdocumentitem', verbose_name='Позиция документа списания'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocument',
|
||||
index=models.Index(fields=['document_number'], name='inventory_w_documen_a9ae00_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocument',
|
||||
index=models.Index(fields=['warehouse', 'status'], name='inventory_w_warehou_69fbf6_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocument',
|
||||
index=models.Index(fields=['date'], name='inventory_w_date_a853cb_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocument',
|
||||
index=models.Index(fields=['-created_at'], name='inventory_w_created_02c298_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocumentitem',
|
||||
index=models.Index(fields=['document'], name='inventory_w_documen_d77c5e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='writeoffdocumentitem',
|
||||
index=models.Index(fields=['product'], name='inventory_w_product_6e32fc_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-11 18:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0010_writeoff_document'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='reservation',
|
||||
name='converted_at',
|
||||
field=models.DateTimeField(blank=True, help_text='Дата преобразования в продажу или списание', null=True, verbose_name='Дата преобразования'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='reservation',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('reserved', 'Зарезервирован'), ('released', 'Освобожден'), ('converted_to_sale', 'Преобразован в продажу'), ('converted_to_writeoff', 'Преобразован в списание')], default='reserved', max_length=25, verbose_name='Статус'),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-11 19:23
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0011_add_writeoff_status_to_reservation'),
|
||||
('orders', '0006_transaction_delete_payment_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='showcaseitem',
|
||||
name='sold_order_item',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sold_showcase_items', to='orders.orderitem', verbose_name='Позиция заказа (продажа)'),
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-20 20:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0012_change_sold_order_item_to_fk'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='incomingbatch',
|
||||
name='receipt_type',
|
||||
field=models.CharField(choices=[('supplier', 'Поступление от поставщика'), ('inventory', 'Оприходование при инвентаризации'), ('adjustment', 'Оприходование без инвентаризации')], db_index=True, default='supplier', max_length=20, verbose_name='Тип поступления'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingbatch',
|
||||
index=models.Index(fields=['receipt_type'], name='inventory_i_receipt_ce70c1_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,91 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-20 21:10
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0013_add_receipt_type_to_incomingbatch'),
|
||||
('products', '0010_alter_product_cost_price'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='documentcounter',
|
||||
name='counter_type',
|
||||
field=models.CharField(choices=[('transfer', 'Перемещение товара'), ('writeoff', 'Списание товара'), ('incoming', 'Поступление товара')], max_length=20, unique=True, verbose_name='Тип счетчика'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IncomingDocument',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('document_number', models.CharField(db_index=True, max_length=100, unique=True, verbose_name='Номер документа')),
|
||||
('status', models.CharField(choices=[('draft', 'Черновик'), ('confirmed', 'Проведён'), ('cancelled', 'Отменён')], db_index=True, default='draft', max_length=20, verbose_name='Статус')),
|
||||
('date', models.DateField(help_text='Дата, к которой относится поступление', verbose_name='Дата документа')),
|
||||
('receipt_type', models.CharField(choices=[('supplier', 'Поступление от поставщика'), ('inventory', 'Оприходование при инвентаризации'), ('adjustment', 'Оприходование без инвентаризации')], db_index=True, default='supplier', max_length=20, verbose_name='Тип поступления')),
|
||||
('supplier_name', models.CharField(blank=True, help_text="Заполняется для типа 'Поступление от поставщика'", max_length=200, null=True, verbose_name='Наименование поставщика')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('confirmed_at', models.DateTimeField(blank=True, null=True, verbose_name='Дата проведения')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлён')),
|
||||
('confirmed_by', 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='Провёл')),
|
||||
('created_by', 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='Создал')),
|
||||
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='incoming_documents', to='inventory.warehouse', verbose_name='Склад')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Документ поступления',
|
||||
'verbose_name_plural': 'Документы поступления',
|
||||
'ordering': ['-date', '-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IncomingDocumentItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Количество')),
|
||||
('cost_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Закупочная цена')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлён')),
|
||||
('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='inventory.incomingdocument', verbose_name='Документ')),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='incoming_document_items', to='products.product', verbose_name='Товар')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Позиция документа поступления',
|
||||
'verbose_name_plural': 'Позиции документа поступления',
|
||||
'ordering': ['id'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocument',
|
||||
index=models.Index(fields=['document_number'], name='inventory_i_documen_5b89ad_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocument',
|
||||
index=models.Index(fields=['warehouse', 'status'], name='inventory_i_warehou_8f141d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocument',
|
||||
index=models.Index(fields=['date'], name='inventory_i_date_8ace9b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocument',
|
||||
index=models.Index(fields=['receipt_type'], name='inventory_i_receipt_92f322_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocument',
|
||||
index=models.Index(fields=['-created_at'], name='inventory_i_created_174930_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocumentitem',
|
||||
index=models.Index(fields=['document'], name='inventory_i_documen_96d470_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='incomingdocumentitem',
|
||||
index=models.Index(fields=['product'], name='inventory_i_product_932432_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-21 18:45
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0014_alter_documentcounter_counter_type_incomingdocument_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='incomingdocument',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incoming_documents', to='inventory.inventory', verbose_name='Инвентаризация'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='writeoffdocument',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='writeoff_documents', to='inventory.inventory', verbose_name='Инвентаризация'),
|
||||
),
|
||||
]
|
||||
@@ -1,27 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-21 19:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0015_add_inventory_foreign_keys'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='inventory',
|
||||
name='document_number',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=100, null=True, unique=True, verbose_name='Номер документа'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='documentcounter',
|
||||
name='counter_type',
|
||||
field=models.CharField(choices=[('transfer', 'Перемещение товара'), ('writeoff', 'Списание товара'), ('incoming', 'Поступление товара'), ('inventory', 'Инвентаризация')], max_length=20, unique=True, verbose_name='Тип счетчика'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='inventory',
|
||||
index=models.Index(fields=['document_number'], name='inventory_i_documen_8df782_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,101 +0,0 @@
|
||||
# Generated manually - Change conducted_by from CharField to ForeignKey
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def migrate_conducted_by_data(apps, schema_editor):
|
||||
"""
|
||||
Миграция данных: для существующих записей инвентаризации
|
||||
найти первого пользователя с ролью "owner" (владелец) в текущем тенанте
|
||||
и проставить его UserRole. Если владельца нет - оставить NULL.
|
||||
"""
|
||||
Inventory = apps.get_model('inventory', 'Inventory')
|
||||
UserRole = apps.get_model('user_roles', 'UserRole')
|
||||
Role = apps.get_model('user_roles', 'Role')
|
||||
|
||||
# Находим первого владельца в текущем тенанте
|
||||
try:
|
||||
owner_role = Role.objects.get(code='owner', is_system=True)
|
||||
owner_user_role = UserRole.objects.filter(
|
||||
role=owner_role,
|
||||
is_active=True
|
||||
).first()
|
||||
|
||||
if owner_user_role:
|
||||
# Обновляем все существующие инвентаризации, у которых есть старое текстовое значение
|
||||
# Используем conducted_by_old (переименованное поле) для проверки
|
||||
Inventory.objects.exclude(conducted_by_old__isnull=True).exclude(conducted_by_old='').update(
|
||||
conducted_by_new=owner_user_role
|
||||
)
|
||||
except Role.DoesNotExist:
|
||||
# Если нет роли - оставляем NULL
|
||||
pass
|
||||
|
||||
|
||||
def reverse_migration(apps, schema_editor):
|
||||
"""
|
||||
Откат: очищаем новое поле
|
||||
"""
|
||||
Inventory = apps.get_model('inventory', 'Inventory')
|
||||
Inventory.objects.filter(conducted_by_new__isnull=False).update(conducted_by_new=None)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0016_add_document_number_to_inventory'),
|
||||
('user_roles', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Шаг 1: Переименовываем старое поле в conducted_by_old
|
||||
migrations.RenameField(
|
||||
model_name='inventory',
|
||||
old_name='conducted_by',
|
||||
new_name='conducted_by_old',
|
||||
),
|
||||
# Шаг 2: Добавляем новое поле как ForeignKey
|
||||
migrations.AddField(
|
||||
model_name='inventory',
|
||||
name='conducted_by_new',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='inventories_new',
|
||||
to='user_roles.userrole',
|
||||
verbose_name='Провел инвентаризацию'
|
||||
),
|
||||
),
|
||||
# Шаг 3: Миграция данных
|
||||
migrations.RunPython(
|
||||
migrate_conducted_by_data,
|
||||
reverse_code=reverse_migration
|
||||
),
|
||||
# Шаг 4: Удаляем старое поле
|
||||
migrations.RemoveField(
|
||||
model_name='inventory',
|
||||
name='conducted_by_old',
|
||||
),
|
||||
# Шаг 5: Переименовываем новое поле в conducted_by
|
||||
migrations.RenameField(
|
||||
model_name='inventory',
|
||||
old_name='conducted_by_new',
|
||||
new_name='conducted_by',
|
||||
),
|
||||
# Шаг 6: Исправляем related_name
|
||||
migrations.AlterField(
|
||||
model_name='inventory',
|
||||
name='conducted_by',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='inventories',
|
||||
to='user_roles.userrole',
|
||||
verbose_name='Провел инвентаризацию'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-22 10:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0017_change_conducted_by_to_fk'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='inventoryline',
|
||||
name='snapshot_difference',
|
||||
field=models.DecimalField(blank=True, decimal_places=3, help_text='Итоговая разница на момент завершения инвентаризации', max_digits=10, null=True, verbose_name='Итоговая разница (snapshot)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryline',
|
||||
name='snapshot_quantity_available',
|
||||
field=models.DecimalField(blank=True, decimal_places=3, help_text='Всего на складе на момент завершения инвентаризации', max_digits=10, null=True, verbose_name='Всего на складе (snapshot)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryline',
|
||||
name='snapshot_quantity_reserved',
|
||||
field=models.DecimalField(blank=True, decimal_places=3, help_text='В резервах на момент завершения инвентаризации', max_digits=10, null=True, verbose_name='В резервах (snapshot)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryline',
|
||||
name='snapshot_quantity_system',
|
||||
field=models.DecimalField(blank=True, decimal_places=3, help_text='В системе свободно на момент завершения инвентаризации', max_digits=10, null=True, verbose_name='В системе свободно (snapshot)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventoryline',
|
||||
name='difference',
|
||||
field=models.DecimalField(decimal_places=3, default=0, editable=False, help_text='(Подсчитано + Зарезервировано) - Всего на складе', max_digits=10, verbose_name='Итоговая разница'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventoryline',
|
||||
name='quantity_fact',
|
||||
field=models.DecimalField(decimal_places=3, help_text='Количество свободных товаров, подсчитанных физически', max_digits=10, verbose_name='Подсчитано (факт, свободные)'),
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user