Склад (Phase 1)

Источник требований

Складской учёт Phase 1: склады per-ТТ, складские остатки, партии с закупочными ценами, акты приёмки (приход ингредиентов), акты списания (порча, недостача). Средневзвешенная цена из партий используется для расчёта себестоимости в техкартах.

Сервис: Warehouse Service (:3008).

Фронт: не входит в Phase 1 (только бэкенд). Фронтенд складского учёта — отдельная BR.


Склады (warehouses)

Один склад на торговую точку. Создаётся автоматически при создании ТТ (через событие store.created из Store Service).

Поля

ПолеОбязательностьОписание
ФраншизаОбязательноfranchise_id (из JWT)
Торговая точкаОбязательноstore_id (кросс-сервисная ссылка → Store Service)
НазваниеОбязательноСовпадает с названием ТТ
СтатусОбязательноactive / inactive

Правила

  • Нельзя создать склад вручную — только автоматически при создании ТТ
  • Нельзя удалить склад — привязан к жизненному циклу ТТ
  • Один склад на одну ТТ (UNIQUE franchise_id + store_id)
  • Название склада = название торговой точки

Складские остатки (stock balances)

Текущее количество каждого ингредиента на каждом складе. Обновляется автоматически при проводке документов (приёмка +, списание -).

Поля

ПолеОбязательностьОписание
СкладОбязательноwarehouse_id (FK)
ИнгредиентОбязательноingredient_id (FK → ingredients)
Текущее количествоОбязательноОбновляется при проводке. Не может быть отрицательным
Средняя ценаНеобязательноСредневзвешенная закупочная цена (пересчитывается при приёмке/списании)
Единица измеренияОбязательноДолжна совпадать с единицей ингредиента
Дата обновленияОбязательноВремя последнего изменения

Правила

  • Уникальная пара (warehouse_id, ingredient_id)
  • Остатки не могут быть отрицательными — валидация при списании
  • Создаются/обновляются автоматически при проводке документов

Складские партии (stock batches)

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

Поля

ПолеОбязательностьОписание
СкладОбязательноwarehouse_id (FK)
ИнгредиентОбязательноingredient_id (FK → ingredients)
Закупочная ценаОбязательноЦена за единицу из акта приёмки
КоличествоОбязательноТекущий остаток партии (уменьшается при списании)
Единица измеренияОбязательноСовпадает с единицей ингредиента
Дата поступленияОбязательноКогда партия поступила на склад
Срок годностиНеобязательноЕсли указан в акте приёмки
СтатусОбязательноactive / exhausted (исчерпана)

Правила

  • При списании по FIFO — сначала расходуются самые старые партии (по received_date)
  • Когда количество партии достигает 0, статус = exhausted
  • Средневзвешенная цена = SUM(purchase_price * quantity) / SUM(quantity) по всем партиям с qty > 0

Акты приёмки (receipt acts)

Документ поступления ингредиентов на склад. Без поставщика (Phase 1 без справочника поставщиков).

Жизненный цикл

stateDiagram-v2
    [*] --> draft : Создание
    draft --> posted : Проводка
    posted --> [*]

    style draft fill:#1a1a2e,stroke:#e94560,color:#fff
    style posted fill:#2d6a4f,stroke:#40916c,color:#fff
  • draft — можно редактировать (добавлять/удалять строки, менять количества, даты, комментарий)
  • posted — зафиксирован, создаёт партии, обновляет остатки. Нельзя редактировать

Поля документа

ПолеОбязательностьОписание
СкладОбязательноwarehouse_id (FK)
Номер документаОбязательноАвтоинкремент per franchise
Дата приёмкиОбязательноКогда приняли товар
КомментарийНеобязательноПроизвольный текст
СтатусОбязательноdraft / posted
Общая суммаВычисляемоеСумма всех строк
СоздалОбязательноcreated_by (user_id из JWT)

Строки документа

ПолеОбязательностьОписание
ИнгредиентОбязательноingredient_id (FK → ingredients)
КоличествоОбязательноСколько поступило
Единица измеренияОбязательноДолжна совпадать с единицей ингредиента
Цена за единицуОбязательноЗакупочная цена
Сумма строкиВычисляемоеquantity * unit_price
Срок годностиНеобязательноДата окончания срока

