795 lines
29 KiB
Python
795 lines
29 KiB
Python
# Generated for the initial product-centric commerce analytics schema.
|
||
|
||
import uuid
|
||
from decimal import Decimal
|
||
|
||
import django.core.validators
|
||
import django.db.models.deletion
|
||
import django.db.models.functions.text
|
||
from django.db import migrations, models
|
||
|
||
|
||
class Migration(migrations.Migration):
|
||
initial = True
|
||
|
||
dependencies = []
|
||
|
||
operations = [
|
||
migrations.CreateModel(
|
||
name="SourceSystem",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.BigAutoField(
|
||
auto_created=True,
|
||
primary_key=True,
|
||
serialize=False,
|
||
verbose_name="ID",
|
||
),
|
||
),
|
||
(
|
||
"code",
|
||
models.SlugField(max_length=64, unique=True, verbose_name="Код"),
|
||
),
|
||
("name", models.CharField(max_length=128, verbose_name="Название")),
|
||
(
|
||
"is_active",
|
||
models.BooleanField(default=True, verbose_name="Активен"),
|
||
),
|
||
(
|
||
"metadata",
|
||
models.JSONField(
|
||
blank=True, default=dict, 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="Product",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
primary_key=True,
|
||
serialize=False,
|
||
),
|
||
),
|
||
(
|
||
"kind",
|
||
models.CharField(
|
||
choices=[
|
||
("product", "Товар"),
|
||
("service", "Услуга"),
|
||
("bundle", "Комплект"),
|
||
],
|
||
default="product",
|
||
max_length=20,
|
||
verbose_name="Тип",
|
||
),
|
||
),
|
||
(
|
||
"sku",
|
||
models.CharField(
|
||
max_length=128, unique=True, verbose_name="Внутренний артикул"
|
||
),
|
||
),
|
||
("name", models.CharField(max_length=255, verbose_name="Название")),
|
||
(
|
||
"barcode",
|
||
models.CharField(
|
||
blank=True,
|
||
db_index=True,
|
||
max_length=64,
|
||
verbose_name="Штрихкод",
|
||
),
|
||
),
|
||
("description", models.TextField(blank=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="Обновлено"),
|
||
),
|
||
(
|
||
"parent",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
null=True,
|
||
on_delete=django.db.models.deletion.PROTECT,
|
||
related_name="variants",
|
||
to="commerce.product",
|
||
verbose_name="Родительский товар",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Товар",
|
||
"verbose_name_plural": "Товары",
|
||
"ordering": ["name", "sku"],
|
||
},
|
||
),
|
||
migrations.CreateModel(
|
||
name="SourceRecord",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
primary_key=True,
|
||
serialize=False,
|
||
),
|
||
),
|
||
(
|
||
"entity_type",
|
||
models.CharField(max_length=64, verbose_name="Тип объекта"),
|
||
),
|
||
(
|
||
"external_id",
|
||
models.CharField(max_length=255, verbose_name="Внешний ID"),
|
||
),
|
||
(
|
||
"payload",
|
||
models.JSONField(default=dict, verbose_name="Исходные данные"),
|
||
),
|
||
(
|
||
"payload_hash",
|
||
models.CharField(
|
||
editable=False, max_length=64, verbose_name="Хеш данных"
|
||
),
|
||
),
|
||
(
|
||
"source_updated_at",
|
||
models.DateTimeField(
|
||
blank=True, null=True, verbose_name="Изменено в источнике"
|
||
),
|
||
),
|
||
(
|
||
"received_at",
|
||
models.DateTimeField(auto_now=True, verbose_name="Получено"),
|
||
),
|
||
(
|
||
"created_at",
|
||
models.DateTimeField(auto_now_add=True, verbose_name="Создано"),
|
||
),
|
||
(
|
||
"source",
|
||
models.ForeignKey(
|
||
on_delete=django.db.models.deletion.PROTECT,
|
||
related_name="records",
|
||
to="commerce.sourcesystem",
|
||
verbose_name="Источник",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Исходная запись",
|
||
"verbose_name_plural": "Исходные записи",
|
||
},
|
||
),
|
||
migrations.CreateModel(
|
||
name="ProductBinding",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
primary_key=True,
|
||
serialize=False,
|
||
),
|
||
),
|
||
(
|
||
"external_id",
|
||
models.CharField(max_length=255, verbose_name="Внешний ID"),
|
||
),
|
||
(
|
||
"external_sku",
|
||
models.CharField(
|
||
blank=True,
|
||
db_index=True,
|
||
max_length=128,
|
||
verbose_name="Артикул в источнике",
|
||
),
|
||
),
|
||
(
|
||
"is_primary",
|
||
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="Обновлено"),
|
||
),
|
||
(
|
||
"product",
|
||
models.ForeignKey(
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="bindings",
|
||
to="commerce.product",
|
||
verbose_name="Товар",
|
||
),
|
||
),
|
||
(
|
||
"source",
|
||
models.ForeignKey(
|
||
on_delete=django.db.models.deletion.PROTECT,
|
||
related_name="product_bindings",
|
||
to="commerce.sourcesystem",
|
||
verbose_name="Источник",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Привязка товара",
|
||
"verbose_name_plural": "Привязки товаров",
|
||
},
|
||
),
|
||
migrations.CreateModel(
|
||
name="Sale",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
primary_key=True,
|
||
serialize=False,
|
||
),
|
||
),
|
||
(
|
||
"external_id",
|
||
models.CharField(max_length=255, verbose_name="Внешний ID"),
|
||
),
|
||
(
|
||
"event_type",
|
||
models.CharField(
|
||
choices=[("sale", "Продажа"), ("return", "Возврат")],
|
||
default="sale",
|
||
max_length=16,
|
||
verbose_name="Тип операции",
|
||
),
|
||
),
|
||
(
|
||
"number",
|
||
models.CharField(blank=True, max_length=128, verbose_name="Номер"),
|
||
),
|
||
(
|
||
"channel",
|
||
models.CharField(
|
||
blank=True,
|
||
db_index=True,
|
||
max_length=64,
|
||
verbose_name="Канал продаж",
|
||
),
|
||
),
|
||
(
|
||
"status",
|
||
models.CharField(
|
||
blank=True, max_length=128, verbose_name="Статус источника"
|
||
),
|
||
),
|
||
(
|
||
"counts_as_sale",
|
||
models.BooleanField(
|
||
default=True,
|
||
help_text="Отключается для отменённых или тестовых заказов.",
|
||
verbose_name="Учитывать в продажах",
|
||
),
|
||
),
|
||
(
|
||
"currency",
|
||
models.CharField(
|
||
default="RUB", max_length=3, verbose_name="Валюта"
|
||
),
|
||
),
|
||
(
|
||
"occurred_at",
|
||
models.DateTimeField(db_index=True, verbose_name="Дата операции"),
|
||
),
|
||
(
|
||
"source_updated_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="Обновлено"),
|
||
),
|
||
(
|
||
"source",
|
||
models.ForeignKey(
|
||
on_delete=django.db.models.deletion.PROTECT,
|
||
related_name="sales",
|
||
to="commerce.sourcesystem",
|
||
verbose_name="Источник",
|
||
),
|
||
),
|
||
(
|
||
"source_record",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="sales",
|
||
to="commerce.sourcerecord",
|
||
verbose_name="Исходная запись",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Продажа",
|
||
"verbose_name_plural": "Продажи",
|
||
"ordering": ["-occurred_at", "-created_at"],
|
||
},
|
||
),
|
||
migrations.CreateModel(
|
||
name="SaleLine",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
primary_key=True,
|
||
serialize=False,
|
||
),
|
||
),
|
||
(
|
||
"external_id",
|
||
models.CharField(max_length=255, verbose_name="Внешний ID позиции"),
|
||
),
|
||
(
|
||
"external_product_id",
|
||
models.CharField(
|
||
blank=True, max_length=255, verbose_name="Внешний ID товара"
|
||
),
|
||
),
|
||
(
|
||
"external_sku",
|
||
models.CharField(
|
||
blank=True, max_length=128, verbose_name="Внешний артикул"
|
||
),
|
||
),
|
||
(
|
||
"name",
|
||
models.CharField(
|
||
blank=True, max_length=255, verbose_name="Название в источнике"
|
||
),
|
||
),
|
||
(
|
||
"quantity",
|
||
models.DecimalField(
|
||
decimal_places=4,
|
||
default=Decimal("1"),
|
||
max_digits=18,
|
||
validators=[
|
||
django.core.validators.MinValueValidator(Decimal("0"))
|
||
],
|
||
verbose_name="Количество",
|
||
),
|
||
),
|
||
(
|
||
"gross_amount",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0"),
|
||
max_digits=18,
|
||
validators=[
|
||
django.core.validators.MinValueValidator(Decimal("0"))
|
||
],
|
||
verbose_name="Сумма до скидки",
|
||
),
|
||
),
|
||
(
|
||
"discount_amount",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0"),
|
||
max_digits=18,
|
||
validators=[
|
||
django.core.validators.MinValueValidator(Decimal("0"))
|
||
],
|
||
verbose_name="Скидка",
|
||
),
|
||
),
|
||
(
|
||
"cost_amount",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0"),
|
||
max_digits=18,
|
||
validators=[
|
||
django.core.validators.MinValueValidator(Decimal("0"))
|
||
],
|
||
verbose_name="Себестоимость",
|
||
),
|
||
),
|
||
(
|
||
"metadata",
|
||
models.JSONField(
|
||
blank=True, default=dict, verbose_name="Дополнительные данные"
|
||
),
|
||
),
|
||
(
|
||
"product",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
null=True,
|
||
on_delete=django.db.models.deletion.PROTECT,
|
||
related_name="sale_lines",
|
||
to="commerce.product",
|
||
verbose_name="Товар",
|
||
),
|
||
),
|
||
(
|
||
"sale",
|
||
models.ForeignKey(
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="lines",
|
||
to="commerce.sale",
|
||
verbose_name="Продажа",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Позиция продажи",
|
||
"verbose_name_plural": "Позиции продаж",
|
||
},
|
||
),
|
||
migrations.CreateModel(
|
||
name="Cost",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
primary_key=True,
|
||
serialize=False,
|
||
),
|
||
),
|
||
(
|
||
"external_id",
|
||
models.CharField(
|
||
blank=True, max_length=255, verbose_name="Внешний ID"
|
||
),
|
||
),
|
||
(
|
||
"channel",
|
||
models.CharField(
|
||
blank=True,
|
||
db_index=True,
|
||
max_length=64,
|
||
verbose_name="Канал продаж",
|
||
),
|
||
),
|
||
(
|
||
"cost_type",
|
||
models.CharField(
|
||
choices=[
|
||
("commission", "Комиссия"),
|
||
("delivery", "Доставка"),
|
||
("storage", "Хранение"),
|
||
("advertising", "Реклама"),
|
||
("acquiring", "Эквайринг"),
|
||
("return", "Возврат"),
|
||
("other", "Прочее"),
|
||
],
|
||
max_length=32,
|
||
verbose_name="Тип расхода",
|
||
),
|
||
),
|
||
(
|
||
"amount",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0"),
|
||
max_digits=18,
|
||
verbose_name="Сумма",
|
||
),
|
||
),
|
||
(
|
||
"currency",
|
||
models.CharField(
|
||
default="RUB", max_length=3, verbose_name="Валюта"
|
||
),
|
||
),
|
||
(
|
||
"occurred_at",
|
||
models.DateTimeField(db_index=True, verbose_name="Дата расхода"),
|
||
),
|
||
(
|
||
"metadata",
|
||
models.JSONField(
|
||
blank=True, default=dict, verbose_name="Дополнительные данные"
|
||
),
|
||
),
|
||
(
|
||
"created_at",
|
||
models.DateTimeField(auto_now_add=True, verbose_name="Создано"),
|
||
),
|
||
(
|
||
"product",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
null=True,
|
||
on_delete=django.db.models.deletion.PROTECT,
|
||
related_name="costs",
|
||
to="commerce.product",
|
||
verbose_name="Товар",
|
||
),
|
||
),
|
||
(
|
||
"sale_line",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
null=True,
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="costs",
|
||
to="commerce.saleline",
|
||
verbose_name="Позиция продажи",
|
||
),
|
||
),
|
||
(
|
||
"source",
|
||
models.ForeignKey(
|
||
on_delete=django.db.models.deletion.PROTECT,
|
||
related_name="costs",
|
||
to="commerce.sourcesystem",
|
||
verbose_name="Источник",
|
||
),
|
||
),
|
||
],
|
||
options={"verbose_name": "Расход", "verbose_name_plural": "Расходы"},
|
||
),
|
||
migrations.CreateModel(
|
||
name="StockSnapshot",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
primary_key=True,
|
||
serialize=False,
|
||
),
|
||
),
|
||
(
|
||
"warehouse_external_id",
|
||
models.CharField(max_length=255, verbose_name="ID склада"),
|
||
),
|
||
(
|
||
"warehouse_name",
|
||
models.CharField(blank=True, max_length=255, verbose_name="Склад"),
|
||
),
|
||
(
|
||
"quantity",
|
||
models.DecimalField(
|
||
decimal_places=4,
|
||
default=Decimal("0"),
|
||
max_digits=18,
|
||
verbose_name="Остаток",
|
||
),
|
||
),
|
||
(
|
||
"reserved",
|
||
models.DecimalField(
|
||
decimal_places=4,
|
||
default=Decimal("0"),
|
||
max_digits=18,
|
||
verbose_name="Резерв",
|
||
),
|
||
),
|
||
(
|
||
"in_transit",
|
||
models.DecimalField(
|
||
decimal_places=4,
|
||
default=Decimal("0"),
|
||
max_digits=18,
|
||
verbose_name="В пути",
|
||
),
|
||
),
|
||
(
|
||
"captured_at",
|
||
models.DateTimeField(db_index=True, verbose_name="Дата снимка"),
|
||
),
|
||
(
|
||
"metadata",
|
||
models.JSONField(
|
||
blank=True, default=dict, verbose_name="Дополнительные данные"
|
||
),
|
||
),
|
||
(
|
||
"created_at",
|
||
models.DateTimeField(auto_now_add=True, verbose_name="Создано"),
|
||
),
|
||
(
|
||
"product",
|
||
models.ForeignKey(
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="stock_snapshots",
|
||
to="commerce.product",
|
||
verbose_name="Товар",
|
||
),
|
||
),
|
||
(
|
||
"source",
|
||
models.ForeignKey(
|
||
on_delete=django.db.models.deletion.PROTECT,
|
||
related_name="stock_snapshots",
|
||
to="commerce.sourcesystem",
|
||
verbose_name="Источник",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Снимок остатка",
|
||
"verbose_name_plural": "Снимки остатков",
|
||
"ordering": ["-captured_at"],
|
||
},
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="product",
|
||
index=models.Index(
|
||
fields=["is_active", "name"], name="commerce_product_active_name"
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="product",
|
||
constraint=models.CheckConstraint(
|
||
condition=models.Q(("parent", models.F("id")), _negated=True),
|
||
name="commerce_product_not_own_parent",
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="product",
|
||
constraint=models.UniqueConstraint(
|
||
django.db.models.functions.text.Lower("sku"),
|
||
name="commerce_product_sku_case_insensitive",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="sourcerecord",
|
||
index=models.Index(
|
||
fields=["source", "entity_type", "source_updated_at"],
|
||
name="commerce_source_record_sync",
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="sourcerecord",
|
||
constraint=models.UniqueConstraint(
|
||
fields=("source", "entity_type", "external_id"),
|
||
name="commerce_source_record_identity",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="productbinding",
|
||
index=models.Index(
|
||
fields=["source", "external_sku"],
|
||
name="commerce_binding_source_sku",
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="productbinding",
|
||
constraint=models.UniqueConstraint(
|
||
fields=("source", "external_id"),
|
||
name="commerce_binding_source_external",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="sale",
|
||
index=models.Index(
|
||
fields=["counts_as_sale", "occurred_at"],
|
||
name="commerce_sale_counted_date",
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="sale",
|
||
constraint=models.UniqueConstraint(
|
||
fields=("source", "external_id"),
|
||
name="commerce_sale_source_external",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="saleline",
|
||
index=models.Index(
|
||
fields=["product", "sale"], name="commerce_line_product_sale"
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="saleline",
|
||
constraint=models.UniqueConstraint(
|
||
fields=("sale", "external_id"),
|
||
name="commerce_sale_line_identity",
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="saleline",
|
||
constraint=models.CheckConstraint(
|
||
condition=models.Q(("discount_amount__lte", models.F("gross_amount"))),
|
||
name="commerce_sale_line_discount_lte_gross",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="cost",
|
||
index=models.Index(
|
||
fields=["product", "occurred_at"], name="commerce_cost_product_date"
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="cost",
|
||
constraint=models.CheckConstraint(
|
||
condition=models.Q(
|
||
("product__isnull", False),
|
||
("sale_line__isnull", False),
|
||
_connector="OR",
|
||
),
|
||
name="commerce_cost_has_target",
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="cost",
|
||
constraint=models.UniqueConstraint(
|
||
condition=models.Q(("external_id", ""), _negated=True),
|
||
fields=("source", "external_id", "cost_type"),
|
||
name="commerce_cost_source_external_type",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="stocksnapshot",
|
||
index=models.Index(
|
||
fields=[
|
||
"product",
|
||
"source",
|
||
"warehouse_external_id",
|
||
"-captured_at",
|
||
],
|
||
name="commerce_stock_latest",
|
||
),
|
||
),
|
||
migrations.AddConstraint(
|
||
model_name="stocksnapshot",
|
||
constraint=models.UniqueConstraint(
|
||
fields=(
|
||
"product",
|
||
"source",
|
||
"warehouse_external_id",
|
||
"captured_at",
|
||
),
|
||
name="commerce_stock_snapshot_identity",
|
||
),
|
||
),
|
||
]
|