Warehouse Service — Data Model
База данных: warehouse_db
(Добавлено в BR 1.9)
erDiagram tech_cards { uuid id PK uuid franchise_id "NOT NULL" uuid product_id "NULL (→ Catalog Service)" uuid ingredient_id FK "NULL → ingredients.id" uuid modifier_option_id "NULL (→ Catalog Service)" varchar name "NOT NULL, (255)" decimal output_weight "NOT NULL, (10,3)" varchar output_unit "NOT NULL, (20)" text cooking_description "NULL" varchar status "NOT NULL, default active, (20)" timestamp created_at "NOT NULL" timestamp updated_at "NOT NULL" } ingredients { uuid id PK uuid franchise_id "NOT NULL" varchar name "NOT NULL, (255)" text description "NULL" varchar unit_of_measure "NOT NULL, (20)" varchar status "NOT NULL, default active, (20)" timestamp created_at "NOT NULL" timestamp updated_at "NOT NULL" } recipe_items { uuid id PK uuid tech_card_id FK "NOT NULL" uuid ingredient_id FK "NOT NULL → ingredients.id" decimal gross_weight "NOT NULL, (10,3)" decimal net_weight "NOT NULL, (10,3)" decimal cold_loss_percent "NULL, (5,2)" decimal hot_loss_percent "NULL, (5,2)" varchar unit_of_measure "NOT NULL, (20)" integer sort_order "NOT NULL, default 0" timestamp created_at "NOT NULL" } unit_conversions { uuid id PK uuid franchise_id "NOT NULL" uuid product_id "NULL (→ Catalog Service)" uuid ingredient_id FK "NULL → ingredients.id" varchar from_unit "NOT NULL, (20)" varchar to_unit "NOT NULL, (20)" decimal factor "NOT NULL, (10,4)" timestamp created_at "NOT NULL" timestamp updated_at "NOT NULL" } modifier_tech_cards { uuid id PK uuid franchise_id "NOT NULL" uuid modifier_option_id "NOT NULL (→ Catalog Service)" decimal output_weight "NOT NULL, (10,3)" varchar output_unit "NOT NULL, (20)" text cooking_description "NULL" varchar status "NOT NULL, default active, (20)" timestamp created_at "NOT NULL" timestamp updated_at "NOT NULL" } modifier_tech_card_items { uuid id PK uuid modifier_tech_card_id FK "NOT NULL" uuid ingredient_id FK "NOT NULL → ingredients.id" decimal gross_weight "NOT NULL, (10,3)" decimal net_weight "NOT NULL, (10,3)" decimal cold_loss_percent "NULL, (5,2)" decimal hot_loss_percent "NULL, (5,2)" varchar unit_of_measure "NOT NULL, (20)" integer sort_order "NOT NULL, default 0" timestamp created_at "NOT NULL" } tech_cards ||--o{ recipe_items : "contains" modifier_tech_cards ||--o{ modifier_tech_card_items : "contains" ingredients ||--o{ recipe_items : "used in" ingredients ||--o{ modifier_tech_card_items : "used in" ingredients ||--o{ tech_cards : "has tech card" ingredients ||--o{ stock_batches : "has batches" ingredients ||--o{ stock_balances : "has balance" ingredients ||--o{ receipt_act_lines : "received" ingredients ||--o{ write_off_act_lines : "written off" warehouses { uuid id PK uuid franchise_id "NOT NULL" uuid store_id "NOT NULL (→ Store Service)" varchar name "NOT NULL, (255)" varchar status "NOT NULL, default active, (20)" timestamp created_at "NOT NULL" } stock_batches { uuid id PK uuid warehouse_id FK "NOT NULL → warehouses.id" uuid ingredient_id FK "NOT NULL → ingredients.id" decimal purchase_price "NOT NULL, (10,4)" decimal quantity "NOT NULL, (12,3)" varchar unit_of_measure "NOT NULL, (20)" timestamp received_date "NOT NULL" date shelf_life_date "NULL" varchar status "NOT NULL, default active, (20)" timestamp created_at "NOT NULL" } stock_balances { uuid id PK uuid warehouse_id FK "NOT NULL → warehouses.id" uuid ingredient_id FK "NOT NULL → ingredients.id" decimal current_quantity "NOT NULL, (12,3)" decimal average_cost "NULL, (10,4)" varchar unit_of_measure "NOT NULL, (20)" timestamp updated_at "NOT NULL" } receipt_acts { uuid id PK uuid franchise_id "NOT NULL" uuid warehouse_id FK "NOT NULL → warehouses.id" varchar document_number "NOT NULL" timestamp receipt_date "NOT NULL" text comment "NULL" varchar status "NOT NULL, default draft, (20)" decimal total_amount "NOT NULL, default 0, (12,2)" uuid created_by "NOT NULL" timestamp created_at "NOT NULL" timestamp updated_at "NOT NULL" } receipt_act_lines { uuid id PK uuid receipt_act_id FK "NOT NULL → receipt_acts.id CASCADE" uuid ingredient_id FK "NOT NULL → ingredients.id" decimal quantity "NOT NULL, (12,3)" varchar unit_of_measure "NOT NULL, (20)" decimal unit_price "NOT NULL, (10,4)" decimal line_total "NOT NULL, (12,2)" date shelf_life_date "NULL" } write_off_acts { uuid id PK uuid franchise_id "NOT NULL" uuid warehouse_id FK "NOT NULL → warehouses.id" varchar document_number "NOT NULL" timestamp write_off_date "NOT NULL" varchar reason "NOT NULL, (255)" varchar status "NOT NULL, default draft, (20)" decimal total_cost "NOT NULL, default 0, (12,2)" uuid created_by "NOT NULL" timestamp created_at "NOT NULL" timestamp updated_at "NOT NULL" } write_off_act_lines { uuid id PK uuid write_off_act_id FK "NOT NULL → write_off_acts.id CASCADE" uuid ingredient_id FK "NOT NULL → ingredients.id" decimal quantity "NOT NULL, (12,3)" decimal unit_cost "NOT NULL, (10,4)" decimal line_cost "NOT NULL, (12,2)" } warehouses ||--o{ stock_batches : "stores" warehouses ||--o{ stock_balances : "tracks" warehouses ||--o{ receipt_acts : "receives" warehouses ||--o{ write_off_acts : "writes off" receipt_acts ||--o{ receipt_act_lines : "contains" write_off_acts ||--o{ write_off_act_lines : "contains"
Таблицы
tech_cards
(BR 1.9)
Техкарты (рецептуры). Привязаны к товару из Catalog Service или к ингредиенту (полуфабрикат). (Обновлено в BUG-010)
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
franchise_id | uuid | NOT NULL | — | ID франшизы (из JWT) |
product_id | uuid | NULL | — | Товар из Catalog Service (для dish-техкарт) |
ingredient_id | uuid | NULL | — | FK → ingredients.id (для полуфабрикатов) |
modifier_option_id | uuid | NULL | — | Опция модификатора (per-size). NULL = базовая техкарта |
name | varchar(255) | NOT NULL | — | Название техкарты |
output_weight | decimal(10,3) | NOT NULL | — | Выход готового блюда (вес/объём) |
output_unit | varchar(20) | NOT NULL | — | Единица выхода (г, кг, мл, л, порция) |
cooking_description | text | NULL | — | Технология приготовления |
status | varchar(20) | NOT NULL | 'active' | active / inactive |
created_at | timestamp | NOT NULL | now() | |
updated_at | timestamp | NOT NULL | now() |
Ограничения:
CHECK (product_id IS NOT NULL OR ingredient_id IS NOT NULL)— ровно одно из двух (BUG-010)CHECK (product_id IS NULL OR ingredient_id IS NULL)— взаимоисключающиеUNIQUE (product_id, modifier_option_id) WHERE product_id IS NOT NULL— одна техкарта на товар + модификатор (partial unique index)UNIQUE (ingredient_id) WHERE ingredient_id IS NOT NULL— одна техкарта на ингредиент-полуфабрикатFK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT- Кросс-сервисные ссылки:
product_idиmodifier_option_id→ Catalog Service (не FK, lookup по API)
Индексы:
idx_tech_cards_franchise_id—franchise_ididx_tech_cards_product_id—product_id
ingredients
(BR 1.11)
Справочник ингредиентов франшизы. Сырьё для техкарт.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
franchise_id | uuid | NOT NULL | — | ID франшизы |
name | varchar(255) | NOT NULL | — | Название ингредиента |
description | text | NULL | — | Описание |
unit_of_measure | varchar(20) | NOT NULL | — | г, кг, мл, л, шт |
status | varchar(20) | NOT NULL | 'active' | active / inactive |
created_at | timestamp | NOT NULL | now() | |
updated_at | timestamp | NOT NULL | now() |
Ограничения:
UNIQUE (franchise_id, name)— уникальное название per franchise
Индексы:
idx_ingredients_franchise_id—franchise_id
recipe_items
Строки рецепта — ингредиенты техкарты.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
tech_card_id | uuid | NOT NULL | — | FK → tech_cards.id |
ingredient_id | uuid | NOT NULL | — | FK → ingredients.id (BUG-010: всегда NOT NULL, полуфабрикаты тоже ингредиенты) |
gross_weight | decimal(10,3) | NOT NULL | — | Масса брутто (до обработки) |
net_weight | decimal(10,3) | NOT NULL | — | Масса нетто (после обработки) |
cold_loss_percent | decimal(5,2) | NULL | — | % потерь при холодной обработке |
hot_loss_percent | decimal(5,2) | NULL | — | % потерь при горячей обработке |
unit_of_measure | varchar(20) | NOT NULL | — | г, кг, мл, л, шт |
sort_order | integer | NOT NULL | 0 | Порядок в рецепте |
created_at | timestamp | NOT NULL | now() |
Ограничения:
FK tech_card_id REFERENCES tech_cards(id) ON DELETE CASCADEFK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT
Индексы:
idx_recipe_items_tech_card_id—tech_card_id
unit_conversions
Справочник конвертации единиц измерения per-product.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
franchise_id | uuid | NOT NULL | — | ID франшизы |
product_id | uuid | NULL | — | Продукт из Catalog Service |
ingredient_id | uuid | NULL | — | FK → ingredients.id |
from_unit | varchar(20) | NOT NULL | — | Исходная единица (“упаковка”, “бутылка”) |
to_unit | varchar(20) | NOT NULL | — | Целевая единица (“г”, “мл”) |
factor | decimal(10,4) | NOT NULL | — | Коэффициент: 1 from_unit = factor × to_unit |
created_at | timestamp | NOT NULL | now() | |
updated_at | timestamp | NOT NULL | now() |
Ограничения:
UNIQUE (franchise_id, product_id, ingredient_id, from_unit, to_unit)CHECK (product_id IS NOT NULL AND ingredient_id IS NULL) OR (product_id IS NULL AND ingredient_id IS NOT NULL)— ровно одно из двухFK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT
Индексы:
idx_unit_conv_product—(franchise_id, product_id)idx_unit_conv_ingredient—(franchise_id, ingredient_id)
modifier_tech_cards
(BR 1.9.1)
Техкарты опций модификаторов (добавки). Привязаны к опции + версии группы.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
franchise_id | uuid | NOT NULL | — | ID франшизы |
modifier_option_id | uuid | NOT NULL | — | Опция модификатора (→ Catalog Service) |
output_weight | decimal(10,3) | NOT NULL | — | Выход |
output_unit | varchar(20) | NOT NULL | — | Единица выхода |
cooking_description | text | NULL | — | Технология |
status | varchar(20) | NOT NULL | 'active' | active / inactive |
created_at | timestamp | NOT NULL | now() | |
updated_at | timestamp | NOT NULL | now() |
Ограничения:
UNIQUE (modifier_option_id)— одна техкарта на опцию
Индексы:
idx_mtc_modifier_option—modifier_option_id
modifier_tech_card_items
(BR 1.9.1)
Строки рецепта техкарт модификаторов. Аналогично recipe_items.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
modifier_tech_card_id | uuid | NOT NULL | — | FK → modifier_tech_cards.id |
ingredient_id | uuid | NOT NULL | — | FK → ingredients.id |
gross_weight | decimal(10,3) | NOT NULL | — | Масса брутто |
net_weight | decimal(10,3) | NOT NULL | — | Масса нетто |
cold_loss_percent | decimal(5,2) | NULL | — | % потерь хол. |
hot_loss_percent | decimal(5,2) | NULL | — | % потерь гор. |
unit_of_measure | varchar(20) | NOT NULL | — | Единица |
sort_order | integer | NOT NULL | 0 | Порядок |
created_at | timestamp | NOT NULL | now() |
Ограничения:
FK modifier_tech_card_id REFERENCES modifier_tech_cards(id) ON DELETE CASCADEFK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT
warehouses
(BR 1.14)
Склады. Один склад на торговую точку, создаётся автоматически при создании ТТ.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
franchise_id | uuid | NOT NULL | — | ID франшизы |
store_id | uuid | NOT NULL | — | Торговая точка (→ Store Service, кросс-сервисная ссылка) |
name | varchar(255) | NOT NULL | — | Название склада (= название ТТ) |
status | varchar(20) | NOT NULL | 'active' | active / inactive |
created_at | timestamp | NOT NULL | now() |
Ограничения:
UNIQUE (franchise_id, store_id)— один склад на ТТ per franchise
Индексы:
idx_warehouses_franchise_id—franchise_id
stock_batches
(BR 1.14)
Складские партии. Создаются при проводке акта приёмки. Используются для FIFO-списания и расчёта средневзвешенной цены.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
warehouse_id | uuid | NOT NULL | — | FK → warehouses.id |
ingredient_id | uuid | NOT NULL | — | FK → ingredients.id |
purchase_price | decimal(10,4) | NOT NULL | — | Закупочная цена за единицу |
quantity | decimal(12,3) | NOT NULL | — | Текущий остаток партии |
unit_of_measure | varchar(20) | NOT NULL | — | Единица измерения |
received_date | timestamp | NOT NULL | — | Дата поступления |
shelf_life_date | date | NULL | — | Срок годности |
status | varchar(20) | NOT NULL | 'active' | active / exhausted |
created_at | timestamp | NOT NULL | now() |
Ограничения:
FK warehouse_id REFERENCES warehouses(id) ON DELETE RESTRICTFK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT
Индексы:
idx_stock_batches_warehouse_ingredient—(warehouse_id, ingredient_id)
stock_balances
(BR 1.14)
Текущие складские остатки. Per-warehouse per-ingredient. Обновляются при проводке документов.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
warehouse_id | uuid | NOT NULL | — | FK → warehouses.id |
ingredient_id | uuid | NOT NULL | — | FK → ingredients.id |
current_quantity | decimal(12,3) | NOT NULL | 0 | Текущий остаток |
average_cost | decimal(10,4) | NULL | — | Средневзвешенная закупочная цена |
unit_of_measure | varchar(20) | NOT NULL | — | Единица измерения |
updated_at | timestamp | NOT NULL | now() | Время последнего обновления |
Ограничения:
UNIQUE (warehouse_id, ingredient_id)— один баланс на ингредиент per складFK warehouse_id REFERENCES warehouses(id) ON DELETE RESTRICTFK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT
receipt_acts
(BR 1.14)
Акты приёмки (поступление ингредиентов на склад). Жизненный цикл: draft → posted.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
franchise_id | uuid | NOT NULL | — | ID франшизы |
warehouse_id | uuid | NOT NULL | — | FK → warehouses.id |
document_number | varchar | NOT NULL | — | Номер документа (автоинкремент per franchise) |
receipt_date | timestamp | NOT NULL | — | Дата приёмки |
comment | text | NULL | — | Комментарий |
status | varchar(20) | NOT NULL | 'draft' | draft / posted |
total_amount | decimal(12,2) | NOT NULL | 0 | Общая сумма (вычисляется из строк) |
created_by | uuid | NOT NULL | — | Кто создал (user_id из JWT) |
created_at | timestamp | NOT NULL | now() | |
updated_at | timestamp | NOT NULL | now() |
Ограничения:
FK warehouse_id REFERENCES warehouses(id) ON DELETE RESTRICT
Индексы:
idx_receipt_acts_franchise_status—(franchise_id, status)
receipt_act_lines
(BR 1.14)
Строки акта приёмки.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
receipt_act_id | uuid | NOT NULL | — | FK → receipt_acts.id |
ingredient_id | uuid | NOT NULL | — | FK → ingredients.id |
quantity | decimal(12,3) | NOT NULL | — | Количество |
unit_of_measure | varchar(20) | NOT NULL | — | Единица измерения |
unit_price | decimal(10,4) | NOT NULL | — | Цена за единицу |
line_total | decimal(12,2) | NOT NULL | — | Сумма строки (quantity * unit_price) |
shelf_life_date | date | NULL | — | Срок годности |
Ограничения:
FK receipt_act_id REFERENCES receipt_acts(id) ON DELETE CASCADEFK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT
write_off_acts
(BR 1.14)
Акты списания. Жизненный цикл: draft → posted. FIFO-списание с партий.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
franchise_id | uuid | NOT NULL | — | ID франшизы |
warehouse_id | uuid | NOT NULL | — | FK → warehouses.id |
document_number | varchar | NOT NULL | — | Номер документа |
write_off_date | timestamp | NOT NULL | — | Дата списания |
reason | varchar(255) | NOT NULL | — | Причина списания (обязательно) |
status | varchar(20) | NOT NULL | 'draft' | draft / posted |
total_cost | decimal(12,2) | NOT NULL | 0 | Общая стоимость списания |
created_by | uuid | NOT NULL | — | Кто создал |
created_at | timestamp | NOT NULL | now() | |
updated_at | timestamp | NOT NULL | now() |
Ограничения:
FK warehouse_id REFERENCES warehouses(id) ON DELETE RESTRICT
Индексы:
idx_write_off_acts_franchise_status—(franchise_id, status)
write_off_act_lines
(BR 1.14)
Строки акта списания.
| Колонка | Тип | Nullable | Default | Описание |
|---|---|---|---|---|
id | uuid | NOT NULL | gen_random_uuid() | PK |
write_off_act_id | uuid | NOT NULL | — | FK → write_off_acts.id |
ingredient_id | uuid | NOT NULL | — | FK → ingredients.id |
quantity | decimal(12,3) | NOT NULL | — | Количество к списанию |
unit_cost | decimal(10,4) | NOT NULL | — | Себестоимость единицы (из средневзвешенной) |
line_cost | decimal(12,2) | NOT NULL | — | Стоимость строки (quantity * unit_cost) |
Ограничения:
FK write_off_act_id REFERENCES write_off_acts(id) ON DELETE CASCADEFK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT
Кросс-сервисные ссылки
Warehouse Service хранит UUID продуктов и модификаторов из Catalog Service. Это не FK в БД — lookup через HTTP API:
| Поле | Источник | API |
|---|---|---|
tech_cards.product_id | Catalog Service → products.id | GET /api/v1/products/{id} |
tech_cards.modifier_option_id | Catalog Service → modifier_options.id | GET /api/v1/modifier-groups/{id}/versions |
warehouses.store_id | Store Service → stores.id | GET /api/v1/stores/{id} |
Локальные FK (BR 1.11, BUG-010)
tech_cards.ingredient_id→ingredients.id(локальная FK, для полуфабрикатов)recipe_items.ingredient_id→ingredients.id(локальная FK)modifier_tech_card_items.ingredient_id→ingredients.id(локальная FK)unit_conversions.ingredient_id→ingredients.id(локальная FK)