При проводке (post)

  1. Для каждой строки создать stock_batch (партия с закупочной ценой)
  2. Обновить stock_balance (+quantity)
  3. Пересчитать average_cost ингредиента на этом складе
  4. Заблокировать редактирование документа (status = posted)

Валидации

  • Нельзя провести пустой документ (без строк) — EMPTY_DOCUMENT
  • Нельзя провести уже проведённый документ — DOCUMENT_ALREADY_POSTED
  • Нельзя редактировать проведённый документ — DOCUMENT_ALREADY_POSTED

Акты списания (write-off acts)

Документ списания ингредиентов со склада (порча, истечение срока, недостача).

Жизненный цикл

Аналогичен актам приёмки: draft → posted.

Поля документа

ПолеОбязательностьОписание
СкладОбязательноwarehouse_id (FK)
Номер документаОбязательноАвтоинкремент per franchise
Дата списанияОбязательноКогда списали
ПричинаОбязательноТекст: порча, истечение срока, недостача и т.д.
СтатусОбязательноdraft / posted
Общая стоимостьВычисляемоеСебестоимость списанных ингредиентов
СоздалОбязательноcreated_by (user_id из JWT)

Строки документа

ПолеОбязательностьОписание
ИнгредиентОбязательноingredient_id (FK → ingredients)
КоличествоОбязательноСколько списать
Себестоимость единицыВычисляемоеИз средневзвешенной цены
Стоимость строкиВычисляемоеquantity * unit_cost

При проводке (post)

  1. Проверить достаточность остатков на складе
  2. Списать по FIFO (с самых старых партий)
  3. Обновить stock_balance (-quantity)
  4. Зафиксировать стоимость списания

Валидации

  • Нельзя списать больше чем есть на складе — INSUFFICIENT_STOCK
  • Нельзя провести пустой документ — EMPTY_DOCUMENT
  • Нельзя провести уже проведённый — DOCUMENT_ALREADY_POSTED

Себестоимость (средневзвешенная цена)

Per-ingredient per-warehouse. Рассчитывается из складских партий.

average_cost = SUM(batch.purchase_price * batch.quantity) / SUM(batch.quantity)

где batch.quantity > 0 (только непустые партии).

Пересчитывается при каждой приёмке и списании. Используется в техкартах для расчёта себестоимости блюд (CostCalculationService). (BR 1.14)

До BR 1.14

Средняя цена хранилась как статичное значение. Теперь рассчитывается динамически из реальных складских партий.


Ролевая матрица

Франшиза (владелец бренда)

  • Просмотр остатков всех складов
  • Создание/проводка приёмок на любом складе
  • Создание/проводка списаний на любом складе
  • Просмотр средней цены

Франчайзи (партнёр)

  • Просмотр остатков своих складов (по store_ids из JWT)
  • Создание/проводка приёмок на своих складах
  • Создание/проводка списаний на своих складах
  • Просмотр средней цены своих складов

Менеджер ТТ

  • Просмотр остатков своего склада
  • Создание/проводка приёмок на своём складе
  • Создание/проводка списаний на своём складе
  • Просмотр средней цены своего склада

Кассир

  • Нет доступа к складскому учёту

Бизнес-правила

  1. Склад = ТТ — один склад на торговую точку, автоматическое создание
  2. Draft Posted — документ проводится один раз, после этого неизменяем
  3. FIFO — при списании сначала расходуются самые старые партии
  4. Неотрицательные остатки — нельзя списать больше чем есть на складе
  5. Средневзвешенная — цена считается по партиям с положительным количеством
  6. Единица измерения — при приёмке/списании должна совпадать с единицей ингредиента (или конвертация через unit_conversions)

Что НЕ входит (Phase 2+)

  • Поставщики (справочник контрагентов)
  • Акты перемещения между складами
  • Инвентаризация (сверка факт vs учёт)
  • Акты производства (списание по техкарте при изготовлении)
  • Автоматическое списание при продаже (интеграция с Order Service)
  • Авто-стоп по остаткам (интеграция со стоп-листами)
  • Фронт-интерфейс складского учёта
  • Партионный учёт по срокам годности (алерты)
  • Возвраты поставщику

Связи с другими модулями

  • Техкарты — себестоимость рассчитывается из средневзвешенной цены складских партий
  • Store Service — при создании ТТ автоматически создаётся склад
  • Стоп-листы — Phase 2: авто-стоп по остаткам

Ссылки