sequenceDiagram
autonumber
actor User as Пользователь (Экран Готовки)
participant APP as Мобильное приложение (Flutter)
participant API as Бэкенд-шлюз (FastAPI)
participant DB as Реляционная СУБД (PostgreSQL)
participant KAFKA as Брокер событий (Kafka)
User->>APP: Нажимает кнопку "Блюдо готово! Зафиксировать"
APP->>API: HTTP POST /api/v1/cook/recipes/412/confirm (JWT в заголовке)
activate API
note over API: Шаг 3: Чтение структуры рецепта из Master Data
Note over API, DB: Старт атомарной SQL-транзакции
loop По каждому ингредиенту из структуры рецепта
API->>DB: Нахождение всех живых партий продукта (quantity > 0)
activate DB
DB-->>API: Массив записей партий товара с их объемами
deactivate DB
note over API: Шаг 6: Авто-распределение веса списания по записям
alt Суммарного остатка партий ХВАТАЕТ
API->>DB: UPDATE fridge_inventory (уменьшение баланса записей в ноль или остаток)
else Суммарного остатка НЕ ХВАТАЕТ
alt Контекст аккаунта == OFFICE
API-->>APP: HTTP 400 Bad Request (ERR-STOCK-SHORTAGE-CONFIRM)
else Контекст аккаунта == HOME
note over API: Шаг 9: Автоматический уход последней партии в минус
API->>DB: UPDATE fridge_inventory (последняя партия уходит в технический минус)
end
end
end
note over API: Шаг 11: Создание готового блюда (Объем = 1.0)
API->>DB: INSERT INTO fridge_inventory (product_name="Яблочный пирог...", quantity=1.0, unit="шт")
Note over API, DB: Коммит атомарной транзакции (COMMIT)
API->>KAFKA: send_to_kafka_async("bupar.fridge.events.recipe_confirmed")
API-->>APP: HTTP 201 Created (Остатки обновлены, блюдо зачислено)
deactivate API
note over APP: Экран блокируется, юзер перенаправляется на склад, где видит готовое блюдо
Метод POST /api/v1/cook/recipes/{id}/confirm
Документация API: Финальный коммит транзакции готовки по рецепту со списанием ингредиентов и генерацией масштабируемого блюда
1 Функциональное назначение
Метод является финальной точкой контура готовки по рецепту. Он вызывается, когда пользователь нажимает кнопку «Блюдо готово» на экране пошагового руководства Flutter-приложения.
Метод выполняет следующие критические операции в рамках единой транзакции: 1. Каскадное списание объемов: Бэкенд списывает требуемый по рецепту вес/количество ингредиентов. В отличие от ручного метода, здесь списание происходит автоматически. Система ищет все живые партии нужного продукта в рамках home_group_id и уменьшает их баланс. 2. Обслуживание вилки аккаунтов (Уход в минус): * В контексте HOME, если суммарного объема партий не хватает, последняя измененная партия уводится в отрицательное float-значение (виртуальный минус). * В контексте OFFICE транзакция падает с ошибкой, так как предварительная валидация (Метод 3) обязана была заблокировать этот шаг на клиенте. 3. Зачисление каноничного блюда: В холодильник пользователя добавляется новая позиция с именем рецепта и базовым порционным объемом 1.0 шт для последующего дробления в контуре потребления.
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/cook/recipes/{id}/confirm - Формат данных:
application/json
2.1 Спецификация параметров пути (Path Parameters)
| Параметр | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
id |
Integer | Да | Уникальный идентификатор выполненного рецепта в мастер-базе | 412 |
2.2 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Формат передаваемых данных | application/json |
Authorization |
Да | Токен авторизации (Access Token). Извлекаются home_group_id и account_type. |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для сквозного логирования операции списания | req-rec-conf-33cc |
2.3 Спецификация тела запроса (Request Body)
Тело запроса является опциональным или пустым, так как все необходимые пропорции ингредиентов бэкенд извлекает самостоятельно из мастер-базы рецептов по id, переданному в URL.
2.3.1 Пример сырого JSON-запроса (Payload):
{}3 Схема обработки запроса пользователя (Mermaid)
На диаграмме представлена логика автоматического каскадного списания ингредиентов рецепта по существующим записям поставок в холодильнике, а также финальное зачисление готового блюда с коэффициентом объема 1.0.
4 Расшифровка шагов
- Шаг 1 (
User -> APP): Пользователь завершает процесс пошагового приготовления блюда по рецепту (например, испёк пирог) и нажимает на экране смартфона финальную кнопку подтверждения «Блюдо готово! Зафиксировать». - Шаг 2 (
APP -> API): Мобильное приложение отправляет пустой JSON-запрос методомHTTP POST /api/v1/cook/recipes/{id}/confirm. В заголовках передается JWT-токен сессии и сквознойX-Request-ID. - Шаг 3 (
API -> API): Бэкенд-шлюзFastAPIобращается к справочнику рецептов и вычитывает точные пропорции ингредиентов, необходимых для выполнения этогоrecipe_id(например, для рецепта №412 требуется0.5 кгяблок,0.3 кгмуки). - Шаг 4 (
API -> DB):FastAPIоткрывает атомарную SQL-транзакцию вPostgreSQLи запускает цикл автоматического каскадного списания. Бэкенд ищет в холодильнике пользователя (home_group_id) все существующие записи поставок для первого ингредиента рецепта (например, яблок), у которыхquantity > 0. - Шаг 5 (
DB -> API): База данных возвращает массив живых записей партий товара с их текущими физическими объемами. - Шаг 6 (
API -> API): Бэкенд выполняет внутренний расчет распределения веса. В отличие от ручного ввода, система сама списывает объёмы по цепочке найденных записей: уменьшает в ноль самую старую запись, а остаток требуемого веса переносит на следующую строку поставки товара. - Шаг 7 (
API -> DB): Бэкенд отправляет серию командUPDATE fridge_inventoryдля фиксации уменьшения баланса по конкретным измененным ID партий. - Шаг 8 (
API -> API): Если в ходе расчёта обнаруживается, что суммарного объема всех живых партий продукта в холодильнике не хватает для покрытия требований рецепта, система переходит к вилке условий бизнес-контекста. - Шаг 9 (
API -> APP): Ветка OFFICE: Так как в корпоративном контуре уход в минус строго запрещен, бэкенд мгновенно вызываетROLLBACK, аннулирует все промежуточные списания и возвращает мобильному приложению жесткую ошибкуHTTP 400. - Шаг 10 (
API -> DB): Ветка HOME: Контур семьи. Включается логика автоматического ухода в виртуальный минус. Система полностью списывает в ноль все существующие живые партии продукта, а весь оставшийся дефицит веса (которого не хватило) вычитает из последней измененной записи, уводя её баланс в отрицательное float-значение. - Шаг 11 (
API -> DB): После завершения цикла автоматического списания по всем ингредиентам, бэкенд производит канонизацию и зачисление готового блюда. В таблицуfridge_inventoryдобавляется новая независимая запись с названием рецепта (например,"Яблочный пирог классический"). Объём жестко выставляется в базовое значение1.0, а единица измерения — в"шт". Это подготавливает блюдо к последующему порционному дроблению в контуре потребления. - Шаг 12 (
API -> KAFKA): Атомарная транзакция фиксируется в базе данных (COMMIT). Бэкенд асинхронно отправляет бизнес-событие в топик Kafkabupar.fridge.events.recipe_confirmedдля сквозного контроля изменения остатков и продуктовой аналитики. - Шаг 13 (
API -> APP): Бэкенд возвращает успешный ответHTTP 201 Created. Мобильное приложение Flutter закрывает экран готовки и перенаправляет пользователя на главный экран склада, где в списке продуктов появляется готовый пирог объемом1.0 шт.
5 Спецификация ответов сервера (Response Body) и ошибок
5.1 1. Успешный ответ (Success Response)
5.1.1 HTTP 201 Created (Ответ на Шаге 13)
Возвращается после успешного завершения атомарной транзакции (автоматического каскадного списания ингредиентов по записям партий и зачисления готового блюда с базовым коэффициентом порционности 1.0).
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "RECIPE_COOK_CONFIRMED",
"data": {
"fridge_item_id": 7841,
"dish_name": "Яблочный пирог классический",
"allocated_quantity": 1.0,
"unit": "шт",
"account_context": "HOME",
"timestamp": "2026-07-01T21:55:00Z"
}
}5.2 2. Спецификация ошибок и вилок исключений (Error Responses)
5.2.1 Ошибка критического дефицита при коммите (HTTP 400 Bad Request — Шаг 9)
Выбрасывается строго в контексте аккаунта OFFICE, если в момент финальной фиксации блюда обнаружилось, что суммарный физический объем записей партий в БД уменьшился (например, кто-то параллельно списал муку) и продуктов теперь физически не хватает для выполнения рецепта. Транзакция полностью откатывается (ROLLBACK).
{
"error_code": "ERR-STOCK-SHORTAGE-CONFIRM",
"message": "Финиширование рецепта отклонено: обнаружен критический дефицит ингредиентов на складе офиса.",
"details": {
"reason": "Race condition or parallel consumption cleared required stock before commit.",
"missing_product": "Мука",
"required_total": 0.300,
"available_total": 0.050,
"unit": "кг"
}
}5.2.2 Ошибка коллизии состояний рецепта (HTTP 409 Conflict)
Выбрасывается на Шаге 3, если пользователь пытается повторно закоммитить рецепт по уже закрытой или невалидной сессии готовки.
{
"error_code": "ERR-RECIPE-SESSION-CONFLICT",
"message": "Данная сессия приготовления уже была зафиксирована или аннулирована.",
"details": {
"recipe_id": 412,
"action": "Refresh fridge stock state and check actual inventory grid."
}
}6 Архитектурное резюме микросервиса Cook (Готовка)
Мы полностью спроектировали и задокументировали в формате QMD ключевое ядро микросервиса Cook:
- Метод 1 (
POST /manual): Ручная фиксация. Пользователь сам тапает по конкретным строкам партий товаров на экране (как на скриншоте интерфейса) и списывает их. ДляHOMEдоступен виртуальный уход в минус. Готовое блюдо (суп, пирог) канонизируется к базовому объему1.0 шт. - Метод 2 (
GET /recipes/search): Умный подбор рецептов. Работает на уровне СУБД через агрегации иCTE. Ищет пересечения ингредиентов с живым холодильником (совпадение минимум по 1 продукту) и отдает максимум 500 записей, отсортированных по проценту готовности. - Метод 3 (
GET /recipes/{id}/validate): Предварительный расчет дефицита. Суммирует объемы раздельных закупленных партий. Выдает структуруmissing_ingredientsи делит интерфейс Flutter: подсвечивает желтымWARNINGдля семьи или жестко блокирует кнопку старта через красныйBLOCKдля офиса. - Метод 4 (
POST /recipes/{id}/confirm): Финальный коммит. Автоматически распределяет вес списания по цепочке живых записей холодильника, уводит в минус последнюю партию (в контекстеHOME) и зачисляет готовое блюдо с коэффициентом1.0 штдля последующего порционного деления.