Files
octopus/myproject/products/migrations/0001_initial.py
Andrey Smakotin 123f330a26 chore(migrations): update migration generation timestamps to latest time
- Updated generated timestamps in initial migrations of accounts, customers,
  inventory, orders, products, tenants, and user_roles apps
- Reflect new generation time from 08:35 to 23:23 on 2026-01-03
- No changes to migration logic or schema detected
- Ensures migration files align with most recent generation time for consistency
2026-01-04 02:29:49 +03:00

667 lines
50 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 2026-01-03 23:23
import django.core.validators
import django.db.models.deletion
import products.models.photos
from decimal import Decimal
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('inventory', '0001_initial'),
('orders', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='KitItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.DecimalField(blank=True, decimal_places=3, max_digits=10, null=True, verbose_name='Количество')),
],
options={
'verbose_name': 'Компонент комплекта',
'verbose_name_plural': 'Компоненты комплектов',
},
),
migrations.CreateModel(
name='ProductAttribute',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Например: Длина стебля, Цвет, Размер', max_length=100, unique=True, verbose_name='Название')),
('slug', models.SlugField(blank=True, help_text='Автоматически генерируется из названия', max_length=100, unique=True, verbose_name='Slug')),
('description', models.TextField(blank=True, help_text='Опциональное описание атрибута', verbose_name='Описание')),
('position', models.PositiveIntegerField(default=0, help_text='Порядок отображения в списке', 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': ['position', 'name'],
},
),
migrations.CreateModel(
name='ProductVariantGroup',
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, 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': ['name'],
},
),
migrations.CreateModel(
name='ProductVariantGroupItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('priority', models.PositiveIntegerField(default=0, help_text='Меньше = выше приоритет (1 - наивысший приоритет в этой группе)')),
],
options={
'verbose_name': 'Товар в группе вариантов',
'verbose_name_plural': 'Товары в группах вариантов',
'ordering': ['priority', 'id'],
},
),
migrations.CreateModel(
name='SKUCounter',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('counter_type', models.CharField(choices=[('product', 'Product Counter'), ('kit', 'Kit Counter'), ('category', 'Category Counter'), ('configurable', 'Configurable Product Counter')], max_length=20, unique=True, verbose_name='Тип счетчика')),
('current_value', models.IntegerField(default=0, verbose_name='Текущее значение')),
],
options={
'verbose_name': 'Счетчик артикулов',
'verbose_name_plural': 'Счетчики артикулов',
},
),
migrations.CreateModel(
name='UnitOfMeasure',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(help_text='Короткий код: шт, кг, банч, ветка', max_length=20, unique=True, verbose_name='Код')),
('name', models.CharField(help_text='Полное название: Штука, Килограмм, Банч', max_length=100, verbose_name='Название')),
('short_name', models.CharField(help_text='Для UI: шт., кг., бч.', max_length=10, verbose_name='Сокращение')),
('is_active', models.BooleanField(default=True, verbose_name='Активна')),
('position', models.PositiveIntegerField(default=0, verbose_name='Порядок сортировки')),
],
options={
'verbose_name': 'Единица измерения',
'verbose_name_plural': 'Единицы измерения',
'ordering': ['position', 'name'],
},
),
migrations.CreateModel(
name='ConfigurableProduct',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Название')),
('sku', models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='Артикул')),
('slug', models.SlugField(blank=True, max_length=200, unique=True, verbose_name='URL-идентификатор')),
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
('short_description', models.TextField(blank=True, help_text='Используется для карточек товаров, превью и площадок', null=True, verbose_name='Краткое описание')),
('status', models.CharField(choices=[('active', 'Активный'), ('archived', 'Архивный'), ('discontinued', 'Снят')], db_index=True, default='active', max_length=20, verbose_name='Статус')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
('archived_at', models.DateTimeField(blank=True, null=True, verbose_name='Время архивирования')),
('archived_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='archived_%(class)s_set', to=settings.AUTH_USER_MODEL, verbose_name='Архивировано пользователем')),
],
options={
'verbose_name': 'Вариативный товар',
'verbose_name_plural': 'Вариативные товары',
},
),
migrations.CreateModel(
name='ConfigurableProductAttribute',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Например: Цвет, Размер, Длина', max_length=150, verbose_name='Название атрибута')),
('option', models.CharField(help_text='Например: Красный, M, 60см', max_length=150, verbose_name='Значение опции')),
('position', models.PositiveIntegerField(default=0, help_text='Меньше = выше в списке', verbose_name='Порядок отображения')),
('visible', models.BooleanField(default=True, help_text='Показывать ли атрибут на странице товара', verbose_name='Видимый на витрине')),
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parent_attributes', to='products.configurableproduct', verbose_name='Родительский товар')),
],
options={
'verbose_name': 'Атрибут вариативного товара',
'verbose_name_plural': 'Атрибуты вариативных товаров',
'ordering': ['parent', 'position', 'name', 'option'],
},
),
migrations.CreateModel(
name='ConfigurableProductOption',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('attributes', models.JSONField(blank=True, default=dict, help_text='Структурированные атрибуты. Пример: {"length": "60", "color": "red"}', verbose_name='Атрибуты варианта')),
('is_default', models.BooleanField(default=False, verbose_name='Вариант по умолчанию')),
('variant_sku', models.CharField(blank=True, help_text='Дополнительный артикул для внешних площадок. Генерируется автоматически.', max_length=50, verbose_name='Артикул варианта')),
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='products.configurableproduct', verbose_name='Родитель (вариативный товар)')),
],
options={
'verbose_name': 'Вариант товара',
'verbose_name_plural': 'Варианты товаров',
},
),
migrations.CreateModel(
name='ConfigurableProductOptionAttribute',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('attribute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.configurableproductattribute', verbose_name='Значение атрибута')),
('option', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attributes_set', to='products.configurableproductoption', verbose_name='Вариант')),
],
options={
'verbose_name': 'Атрибут варианта',
'verbose_name_plural': 'Атрибуты варианта',
},
),
migrations.CreateModel(
name='PhotoProcessingStatus',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('photo_id', models.IntegerField(help_text='ID объекта ProductPhoto/ProductKitPhoto/ProductCategoryPhoto', verbose_name='ID фото')),
('photo_model', models.CharField(help_text='Полный путь модели (e.g., products.ProductPhoto)', max_length=100, verbose_name='Модель фото')),
('status', models.CharField(choices=[('pending', 'В очереди'), ('processing', 'Обрабатывается'), ('completed', 'Завершено'), ('failed', 'Ошибка')], db_index=True, default='pending', max_length=20, verbose_name='Статус обработки')),
('task_id', models.CharField(blank=True, db_index=True, help_text='Уникальный ID задачи для отслеживания', max_length=255, verbose_name='ID задачи Celery')),
('error_message', models.TextField(blank=True, help_text='Детальное описание ошибки при обработке', verbose_name='Сообщение об ошибке')),
('result_data', models.JSONField(blank=True, default=dict, help_text='JSON с информацией о качестве, путях и метаданных', verbose_name='Результаты обработки')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
('started_at', models.DateTimeField(blank=True, null=True, verbose_name='Время начала обработки')),
('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='Время завершения обработки')),
],
options={
'verbose_name': 'Статус обработки фото',
'verbose_name_plural': 'Статусы обработки фото',
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['photo_id', 'photo_model'], name='products_ph_photo_i_e42a67_idx'), models.Index(fields=['task_id'], name='products_ph_task_id_748118_idx'), models.Index(fields=['status'], name='products_ph_status_1182b4_idx'), models.Index(fields=['status', 'created_at'], name='products_ph_status_41d415_idx')],
},
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Название')),
('sku', models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='Артикул')),
('slug', models.SlugField(blank=True, max_length=200, unique=True, verbose_name='URL-идентификатор')),
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
('short_description', models.TextField(blank=True, help_text='Используется для карточек товаров, превью и площадок', null=True, verbose_name='Краткое описание')),
('status', models.CharField(choices=[('active', 'Активный'), ('archived', 'Архивный'), ('discontinued', 'Снят')], db_index=True, default='active', max_length=20, verbose_name='Статус')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
('archived_at', models.DateTimeField(blank=True, null=True, verbose_name='Время архивирования')),
('variant_suffix', models.CharField(blank=True, help_text='Суффикс для артикула (например: 50, 60, S, M). Автоматически извлекается из названия.', max_length=20, null=True, verbose_name='Суффикс варианта')),
('unit', models.CharField(choices=[('шт', 'Штука'), ('м', 'Метр'), ('г', 'Грамм'), ('л', 'Литр'), ('кг', 'Килограмм')], default='шт', max_length=10, verbose_name='Единица измерения (deprecated)')),
('cost_price', models.DecimalField(blank=True, decimal_places=2, default=0, help_text='Автоматически вычисляется из партий (средневзвешенная стоимость по FIFO)', max_digits=10, null=True, verbose_name='Себестоимость')),
('price', models.DecimalField(decimal_places=2, help_text='Цена продажи товара (бывшее поле sale_price)', max_digits=10, verbose_name='Основная цена')),
('sale_price', models.DecimalField(blank=True, decimal_places=2, help_text='Если задана, товар продается по этой цене (дешевле основной)', max_digits=10, null=True, verbose_name='Цена со скидкой')),
('in_stock', models.BooleanField(db_index=True, default=False, help_text='Автоматически обновляется при изменении остатков на складе', verbose_name='В наличии')),
('search_keywords', models.TextField(blank=True, help_text='Автоматически генерируется из названия, артикула, описания и категории. Можно дополнить вручную.', verbose_name='Ключевые слова для поиска')),
('archived_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='archived_%(class)s_set', to=settings.AUTH_USER_MODEL, verbose_name='Архивировано пользователем')),
],
options={
'verbose_name': 'Товар',
'verbose_name_plural': 'Товары',
},
),
migrations.CreateModel(
name='KitItemPriority',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('priority', models.PositiveIntegerField(default=0, help_text='Меньше = выше приоритет (0 - наивысший)')),
('kit_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='priorities', to='products.kititem', verbose_name='Позиция в букете')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product', verbose_name='Товар')),
],
options={
'verbose_name': 'Приоритет варианта',
'verbose_name_plural': 'Приоритеты вариантов',
'ordering': ['priority', 'id'],
},
),
migrations.AddField(
model_name='kititem',
name='product',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='kit_items_direct', to='products.product', verbose_name='Конкретный товар'),
),
migrations.CreateModel(
name='CostPriceHistory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('old_cost_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Старая себестоимость')),
('new_cost_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Новая себестоимость')),
('reason', models.CharField(choices=[('incoming', 'Поступление товара'), ('batch_edit', 'Редактирование партии'), ('batch_delete', 'Удаление партии'), ('recalculation', 'Пересчет себестоимости'), ('system', 'Системная корректировка')], max_length=20, verbose_name='Причина изменения')),
('related_object_id', models.IntegerField(blank=True, help_text='Например, ID партии (StockBatch) для поступлений', null=True, verbose_name='ID связанного объекта')),
('related_object_type', models.CharField(blank=True, help_text="Например, 'StockBatch' для партий", max_length=50, verbose_name='Тип связанного объекта')),
('notes', models.TextField(blank=True, help_text='Дополнительная информация об изменении', verbose_name='Примечания')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата и время изменения')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cost_price_history', to='products.product', verbose_name='Товар')),
],
options={
'verbose_name': 'История себестоимости',
'verbose_name_plural': 'Истории себестоимости',
'ordering': ['-created_at'],
},
),
migrations.AddField(
model_name='configurableproductoption',
name='product',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='as_configurable_option_in', to='products.product', verbose_name='Товар (вариант)'),
),
migrations.AddField(
model_name='configurableproductattribute',
name='product',
field=models.ForeignKey(blank=True, help_text='Какой Product связан с этим значением атрибута', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='as_attribute_value_in', to='products.product', verbose_name='Товар для этого значения'),
),
migrations.CreateModel(
name='ProductAttributeValue',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.CharField(help_text='Например: 50, 60, 70 (для длины) или Красный, Белый (для цвета)', max_length=100, verbose_name='Значение')),
('slug', models.SlugField(blank=True, max_length=100, verbose_name='Slug')),
('position', models.PositiveIntegerField(default=0, help_text='Порядок отображения в списке значений', verbose_name='Позиция')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
('attribute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='values', to='products.productattribute', verbose_name='Атрибут')),
],
options={
'verbose_name': 'Значение атрибута',
'verbose_name_plural': 'Значения атрибутов',
'ordering': ['position', 'value'],
},
),
migrations.CreateModel(
name='ProductCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Название')),
('sku', models.CharField(blank=True, db_index=True, max_length=100, null=True, unique=True, verbose_name='Артикул')),
('slug', models.SlugField(blank=True, max_length=200, unique=True, verbose_name='URL-идентификатор')),
('is_active', models.BooleanField(default=True, verbose_name='Активна')),
('created_at', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Дата создания')),
('updated_at', models.DateTimeField(auto_now=True, null=True, verbose_name='Дата обновления')),
('is_deleted', models.BooleanField(db_index=True, default=False, verbose_name='Удалена')),
('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='Время удаления')),
('deleted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_categories', to=settings.AUTH_USER_MODEL, verbose_name='Удалена пользователем')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='products.productcategory', verbose_name='Родительская категория')),
],
options={
'verbose_name': 'Категория товара',
'verbose_name_plural': 'Категории товаров',
},
),
migrations.AddField(
model_name='product',
name='categories',
field=models.ManyToManyField(blank=True, related_name='products', to='products.productcategory', verbose_name='Категории'),
),
migrations.CreateModel(
name='ProductCategoryPhoto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('image', models.ImageField(upload_to=products.models.photos.get_category_photo_upload_path, verbose_name='Оригинальное фото')),
('quality_level', models.CharField(choices=[('excellent', 'Отлично (>= 2052px)'), ('good', 'Хорошо (1512-2051px)'), ('acceptable', 'Приемлемо (864-1511px)'), ('poor', 'Плохо (432-863px)'), ('very_poor', 'Очень плохо (< 432px)')], db_index=True, default='acceptable', help_text='Определяется автоматически на основе размера изображения', max_length=15, verbose_name='Уровень качества')),
('quality_warning', models.BooleanField(db_index=True, default=False, help_text='True если нужно обновить фото перед выгрузкой на сайт', verbose_name='Требует обновления')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='products.productcategory', verbose_name='Категория')),
],
options={
'verbose_name': 'Фото категории',
'verbose_name_plural': 'Фото категорий',
'ordering': ['order', '-created_at'],
},
),
migrations.CreateModel(
name='ProductKit',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Название')),
('sku', models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='Артикул')),
('slug', models.SlugField(blank=True, max_length=200, unique=True, verbose_name='URL-идентификатор')),
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
('short_description', models.TextField(blank=True, help_text='Используется для карточек товаров, превью и площадок', null=True, verbose_name='Краткое описание')),
('status', models.CharField(choices=[('active', 'Активный'), ('archived', 'Архивный'), ('discontinued', 'Снят')], db_index=True, default='active', max_length=20, verbose_name='Статус')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
('archived_at', models.DateTimeField(blank=True, null=True, verbose_name='Время архивирования')),
('base_price', models.DecimalField(decimal_places=2, default=0, help_text='Сумма actual_price всех компонентов. Пересчитывается автоматически.', max_digits=10, verbose_name='Базовая цена')),
('price', models.DecimalField(decimal_places=2, default=0, help_text='Базовая цена с учетом корректировок. Вычисляется автоматически.', max_digits=10, verbose_name='Итоговая цена')),
('sale_price', models.DecimalField(blank=True, decimal_places=2, help_text='Если задана, комплект продается по этой цене', max_digits=10, null=True, verbose_name='Цена со скидкой')),
('price_adjustment_type', models.CharField(choices=[('none', 'Без изменения'), ('increase_percent', 'Увеличить на %'), ('increase_amount', 'Увеличить на сумму'), ('decrease_percent', 'Уменьшить на %'), ('decrease_amount', 'Уменьшить на сумму')], default='none', max_length=20, verbose_name='Тип корректировки цены')),
('price_adjustment_value', models.DecimalField(decimal_places=2, default=0, help_text='Процент (%) или сумма (руб) в зависимости от типа корректировки', max_digits=10, verbose_name='Значение корректировки')),
('is_temporary', models.BooleanField(default=False, help_text='Временные комплекты не показываются в каталоге и создаются для конкретного заказа', verbose_name='Временный комплект')),
('archived_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='archived_%(class)s_set', to=settings.AUTH_USER_MODEL, verbose_name='Архивировано пользователем')),
('categories', models.ManyToManyField(blank=True, related_name='kits', to='products.productcategory', verbose_name='Категории')),
('order', models.ForeignKey(blank=True, help_text='Заказ, для которого создан временный комплект', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='temporary_kits', to='orders.order', verbose_name='Заказ')),
('showcase', models.ForeignKey(blank=True, help_text='Витрина, на которой выложен временный комплект', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='temporary_kits', to='inventory.showcase', verbose_name='Витрина')),
],
options={
'verbose_name': 'Комплект',
'verbose_name_plural': 'Комплекты',
},
),
migrations.AddField(
model_name='kititem',
name='kit',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='kit_items', to='products.productkit', verbose_name='Комплект'),
),
migrations.AddField(
model_name='configurableproductoption',
name='kit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='as_configurable_option_in', to='products.productkit', verbose_name='Комплект (вариант)'),
),
migrations.AddField(
model_name='configurableproductattribute',
name='kit',
field=models.ForeignKey(blank=True, help_text='Какой ProductKit связан с этим значением атрибута', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='as_attribute_value_in', to='products.productkit', verbose_name='Комплект для этого значения'),
),
migrations.CreateModel(
name='ProductKitPhoto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('image', models.ImageField(upload_to=products.models.photos.get_kit_photo_upload_path, verbose_name='Оригинальное фото')),
('quality_level', models.CharField(choices=[('excellent', 'Отлично (>= 2052px)'), ('good', 'Хорошо (1512-2051px)'), ('acceptable', 'Приемлемо (864-1511px)'), ('poor', 'Плохо (432-863px)'), ('very_poor', 'Очень плохо (< 432px)')], db_index=True, default='acceptable', help_text='Определяется автоматически на основе размера изображения', max_length=15, verbose_name='Уровень качества')),
('quality_warning', models.BooleanField(db_index=True, default=False, help_text='True если нужно обновить фото перед выгрузкой на сайт', verbose_name='Требует обновления')),
('kit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='products.productkit', verbose_name='Комплект')),
],
options={
'verbose_name': 'Фото комплекта',
'verbose_name_plural': 'Фото комплектов',
'ordering': ['order', '-created_at'],
},
),
migrations.CreateModel(
name='ProductPhoto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('image', models.ImageField(upload_to=products.models.photos.get_product_photo_upload_path, verbose_name='Оригинальное фото')),
('quality_level', models.CharField(choices=[('excellent', 'Отлично (>= 2052px)'), ('good', 'Хорошо (1512-2051px)'), ('acceptable', 'Приемлемо (864-1511px)'), ('poor', 'Плохо (432-863px)'), ('very_poor', 'Очень плохо (< 432px)')], db_index=True, default='acceptable', help_text='Определяется автоматически на основе размера изображения', max_length=15, verbose_name='Уровень качества')),
('quality_warning', models.BooleanField(db_index=True, default=False, help_text='True если нужно обновить фото перед выгрузкой на сайт (poor или very_poor)', verbose_name='Требует обновления')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='products.product', verbose_name='Товар')),
],
options={
'verbose_name': 'Фото товара',
'verbose_name_plural': 'Фото товаров',
'ordering': ['order', '-created_at'],
},
),
migrations.CreateModel(
name='ProductSalesUnit',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text="Например: 'Ветка большая', 'Ветка средняя'", max_length=100, verbose_name='Название')),
('conversion_factor', models.DecimalField(decimal_places=6, help_text='Сколько единиц продажи в 1 базовой единице товара. Например: 15 (из 1 банча получается 15 больших веток)', max_digits=15, validators=[django.core.validators.MinValueValidator(Decimal('0.000001'))], verbose_name='Коэффициент конверсии')),
('price', models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0'))], verbose_name='Цена продажи')),
('sale_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0'))], verbose_name='Цена со скидкой')),
('min_quantity', models.DecimalField(decimal_places=3, default=Decimal('1'), help_text='Минимальное количество для продажи', max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.001'))], verbose_name='Мин. количество')),
('quantity_step', models.DecimalField(decimal_places=3, default=Decimal('1'), help_text='С каким шагом можно заказывать (0.1, 0.5, 1)', max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.001'))], verbose_name='Шаг количества')),
('is_default', models.BooleanField(default=False, help_text='Единица, выбираемая по умолчанию при добавлении в заказ', verbose_name='Единица по умолчанию')),
('is_active', models.BooleanField(default=True, verbose_name='Активна')),
('position', models.PositiveIntegerField(default=0, verbose_name='Порядок сортировки')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sales_units', to='products.product', verbose_name='Товар')),
],
options={
'verbose_name': 'Единица продажи товара',
'verbose_name_plural': 'Единицы продажи товаров',
'ordering': ['position', 'id'],
},
),
migrations.CreateModel(
name='ProductTag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='Название')),
('slug', models.SlugField(max_length=100, unique=True, verbose_name='URL-идентификатор')),
('is_active', models.BooleanField(db_index=True, default=True, verbose_name='Активен')),
('created_at', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Дата создания')),
('updated_at', models.DateTimeField(auto_now=True, null=True, verbose_name='Дата обновления')),
],
options={
'verbose_name': 'Тег товара',
'verbose_name_plural': 'Теги товаров',
'indexes': [models.Index(fields=['is_active'], name='products_pr_is_acti_7f288f_idx')],
},
),
migrations.AddConstraint(
model_name='producttag',
constraint=models.UniqueConstraint(condition=models.Q(('is_active', True)), fields=('name',), name='unique_active_tag_name'),
),
migrations.AddField(
model_name='productkit',
name='tags',
field=models.ManyToManyField(blank=True, related_name='kits', to='products.producttag', verbose_name='Теги'),
),
migrations.AddField(
model_name='product',
name='tags',
field=models.ManyToManyField(blank=True, related_name='products', to='products.producttag', verbose_name='Теги'),
),
migrations.AddField(
model_name='product',
name='variant_groups',
field=models.ManyToManyField(blank=True, related_name='products', to='products.productvariantgroup', verbose_name='Группы вариантов'),
),
migrations.AddField(
model_name='kititem',
name='variant_group',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='kit_items', to='products.productvariantgroup', verbose_name='Группа вариантов'),
),
migrations.AddField(
model_name='productvariantgroupitem',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variant_group_items', to='products.product', verbose_name='Товар'),
),
migrations.AddField(
model_name='productvariantgroupitem',
name='variant_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='products.productvariantgroup', verbose_name='Группа вариантов'),
),
migrations.AddField(
model_name='productsalesunit',
name='unit',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='products.unitofmeasure', verbose_name='Единица измерения'),
),
migrations.AddField(
model_name='product',
name='base_unit',
field=models.ForeignKey(blank=True, help_text="Единица хранения и закупки (банч, кг, шт). Если указана, используется вместо поля 'unit'.", null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='products.unitofmeasure', verbose_name='Базовая единица'),
),
migrations.AddIndex(
model_name='configurableproductoptionattribute',
index=models.Index(fields=['option'], name='products_co_option__bd950a_idx'),
),
migrations.AddIndex(
model_name='configurableproductoptionattribute',
index=models.Index(fields=['attribute'], name='products_co_attribu_705d5a_idx'),
),
migrations.AlterUniqueTogether(
name='configurableproductoptionattribute',
unique_together={('option', 'attribute')},
),
migrations.AlterUniqueTogether(
name='kititempriority',
unique_together={('kit_item', 'product')},
),
migrations.AddIndex(
model_name='costpricehistory',
index=models.Index(fields=['product', '-created_at'], name='products_co_product_3320c9_idx'),
),
migrations.AddIndex(
model_name='costpricehistory',
index=models.Index(fields=['reason'], name='products_co_reason_959ee1_idx'),
),
migrations.AddIndex(
model_name='productattributevalue',
index=models.Index(fields=['attribute', 'position'], name='products_pr_attribu_460f9e_idx'),
),
migrations.AlterUniqueTogether(
name='productattributevalue',
unique_together={('attribute', 'value')},
),
migrations.AddIndex(
model_name='productcategory',
index=models.Index(fields=['is_active'], name='products_pr_is_acti_226e74_idx'),
),
migrations.AddIndex(
model_name='productcategory',
index=models.Index(fields=['is_deleted'], name='products_pr_is_dele_2a96d1_idx'),
),
migrations.AddIndex(
model_name='productcategory',
index=models.Index(fields=['is_deleted', 'created_at'], name='products_pr_is_dele_b8cdf3_idx'),
),
migrations.AddConstraint(
model_name='productcategory',
constraint=models.UniqueConstraint(condition=models.Q(('is_deleted', False)), fields=('name',), name='unique_active_category_name'),
),
migrations.AddIndex(
model_name='productcategoryphoto',
index=models.Index(fields=['quality_level'], name='products_pr_quality_ab44c2_idx'),
),
migrations.AddIndex(
model_name='productcategoryphoto',
index=models.Index(fields=['quality_warning'], name='products_pr_quality_d7c69b_idx'),
),
migrations.AddIndex(
model_name='productcategoryphoto',
index=models.Index(fields=['quality_warning', 'category'], name='products_pr_quality_fc505a_idx'),
),
migrations.AddIndex(
model_name='configurableproductoption',
index=models.Index(fields=['parent'], name='products_co_parent__36761a_idx'),
),
migrations.AddIndex(
model_name='configurableproductoption',
index=models.Index(fields=['kit'], name='products_co_kit_id_9e9a00_idx'),
),
migrations.AddIndex(
model_name='configurableproductoption',
index=models.Index(fields=['product'], name='products_co_product_4d77ae_idx'),
),
migrations.AddIndex(
model_name='configurableproductoption',
index=models.Index(fields=['parent', 'is_default'], name='products_co_parent__be8830_idx'),
),
migrations.AddIndex(
model_name='configurableproductoption',
index=models.Index(fields=['variant_sku'], name='products_co_variant_2da938_idx'),
),
migrations.AddConstraint(
model_name='configurableproductoption',
constraint=models.CheckConstraint(check=models.Q(models.Q(('kit__isnull', False), ('product__isnull', True)), models.Q(('kit__isnull', True), ('product__isnull', False)), _connector='OR'), name='configurable_option_kit_xor_product'),
),
migrations.AddIndex(
model_name='configurableproductattribute',
index=models.Index(fields=['parent', 'name'], name='products_co_parent__78337c_idx'),
),
migrations.AddIndex(
model_name='configurableproductattribute',
index=models.Index(fields=['parent', 'position'], name='products_co_parent__90f012_idx'),
),
migrations.AddIndex(
model_name='configurableproductattribute',
index=models.Index(fields=['kit'], name='products_co_kit_id_db7ebb_idx'),
),
migrations.AddIndex(
model_name='configurableproductattribute',
index=models.Index(fields=['product'], name='products_co_product_68c16a_idx'),
),
migrations.AddIndex(
model_name='productkitphoto',
index=models.Index(fields=['quality_level'], name='products_pr_quality_b03c5c_idx'),
),
migrations.AddIndex(
model_name='productkitphoto',
index=models.Index(fields=['quality_warning'], name='products_pr_quality_2aa941_idx'),
),
migrations.AddIndex(
model_name='productkitphoto',
index=models.Index(fields=['quality_warning', 'kit'], name='products_pr_quality_867664_idx'),
),
migrations.AddIndex(
model_name='productphoto',
index=models.Index(fields=['quality_level'], name='products_pr_quality_d8f85c_idx'),
),
migrations.AddIndex(
model_name='productphoto',
index=models.Index(fields=['quality_warning'], name='products_pr_quality_defb5a_idx'),
),
migrations.AddIndex(
model_name='productphoto',
index=models.Index(fields=['quality_warning', 'product'], name='products_pr_quality_6e8b51_idx'),
),
migrations.AddIndex(
model_name='productkit',
index=models.Index(fields=['is_temporary'], name='products_pr_is_temp_e407a2_idx'),
),
migrations.AddIndex(
model_name='productkit',
index=models.Index(fields=['order'], name='products_pr_order_i_2b5675_idx'),
),
migrations.AddIndex(
model_name='productkit',
index=models.Index(fields=['showcase'], name='products_pr_showcas_08c1ca_idx'),
),
migrations.AddConstraint(
model_name='productkit',
constraint=models.UniqueConstraint(condition=models.Q(('is_temporary', False), ('status', 'active')), fields=('name',), name='unique_active_kit_name'),
),
migrations.AddIndex(
model_name='kititem',
index=models.Index(fields=['kit'], name='products_ki_kit_id_d28dc9_idx'),
),
migrations.AddIndex(
model_name='kititem',
index=models.Index(fields=['product'], name='products_ki_product_d2ad00_idx'),
),
migrations.AddIndex(
model_name='kititem',
index=models.Index(fields=['variant_group'], name='products_ki_variant_e42628_idx'),
),
migrations.AddIndex(
model_name='kititem',
index=models.Index(fields=['kit', 'product'], name='products_ki_kit_id_14738f_idx'),
),
migrations.AddIndex(
model_name='kititem',
index=models.Index(fields=['kit', 'variant_group'], name='products_ki_kit_id_8199a8_idx'),
),
migrations.AddIndex(
model_name='productvariantgroupitem',
index=models.Index(fields=['variant_group', 'priority'], name='products_pr_variant_b36b47_idx'),
),
migrations.AddIndex(
model_name='productvariantgroupitem',
index=models.Index(fields=['product'], name='products_pr_product_50be04_idx'),
),
migrations.AlterUniqueTogether(
name='productvariantgroupitem',
unique_together={('variant_group', 'product')},
),
migrations.AlterUniqueTogether(
name='productsalesunit',
unique_together={('product', 'name')},
),
migrations.AddIndex(
model_name='product',
index=models.Index(fields=['in_stock'], name='products_pr_in_stoc_4fee1a_idx'),
),
migrations.AddIndex(
model_name='product',
index=models.Index(fields=['sku'], name='products_pr_sku_ca0cdc_idx'),
),
]