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)

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
franchise_iduuidNOT NULLID франшизы (из JWT)
product_iduuidNULLТовар из Catalog Service (для dish-техкарт)
ingredient_iduuidNULLFK → ingredients.id (для полуфабрикатов)
modifier_option_iduuidNULLОпция модификатора (per-size). NULL = базовая техкарта
namevarchar(255)NOT NULLНазвание техкарты
output_weightdecimal(10,3)NOT NULLВыход готового блюда (вес/объём)
output_unitvarchar(20)NOT NULLЕдиница выхода (г, кг, мл, л, порция)
cooking_descriptiontextNULLТехнология приготовления
statusvarchar(20)NOT NULL'active'active / inactive
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()

Ограничения:

  • 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_idfranchise_id
  • idx_tech_cards_product_idproduct_id

ingredients

(BR 1.11)

Справочник ингредиентов франшизы. Сырьё для техкарт.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
franchise_iduuidNOT NULLID франшизы
namevarchar(255)NOT NULLНазвание ингредиента
descriptiontextNULLОписание
unit_of_measurevarchar(20)NOT NULLг, кг, мл, л, шт
statusvarchar(20)NOT NULL'active'active / inactive
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()

Ограничения:

  • UNIQUE (franchise_id, name) — уникальное название per franchise

Индексы:

  • idx_ingredients_franchise_idfranchise_id

recipe_items

Строки рецепта — ингредиенты техкарты.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
tech_card_iduuidNOT NULLFK → tech_cards.id
ingredient_iduuidNOT NULLFK → ingredients.id (BUG-010: всегда NOT NULL, полуфабрикаты тоже ингредиенты)
gross_weightdecimal(10,3)NOT NULLМасса брутто (до обработки)
net_weightdecimal(10,3)NOT NULLМасса нетто (после обработки)
cold_loss_percentdecimal(5,2)NULL% потерь при холодной обработке
hot_loss_percentdecimal(5,2)NULL% потерь при горячей обработке
unit_of_measurevarchar(20)NOT NULLг, кг, мл, л, шт
sort_orderintegerNOT NULL0Порядок в рецепте
created_attimestampNOT NULLnow()

Ограничения:

  • FK tech_card_id REFERENCES tech_cards(id) ON DELETE CASCADE
  • FK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT

Индексы:

  • idx_recipe_items_tech_card_idtech_card_id

unit_conversions

Справочник конвертации единиц измерения per-product.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
franchise_iduuidNOT NULLID франшизы
product_iduuidNULLПродукт из Catalog Service
ingredient_iduuidNULLFK → ingredients.id
from_unitvarchar(20)NOT NULLИсходная единица (“упаковка”, “бутылка”)
to_unitvarchar(20)NOT NULLЦелевая единица (“г”, “мл”)
factordecimal(10,4)NOT NULLКоэффициент: 1 from_unit = factor × to_unit
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()

Ограничения:

  • 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)

Техкарты опций модификаторов (добавки). Привязаны к опции + версии группы.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
franchise_iduuidNOT NULLID франшизы
modifier_option_iduuidNOT NULLОпция модификатора (→ Catalog Service)
output_weightdecimal(10,3)NOT NULLВыход
output_unitvarchar(20)NOT NULLЕдиница выхода
cooking_descriptiontextNULLТехнология
statusvarchar(20)NOT NULL'active'active / inactive
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()

Ограничения:

  • UNIQUE (modifier_option_id) — одна техкарта на опцию

Индексы:

  • idx_mtc_modifier_optionmodifier_option_id

modifier_tech_card_items

(BR 1.9.1)

Строки рецепта техкарт модификаторов. Аналогично recipe_items.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
modifier_tech_card_iduuidNOT NULLFK → modifier_tech_cards.id
ingredient_iduuidNOT NULLFK → ingredients.id
gross_weightdecimal(10,3)NOT NULLМасса брутто
net_weightdecimal(10,3)NOT NULLМасса нетто
cold_loss_percentdecimal(5,2)NULL% потерь хол.
hot_loss_percentdecimal(5,2)NULL% потерь гор.
unit_of_measurevarchar(20)NOT NULLЕдиница
sort_orderintegerNOT NULL0Порядок
created_attimestampNOT NULLnow()

Ограничения:

  • FK modifier_tech_card_id REFERENCES modifier_tech_cards(id) ON DELETE CASCADE
  • FK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT

warehouses

(BR 1.14)

Склады. Один склад на торговую точку, создаётся автоматически при создании ТТ.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
franchise_iduuidNOT NULLID франшизы
store_iduuidNOT NULLТорговая точка (→ Store Service, кросс-сервисная ссылка)
namevarchar(255)NOT NULLНазвание склада (= название ТТ)
statusvarchar(20)NOT NULL'active'active / inactive
created_attimestampNOT NULLnow()

Ограничения:

  • UNIQUE (franchise_id, store_id) — один склад на ТТ per franchise

Индексы:

  • idx_warehouses_franchise_idfranchise_id

stock_batches

(BR 1.14)

