Warehouse Service — API Contract
Этот документ является единственным источником правды для API Warehouse Service.
(Добавлено в BR 1.9 )
Содержание
Техкарты
Строки рецепта
Себестоимость
Техкарты модификаторов (BR 1.9.1)
Конвертация единиц
Ингредиенты (BR 1.11)
Склады (BR 1.14)
Складские остатки (BR 1.14)
Акты приёмки (BR 1.14)
Акты списания (BR 1.14)
Средняя цена (BR 1.14)
GET /tech-cards
Список техкарт с фильтрацией по товару.
Параметр Значение Auth Bearer JWT (Franchise — все; Franchisee/Manager — активные товары)
Query Parameters
Param Type Required Description product_iduuid no Фильтр по товару ingredient_iduuid no Фильтр по ингредиенту (полуфабрикат) (BUG-010) statusstring no active / inactivepageinteger no default: 1 per_pageinteger no default: 20, max: 100
Response 200
{
"data" : [
{
"id" : "uuid" ,
"product_id" : "uuid | null" ,
"ingredient_id" : "uuid | null" ,
"modifier_option_id" : "uuid | null" ,
"modifier_option_name" : "string | null" ,
"name" : "string" ,
"output_weight" : "decimal" ,
"output_unit" : "string" ,
"status" : "active | inactive" ,
"item_count" : "integer" ,
"created_at" : "datetime"
}
],
"meta" : {
"page" : "integer" ,
"per_page" : "integer" ,
"total" : "integer"
}
}
GET /tech-cards/{id}
Детали техкарты с полным списком ингредиентов.
Параметр Значение Auth Bearer JWT (Franchise — любая; Franchisee/Manager — активные товары)
Response 200
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"product_id" : "uuid | null" ,
"product_name" : "string | null" ,
"ingredient_id" : "uuid | null" ,
"ingredient_name" : "string | null" ,
"modifier_option_id" : "uuid | null" ,
"modifier_option_name" : "string | null" ,
"name" : "string" ,
"output_weight" : "decimal" ,
"output_unit" : "string" ,
"cooking_description" : "string | null" ,
"status" : "active | inactive" ,
"items" : [
{
"id" : "uuid" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"has_tech_card" : "boolean" ,
"gross_weight" : "decimal" ,
"net_weight" : "decimal" ,
"cold_loss_percent" : "decimal | null" ,
"hot_loss_percent" : "decimal | null" ,
"unit_of_measure" : "string" ,
"sort_order" : "integer"
}
],
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 TECH_CARD_NOT_FOUND404 TECH_CARD_NOT_FOUND404 Техкарта не найдена
POST /tech-cards
Создать техкарту для товара.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"product_id" : "uuid, optional — товар из Catalog (для dish-техкарт)" ,
"ingredient_id" : "uuid, optional — ингредиент из Warehouse (для полуфабрикатов) (BUG-010)" ,
"modifier_option_id" : "uuid, optional — для per-size" ,
"name" : "string, required" ,
"output_weight" : "decimal, required — выход готового" ,
"output_unit" : "string, required — г | кг | мл | л | порция" ,
"cooking_description" : "string, optional"
}
Ровно одно из product_id / ingredient_id должно быть указано (BUG-010)
Response 201
Аналогично GET /tech-cards/{id} response (без items — пустая техкарта).
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Не Franchise VALIDATION_ERROR400 Невалидные данные PRODUCT_NOT_FOUND404 Товар не найден в Catalog Service PRODUCT_NOT_DISH422 Товар не типа dish TECH_CARD_ALREADY_EXISTS409 Техкарта для этого товара + модификатора уже существует
PATCH /tech-cards/{id}
Обновить техкарту (поля, не ингредиенты).
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"name" : "string, optional" ,
"output_weight" : "decimal, optional" ,
"output_unit" : "string, optional" ,
"cooking_description" : "string | null, optional" ,
"status" : "string, optional — active | inactive"
}
Response 200
Аналогично GET /tech-cards/{id} response.
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 TECH_CARD_NOT_FOUND404 TECH_CARD_NOT_FOUND404 Техкарта не найдена VALIDATION_ERROR400
DELETE /tech-cards/{id}
Удалить техкарту.
Параметр Значение Auth Bearer JWT (только Franchise)
Response 204
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 TECH_CARD_NOT_FOUND404 TECH_CARD_NOT_FOUND404 Техкарта не найдена
POST /tech-cards/{id}/items
Добавить ингредиент в техкарту.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"ingredient_id" : "uuid, required — FK → ingredients.id" ,
"gross_weight" : "decimal, required" ,
"net_weight" : "decimal, required" ,
"cold_loss_percent" : "decimal, optional" ,
"hot_loss_percent" : "decimal, optional" ,
"unit_of_measure" : "string, required — г | кг | мл | л | шт" ,
"sort_order" : "integer, optional — default 0"
}
### Response 201
```json
{
"data" : {
"id" : "uuid" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"has_tech_card" : "boolean" ,
"gross_weight" : "decimal" ,
"net_weight" : "decimal" ,
"cold_loss_percent" : "decimal | null" ,
"hot_loss_percent" : "decimal | null" ,
"unit_of_measure" : "string" ,
"sort_order" : "integer"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 TECH_CARD_NOT_FOUND404 TECH_CARD_NOT_FOUND404 Техкарта не найдена INGREDIENT_NOT_FOUND404 Ингредиент не найден VALIDATION_ERROR400 net_weight > gross_weight, отрицательные значения CIRCULAR_REFERENCE422 Ингредиент-полуфабрикат создаёт циклическую ссылку
PATCH /tech-cards/{id}/items/{itemId}
Обновить строку рецепта.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"gross_weight" : "decimal, optional" ,
"net_weight" : "decimal, optional" ,
"cold_loss_percent" : "decimal, optional" ,
"hot_loss_percent" : "decimal, optional" ,
"unit_of_measure" : "string, optional" ,
"sort_order" : "integer, optional"
}
Response 200
Аналогично POST /tech-cards/{id}/items response.
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 TECH_CARD_NOT_FOUND404 TECH_CARD_NOT_FOUND404 Техкарта не найдена RECIPE_ITEM_NOT_FOUND404 VALIDATION_ERROR400
DELETE /tech-cards/{id}/items/{itemId}
Удалить строку рецепта.
Параметр Значение Auth Bearer JWT (только Franchise)
Response 204
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 RECIPE_ITEM_NOT_FOUND404
GET /tech-cards/{id}/cost
Расчёт себестоимости техкарты. Разворачивает ингредиенты-полуфабрикаты (имеющие свои техкарты) до сырья.
Параметр Значение Auth Bearer JWT (Franchise, Franchisee, Manager)
Response 200
{
"data" : {
"tech_card_id" : "uuid" ,
"total_cost" : "decimal — общая себестоимость порции" ,
"items" : [
{
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"has_tech_card" : "boolean" ,
"net_weight" : "decimal" ,
"unit_of_measure" : "string" ,
"average_cost_per_unit" : "decimal | null" ,
"item_cost" : "decimal | null"
}
],
"warnings" : [
"string — например 'Нет данных о цене для Моцарелла'"
]
}
}
total_cost = ∑(item_cost). Если для ингредиента нет average_cost — item_cost = null, ингредиент не входит в сумму, но добавляется в warnings.
Errors
Code HTTP Когда UNAUTHORIZED401 TECH_CARD_NOT_FOUND404 TECH_CARD_NOT_FOUND404 Техкарта не найдена
GET /modifier-tech-cards
(BR 1.9.1)
Список техкарт модификаторов.
Параметр Значение Auth Bearer JWT (Franchise — все; Franchisee/Manager — активные товары)
Query Parameters
Param Type Required Description modifier_option_iduuid no Фильтр по опции
Response 200
{
"data" : [
{
"id" : "uuid" ,
"modifier_option_id" : "uuid" ,
"output_weight" : "decimal" ,
"output_unit" : "string" ,
"status" : "active | inactive" ,
"item_count" : "integer"
}
]
}
GET /modifier-tech-cards/{id}
(BR 1.9.1)
Детали техкарты модификатора с ингредиентами.
Параметр Значение Auth Bearer JWT (Franchise — любая; Franchisee/Manager — активные товары)
Response 200
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"modifier_option_id" : "uuid" ,
"output_weight" : "decimal" ,
"output_unit" : "string" ,
"cooking_description" : "string | null" ,
"status" : "active | inactive" ,
"items" : [
{
"id" : "uuid" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"gross_weight" : "decimal" ,
"net_weight" : "decimal" ,
"cold_loss_percent" : "decimal | null" ,
"hot_loss_percent" : "decimal | null" ,
"unit_of_measure" : "string" ,
"sort_order" : "integer"
}
],
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 MODIFIER_TECH_CARD_NOT_FOUND404
POST /modifier-tech-cards
(BR 1.9.1)
Создать техкарту для опции модификатора.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"modifier_option_id" : "uuid, required" ,
"output_weight" : "decimal, required" ,
"output_unit" : "string, required" ,
"cooking_description" : "string, optional"
}
Response 201
Errors
Code HTTP Когда MODIFIER_TECH_CARD_ALREADY_EXISTS409 Техкарта для этой опции уже существует
PATCH /modifier-tech-cards/{id}
(BR 1.9.1)
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"output_weight" : "decimal, optional" ,
"output_unit" : "string, optional" ,
"cooking_description" : "string | null, optional" ,
"status" : "string, optional — active | inactive"
}
Response 200
Аналогично GET /modifier-tech-cards/{id}.
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 MODIFIER_TECH_CARD_NOT_FOUND404
DELETE /modifier-tech-cards/{id}
(BR 1.9.1)
Параметр Значение Auth Bearer JWT (только Franchise)
Response 204
Errors
Code HTTP Когда MODIFIER_TECH_CARD_NOT_FOUND404
POST /modifier-tech-cards/{id}/items
(BR 1.9.1)
Добавить ингредиент в техкарту модификатора.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"ingredient_id" : "uuid, required — FK → ingredients.id" ,
"gross_weight" : "decimal, required" ,
"net_weight" : "decimal, required" ,
"cold_loss_percent" : "decimal, optional" ,
"hot_loss_percent" : "decimal, optional" ,
"unit_of_measure" : "string, required — г | кг | мл | л | шт" ,
"sort_order" : "integer, optional — default 0"
}
Response 201
{
"data" : {
"id" : "uuid" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"gross_weight" : "decimal" ,
"net_weight" : "decimal" ,
"cold_loss_percent" : "decimal | null" ,
"hot_loss_percent" : "decimal | null" ,
"unit_of_measure" : "string" ,
"sort_order" : "integer"
}
}
Errors
Code HTTP Когда MODIFIER_TECH_CARD_NOT_FOUND404 INGREDIENT_NOT_FOUND404 CIRCULAR_REFERENCE422
PATCH /modifier-tech-cards/{id}/items/{itemId}
(BR 1.9.1)
Errors
Code HTTP Когда MODIFIER_TECH_CARD_NOT_FOUND404 RECIPE_ITEM_NOT_FOUND404
DELETE /modifier-tech-cards/{id}/items/{itemId}
(BR 1.9.1)
Response 204
Errors
Code HTTP Когда RECIPE_ITEM_NOT_FOUND404
GET /unit-conversions
Конвертации единиц для продукта.
Параметр Значение Auth Bearer JWT (Franchise)
Query Parameters
Param Type Required Description product_iduuid yes Продукт, для которого список конвертаций
Response 200
{
"data" : [
{
"id" : "uuid" ,
"product_id" : "uuid" ,
"from_unit" : "string" ,
"to_unit" : "string" ,
"factor" : "decimal"
}
]
}
POST /unit-conversions
Создать конвертацию.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"product_id" : "uuid, required" ,
"from_unit" : "string, required" ,
"to_unit" : "string, required" ,
"factor" : "decimal, required — > 0"
}
Response 201
{
"data" : {
"id" : "uuid" ,
"product_id" : "uuid" ,
"from_unit" : "string" ,
"to_unit" : "string" ,
"factor" : "decimal"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 VALIDATION_ERROR400 CONVERSION_ALREADY_EXISTS409
PATCH /unit-conversions/{id}
Обновить коэффициент конвертации.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"factor" : "decimal, required — > 0"
}
Response 200
Аналогично POST /unit-conversions response.
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 CONVERSION_NOT_FOUND404
GET /ingredients
(BR 1.11)
Список ингредиентов франшизы.
Параметр Значение Auth Bearer JWT (Franchise, Franchisee, Manager; Cashier — 403)
Query Parameters
Param Type Required Description searchstring no Поиск по названию statusstring no Фильтр: active / inactive pageinteger no default: 1 per_pageinteger no default: 20, max: 100
Response 200
{
"data" : [
{
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"name" : "string" ,
"description" : "string | null" ,
"unit_of_measure" : "string" ,
"status" : "active | inactive" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
],
"meta" : {
"page" : "integer" ,
"per_page" : "integer" ,
"total" : "integer"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Роль Cashier
GET /ingredients/{id}
(BR 1.11)
Детали ингредиента.
Параметр Значение Auth Bearer JWT (Franchise, Franchisee, Manager; Cashier — 403)
Path Parameters
Param Type Required Description iduuid yes ID ингредиента
Response 200
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"name" : "string" ,
"description" : "string | null" ,
"unit_of_measure" : "string" ,
"status" : "active | inactive" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Роль Cashier INGREDIENT_NOT_FOUND404 Ингредиент не найден
POST /ingredients
(BR 1.11)
Создать ингредиент.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"name" : "string, required — название ингредиента" ,
"description" : "string, optional — описание" ,
"unit_of_measure" : "string, required — г | кг | мл | л | шт"
}
Response 201
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"name" : "string" ,
"description" : "string | null" ,
"unit_of_measure" : "string" ,
"status" : "active" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Не Franchise VALIDATION_ERROR400 Невалидные данные NAME_DUPLICATE409 Ингредиент с таким названием уже существует
PATCH /ingredients/{id}
(BR 1.11)
Обновить ингредиент.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Path Parameters
Param Type Required Description iduuid yes ID ингредиента
Request Body
{
"name" : "string, optional" ,
"description" : "string | null, optional" ,
"unit_of_measure" : "string, optional — г | кг | мл | л | шт" ,
"status" : "string, optional — active | inactive"
}
Response 200
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"name" : "string" ,
"description" : "string | null" ,
"unit_of_measure" : "string" ,
"status" : "active | inactive" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Не Franchise INGREDIENT_NOT_FOUND404 Ингредиент не найден NAME_DUPLICATE409 Ингредиент с таким названием уже существует INGREDIENT_IN_USE422 Нельзя изменить unit_of_measure — ингредиент используется в техкартах
DELETE /ingredients/{id}
(BR 1.11. Обновлено в BUG-024 )
Удалить ингредиент.
Параметр Значение Auth Bearer JWT (только Franchise)
Path Parameters
Param Type Required Description iduuid yes ID ингредиента
Поведение
Если ингредиент используется как компонент в чужих техкартах/модификаторах (recipe_items.ingredient_id или modifier_tech_card_items.ingredient_id) — 422 INGREDIENT_IN_USE
Если у ингредиента есть собственная техкарта (ингредиент-полуфабрикат: tech_cards.ingredient_id = :id) — техкарта и её recipe_items каскадно удаляются вместе с ингредиентом в одной транзакции. Это ожидаемое поведение: техкарта принадлежит своему полуфабрикату.
Response 204
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Не Franchise INGREDIENT_NOT_FOUND404 Ингредиент не найден INGREDIENT_IN_USE422 Ингредиент используется как компонент в чужих техкартах/модификаторах DB_CONSTRAINT409 Safety net: FK-ограничение БД не обработано на уровне сервиса
GET /warehouses
(BR 1.14)
Список складов франшизы. Фильтрация по доступу: Franchise — все, Franchisee/Manager — свои store_ids.
Параметр Значение Auth Bearer JWT (Franchise — все; Franchisee — свои; Manager — свой; Cashier — 403)
Response 200
{
"data" : [
{
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"store_id" : "uuid" ,
"name" : "string" ,
"status" : "active | inactive" ,
"created_at" : "datetime"
}
]
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Роль Cashier
POST /warehouses
(BR 1.14)
Создать склад вручную (обычно склад создаётся автоматически при создании ТТ в Store Service через internal-вызов). Полезно для стандартизированного создания склада с конкретным store_id и name.
Параметр Значение Auth Bearer JWT (только Franchise) Content-Type application/json
Request Body
{
"store_id" : "uuid, required — ID торговой точки" ,
"name" : "string, required — название склада"
}
Response 201
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"store_id" : "uuid" ,
"name" : "string" ,
"status" : "active" ,
"created_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Роль не Franchise STORE_NOT_FOUND404 Store не найден WAREHOUSE_ALREADY_EXISTS409 Склад для этой ТТ уже создан VALIDATION_ERROR400 Пустое name / невалидный store_id
POST /internal/warehouses
(BR 1.14)
Internal endpoint для авто-создания склада из Store Service в момент создания ТТ. Идемпотентный — повторный вызов с теми же store_id + franchise_id вернёт существующий склад с 200 OK, без 409.
Параметр Значение Auth X-Service-Token headerContent-Type application/json Path /internal/warehouses (без /api/v1 префикса)
Request Body
{
"store_id" : "uuid, required" ,
"franchise_id" : "uuid, required" ,
"name" : "string, required — обычно name ТТ"
}
Response 200
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"store_id" : "uuid" ,
"name" : "string" ,
"status" : "active" ,
"created_at" : "datetime"
}
}
Best-effort вызов из Store Service
StoreService.create() вызывает этот эндпоинт после storeRepository.save(store). При HTTP-ошибке Store Service логирует warning, но не откатывает создание ТТ — bootstrap может быть подтянут позже через backfill-эндпоинт.
Errors
Code HTTP Когда UNAUTHORIZED401 Невалидный/отсутствующий X-Service-Token VALIDATION_ERROR400 Пустое name / невалидный UUID
POST /internal/warehouses/backfill
(BR 1.14)
Internal endpoint для одноразового подтягивания складов под уже существующие ТТ. Дёргает Store Service GET /internal/stores/all, для каждой ТТ вызывает ensureForStore — создаёт склад если ещё нет, иначе ничего.
Параметр Значение Auth X-Service-Token headerPath /internal/warehouses/backfill
Response 200
{
"data" : {
"total_stores" : 12 ,
"created" : 7 ,
"skipped" : 5
}
}
Используется один раз после деплоя
Идемпотентен — повторные вызовы не приведут к дубликатам, но это не часть runtime-flow. Когда auto-create через Store Service работает для всех новых ТТ, backfill больше не нужен.
Errors
Code HTTP Когда UNAUTHORIZED401 Невалидный X-Service-Token
GET /stock-balances
(BR 1.14)
Список складских остатков с фильтрацией.
Параметр Значение Auth Bearer JWT (Franchise — все; Franchisee/Manager — свои store_ids; Cashier — 403)
Query Parameters
Param Type Required Description warehouse_iduuid yes Склад, остатки которого запрашиваем ingredient_iduuid no Фильтр по ингредиенту pageinteger no default: 1 per_pageinteger no default: 20, max: 100
Response 200
{
"data" : [
{
"id" : "uuid" ,
"warehouse_id" : "uuid" ,
"warehouse_name" : "string" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"current_quantity" : "decimal" ,
"average_cost" : "decimal | null" ,
"unit_of_measure" : "string" ,
"updated_at" : "datetime"
}
],
"meta" : {
"page" : "integer" ,
"per_page" : "integer" ,
"total" : "integer"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Роль Cashier или нет доступа к складу
GET /stock-balances/{warehouseId}/{ingredientId}
(BR 1.14)
Остаток конкретного ингредиента на конкретном складе.
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403)
Path Parameters
Param Type Required Description warehouseIduuid yes ID склада ingredientIduuid yes ID ингредиента
Response 200
{
"data" : {
"id" : "uuid" ,
"warehouse_id" : "uuid" ,
"warehouse_name" : "string" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"current_quantity" : "decimal" ,
"average_cost" : "decimal | null" ,
"unit_of_measure" : "string" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 STOCK_BALANCE_NOT_FOUND404 Нет баланса для этого ингредиента на складе
GET /receipt-acts
(BR 1.14)
Список актов приёмки с фильтрацией.
Параметр Значение Auth Bearer JWT (Franchise — все; Franchisee/Manager — свои склады; Cashier — 403)
Query Parameters
Param Type Required Description warehouse_iduuid no Фильтр по складу statusstring no draft / posted pageinteger no default: 1 per_pageinteger no default: 20, max: 100
Response 200
{
"data" : [
{
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"warehouse_id" : "uuid" ,
"warehouse_name" : "string" ,
"document_number" : "string" ,
"receipt_date" : "datetime" ,
"comment" : "string | null" ,
"status" : "draft | posted" ,
"total_amount" : "decimal" ,
"created_by" : "uuid" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
],
"meta" : {
"page" : "integer" ,
"per_page" : "integer" ,
"total" : "integer"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Роль Cashier или нет доступа к складу
POST /receipt-acts
(BR 1.14)
Создать акт приёмки (draft).
Параметр Значение Auth Bearer JWT (Franchise — любой склад; Franchisee/Manager — свой склад; Cashier — 403) Content-Type application/json
Request Body
{
"warehouse_id" : "uuid, required — склад" ,
"receipt_date" : "datetime, required — дата приёмки" ,
"comment" : "string, optional — комментарий"
}
Response 201
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"warehouse_id" : "uuid" ,
"warehouse_name" : "string" ,
"document_number" : "string" ,
"receipt_date" : "datetime" ,
"comment" : "string | null" ,
"status" : "draft" ,
"total_amount" : "decimal" ,
"lines" : [],
"created_by" : "uuid" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Нет доступа к складу VALIDATION_ERROR400 Невалидные данные WAREHOUSE_NOT_FOUND404 Склад не найден
GET /receipt-acts/{id}
(BR 1.14)
Детали акта приёмки со строками.
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403)
Path Parameters
Param Type Required Description iduuid yes ID акта приёмки
Response 200
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"warehouse_id" : "uuid" ,
"warehouse_name" : "string" ,
"document_number" : "string" ,
"receipt_date" : "datetime" ,
"comment" : "string | null" ,
"status" : "draft | posted" ,
"total_amount" : "decimal" ,
"lines" : [
{
"id" : "uuid" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"quantity" : "decimal" ,
"unit_of_measure" : "string" ,
"unit_price" : "decimal" ,
"line_total" : "decimal" ,
"shelf_life_date" : "date | null"
}
],
"created_by" : "uuid" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 RECEIPT_ACT_NOT_FOUND404 Акт приёмки не найден
PATCH /receipt-acts/{id}
(BR 1.14)
Обновить акт приёмки (только в статусе draft).
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403) Content-Type application/json
Path Parameters
Param Type Required Description iduuid yes ID акта приёмки
Request Body
{
"receipt_date" : "datetime, optional" ,
"comment" : "string | null, optional"
}
Response 200
Аналогично GET /receipt-acts/{id} response.
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 RECEIPT_ACT_NOT_FOUND404 Акт не найден DOCUMENT_ALREADY_POSTED422 Документ уже проведён, редактирование запрещено
POST /receipt-acts/{id}/post
(BR 1.14)
Провести акт приёмки. Создаёт партии, обновляет остатки, пересчитывает среднюю цену.
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403)
Path Parameters
Param Type Required Description iduuid yes ID акта приёмки
Response 200
Аналогично GET /receipt-acts/{id} response (status = posted).
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 RECEIPT_ACT_NOT_FOUND404 DOCUMENT_ALREADY_POSTED422 Документ уже проведён EMPTY_DOCUMENT422 Нет строк в документе
POST /receipt-acts/{id}/lines
(BR 1.14)
Добавить строку в акт приёмки (только draft).
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403) Content-Type application/json
Path Parameters
Param Type Required Description iduuid yes ID акта приёмки
Request Body
{
"ingredient_id" : "uuid, required — FK → ingredients.id" ,
"quantity" : "decimal, required — > 0" ,
"unit_of_measure" : "string, required — г | кг | мл | л | шт" ,
"unit_price" : "decimal, required — >= 0, закупочная цена" ,
"shelf_life_date" : "date, optional — срок годности"
}
Response 201
{
"data" : {
"id" : "uuid" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"quantity" : "decimal" ,
"unit_of_measure" : "string" ,
"unit_price" : "decimal" ,
"line_total" : "decimal" ,
"shelf_life_date" : "date | null"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 RECEIPT_ACT_NOT_FOUND404 INGREDIENT_NOT_FOUND404 Ингредиент не найден DOCUMENT_ALREADY_POSTED422 Документ уже проведён VALIDATION_ERROR400 Невалидные данные
PATCH /receipt-acts/{id}/lines/{lineId}
(BR 1.14)
Обновить строку акта приёмки (только draft).
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403) Content-Type application/json
Path Parameters
Param Type Required Description iduuid yes ID акта приёмки lineIduuid yes ID строки
Request Body
{
"quantity" : "decimal, optional — > 0" ,
"unit_of_measure" : "string, optional" ,
"unit_price" : "decimal, optional — >= 0" ,
"shelf_life_date" : "date | null, optional"
}
Response 200
Аналогично POST /receipt-acts/{id}/lines response.
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 RECEIPT_ACT_NOT_FOUND404 RECEIPT_ACT_LINE_NOT_FOUND404 Строка не найдена DOCUMENT_ALREADY_POSTED422
DELETE /receipt-acts/{id}/lines/{lineId}
(BR 1.14)
Удалить строку из акта приёмки (только draft).
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403)
Path Parameters
Param Type Required Description iduuid yes ID акта приёмки lineIduuid yes ID строки
Response 200
Возвращает обновлённый объект ReceiptActResponse (полный акт со всеми оставшимися строками и пересчитанными итогами).
{ "data" : { "id" : "uuid" , "status" : "draft" , "lines" : [ ... ], "total" : "decimal" } }
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 RECEIPT_ACT_NOT_FOUND404 RECEIPT_ACT_LINE_NOT_FOUND404 DOCUMENT_ALREADY_POSTED422
GET /write-off-acts
(BR 1.14)
Список актов списания с фильтрацией.
Параметр Значение Auth Bearer JWT (Franchise — все; Franchisee/Manager — свои склады; Cashier — 403)
Query Parameters
Param Type Required Description warehouse_iduuid no Фильтр по складу statusstring no draft / posted pageinteger no default: 1 per_pageinteger no default: 20, max: 100
Response 200
{
"data" : [
{
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"warehouse_id" : "uuid" ,
"warehouse_name" : "string" ,
"document_number" : "string" ,
"write_off_date" : "datetime" ,
"reason" : "string" ,
"status" : "draft | posted" ,
"total_cost" : "decimal" ,
"created_by" : "uuid" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
],
"meta" : {
"page" : "integer" ,
"per_page" : "integer" ,
"total" : "integer"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Роль Cashier или нет доступа к складу
POST /write-off-acts
(BR 1.14)
Создать акт списания (draft).
Параметр Значение Auth Bearer JWT (Franchise — любой склад; Franchisee/Manager — свой склад; Cashier — 403) Content-Type application/json
Request Body
{
"warehouse_id" : "uuid, required — склад" ,
"write_off_date" : "datetime, required — дата списания" ,
"reason" : "string, required — причина списания (порча, недостача и т.д.)"
}
Response 201
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"warehouse_id" : "uuid" ,
"warehouse_name" : "string" ,
"document_number" : "string" ,
"write_off_date" : "datetime" ,
"reason" : "string" ,
"status" : "draft" ,
"total_cost" : "decimal" ,
"lines" : [],
"created_by" : "uuid" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 Нет доступа к складу VALIDATION_ERROR400 Невалидные данные WAREHOUSE_NOT_FOUND404 Склад не найден
GET /write-off-acts/{id}
(BR 1.14)
Детали акта списания со строками.
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403)
Path Parameters
Param Type Required Description iduuid yes ID акта списания
Response 200
{
"data" : {
"id" : "uuid" ,
"franchise_id" : "uuid" ,
"warehouse_id" : "uuid" ,
"warehouse_name" : "string" ,
"document_number" : "string" ,
"write_off_date" : "datetime" ,
"reason" : "string" ,
"status" : "draft | posted" ,
"total_cost" : "decimal" ,
"lines" : [
{
"id" : "uuid" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"quantity" : "decimal" ,
"unit_cost" : "decimal" ,
"line_cost" : "decimal"
}
],
"created_by" : "uuid" ,
"created_at" : "datetime" ,
"updated_at" : "datetime"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 WRITE_OFF_ACT_NOT_FOUND404 Акт списания не найден
PATCH /write-off-acts/{id}
(BR 1.14)
Обновить акт списания (только draft).
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403) Content-Type application/json
Path Parameters
Param Type Required Description iduuid yes ID акта списания
Request Body
{
"write_off_date" : "datetime, optional" ,
"reason" : "string, optional"
}
Response 200
Аналогично GET /write-off-acts/{id} response.
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 WRITE_OFF_ACT_NOT_FOUND404 Акт не найден DOCUMENT_ALREADY_POSTED422 Документ уже проведён
POST /write-off-acts/{id}/post
(BR 1.14)
Провести акт списания. FIFO-списание с партий, обновление остатков.
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403)
Path Parameters
Param Type Required Description iduuid yes ID акта списания
Response 200
Аналогично GET /write-off-acts/{id} response (status = posted).
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 WRITE_OFF_ACT_NOT_FOUND404 DOCUMENT_ALREADY_POSTED422 Документ уже проведён EMPTY_DOCUMENT422 Нет строк в документе INSUFFICIENT_STOCK422 Недостаточно остатков на складе
POST /write-off-acts/{id}/lines
(BR 1.14)
Добавить строку в акт списания (только draft).
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403) Content-Type application/json
Path Parameters
Param Type Required Description iduuid yes ID акта списания
Request Body
{
"ingredient_id" : "uuid, required — FK → ingredients.id" ,
"quantity" : "decimal, required — > 0, количество к списанию"
}
unit_cost и line_cost рассчитываются автоматически из средневзвешенной цены ингредиента на складе.
Response 201
{
"data" : {
"id" : "uuid" ,
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"quantity" : "decimal" ,
"unit_cost" : "decimal" ,
"line_cost" : "decimal"
}
}
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 WRITE_OFF_ACT_NOT_FOUND404 INGREDIENT_NOT_FOUND404 Ингредиент не найден DOCUMENT_ALREADY_POSTED422 VALIDATION_ERROR400 Невалидные данные
DELETE /write-off-acts/{id}/lines/{lineId}
(BR 1.14)
Удалить строку из акта списания (только draft).
Параметр Значение Auth Bearer JWT (Franchise — любой; Franchisee/Manager — свой склад; Cashier — 403)
Path Parameters
Param Type Required Description iduuid yes ID акта списания lineIduuid yes ID строки
Response 204
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 WRITE_OFF_ACT_NOT_FOUND404 WRITE_OFF_ACT_LINE_NOT_FOUND404 DOCUMENT_ALREADY_POSTED422
GET /ingredients/{id}/average-cost
(BR 1.14)
Средневзвешенная закупочная цена ингредиента. Рассчитывается из складских партий.
Параметр Значение Auth Bearer JWT (Franchise — любой склад; Franchisee/Manager — свой склад; Cashier — 403)
Path Parameters
Param Type Required Description iduuid yes ID ингредиента
Query Parameters
Param Type Required Description warehouse_iduuid yes Склад, для которого считаем среднюю цену
Response 200
{
"data" : {
"ingredient_id" : "uuid" ,
"ingredient_name" : "string" ,
"warehouse_id" : "uuid | null" ,
"average_cost" : "decimal | null" ,
"unit_of_measure" : "string" ,
"total_quantity" : "decimal"
}
}
Если нет партий с количеством > 0, average_cost = null.
Errors
Code HTTP Когда UNAUTHORIZED401 FORBIDDEN403 INGREDIENT_NOT_FOUND404 Ингредиент не найден
Общий формат ошибок
{
"error" : {
"code" : "string — UPPER_SNAKE_CASE" ,
"message" : "string — человекочитаемое" ,
"details" : [
{
"field" : "string" ,
"message" : "string"
}
]
}
}