Складские партии. Создаются при проводке акта приёмки. Используются для FIFO-списания и расчёта средневзвешенной цены.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
warehouse_iduuidNOT NULLFK → warehouses.id
ingredient_iduuidNOT NULLFK → ingredients.id
purchase_pricedecimal(10,4)NOT NULLЗакупочная цена за единицу
quantitydecimal(12,3)NOT NULLТекущий остаток партии
unit_of_measurevarchar(20)NOT NULLЕдиница измерения
received_datetimestampNOT NULLДата поступления
shelf_life_datedateNULLСрок годности
statusvarchar(20)NOT NULL'active'active / exhausted
created_attimestampNOT NULLnow()

Ограничения:

  • FK warehouse_id REFERENCES warehouses(id) ON DELETE RESTRICT
  • FK 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. Обновляются при проводке документов.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
warehouse_iduuidNOT NULLFK → warehouses.id
ingredient_iduuidNOT NULLFK → ingredients.id
current_quantitydecimal(12,3)NOT NULL0Текущий остаток
average_costdecimal(10,4)NULLСредневзвешенная закупочная цена
unit_of_measurevarchar(20)NOT NULLЕдиница измерения
updated_attimestampNOT NULLnow()Время последнего обновления

Ограничения:

  • UNIQUE (warehouse_id, ingredient_id) — один баланс на ингредиент per склад
  • FK warehouse_id REFERENCES warehouses(id) ON DELETE RESTRICT
  • FK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT

receipt_acts

(BR 1.14)

Акты приёмки (поступление ингредиентов на склад). Жизненный цикл: draft → posted.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
franchise_iduuidNOT NULLID франшизы
warehouse_iduuidNOT NULLFK → warehouses.id
document_numbervarcharNOT NULLНомер документа (автоинкремент per franchise)
receipt_datetimestampNOT NULLДата приёмки
commenttextNULLКомментарий
statusvarchar(20)NOT NULL'draft'draft / posted
total_amountdecimal(12,2)NOT NULL0Общая сумма (вычисляется из строк)
created_byuuidNOT NULLКто создал (user_id из JWT)
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()

Ограничения:

  • FK warehouse_id REFERENCES warehouses(id) ON DELETE RESTRICT

Индексы:

  • idx_receipt_acts_franchise_status(franchise_id, status)

receipt_act_lines

(BR 1.14)

Строки акта приёмки.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
receipt_act_iduuidNOT NULLFK → receipt_acts.id
ingredient_iduuidNOT NULLFK → ingredients.id
quantitydecimal(12,3)NOT NULLКоличество
unit_of_measurevarchar(20)NOT NULLЕдиница измерения
unit_pricedecimal(10,4)NOT NULLЦена за единицу
line_totaldecimal(12,2)NOT NULLСумма строки (quantity * unit_price)
shelf_life_datedateNULLСрок годности

Ограничения:

  • FK receipt_act_id REFERENCES receipt_acts(id) ON DELETE CASCADE
  • FK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT

write_off_acts

(BR 1.14)

Акты списания. Жизненный цикл: draft → posted. FIFO-списание с партий.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
franchise_iduuidNOT NULLID франшизы
warehouse_iduuidNOT NULLFK → warehouses.id
document_numbervarcharNOT NULLНомер документа
write_off_datetimestampNOT NULLДата списания
reasonvarchar(255)NOT NULLПричина списания (обязательно)
statusvarchar(20)NOT NULL'draft'draft / posted
total_costdecimal(12,2)NOT NULL0Общая стоимость списания
created_byuuidNOT NULLКто создал
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()

Ограничения:

  • 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)

Строки акта списания.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
write_off_act_iduuidNOT NULLFK → write_off_acts.id
ingredient_iduuidNOT NULLFK → ingredients.id
quantitydecimal(12,3)NOT NULLКоличество к списанию
unit_costdecimal(10,4)NOT NULLСебестоимость единицы (из средневзвешенной)
line_costdecimal(12,2)NOT NULLСтоимость строки (quantity * unit_cost)

Ограничения:

  • FK write_off_act_id REFERENCES write_off_acts(id) ON DELETE CASCADE
  • FK ingredient_id REFERENCES ingredients(id) ON DELETE RESTRICT

Кросс-сервисные ссылки

Warehouse Service хранит UUID продуктов и модификаторов из Catalog Service. Это не FK в БД — lookup через HTTP API:

ПолеИсточникAPI
tech_cards.product_idCatalog Service → products.idGET /api/v1/products/{id}
tech_cards.modifier_option_idCatalog Service → modifier_options.idGET /api/v1/modifier-groups/{id}/versions
warehouses.store_idStore Service → stores.idGET /api/v1/stores/{id}

Локальные FK (BR 1.11, BUG-010)

  • tech_cards.ingredient_idingredients.id (локальная FK, для полуфабрикатов)
  • recipe_items.ingredient_idingredients.id (локальная FK)
  • modifier_tech_card_items.ingredient_idingredients.id (локальная FK)
  • unit_conversions.ingredient_idingredients.id (локальная FK)