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/consume/session/sess-meal-99aa-11ef/cancel (с JWT)
activate API
note over API: Шаг 3: Верификация прав доступа к session_id по home_group_id
Note over API, DB: Старт атомарной SQL-транзакции
API->>DB: SELECT * FROM consumption_log WHERE session_id = target_session_id FOR UPDATE
activate DB
DB-->>API: Массив списанных продуктов (item_id, consumed_quantity, unit)
deactivate DB
loop По каждому продукту из лога сессии
API->>DB: UPDATE fridge_inventory SET quantity = quantity + consumed_quantity WHERE item_id
end
API->>DB: DELETE FROM consumption_log WHERE session_id = target_session_id
Note over API, DB: Коммит атомарной транзакции (COMMIT)
API->>KAFKA: send_to_kafka_async("bupar.fridge.events.consume_cancelled")
API-->>APP: HTTP 200 OK (status: "CONSUME_SESSION_CANCELLED")
deactivate API
note over APP: Экран обновляется: отмененный обед исчезает, остатки в холодильнике восстановлены
Метод POST /api/v1/consume/session/{session_id}/cancel
Документация API: Атомарный реверс (откат) сессии потребления с восстановлением float-коэффициентов остатков
- INVENTORY-2XX Backlog — Краткое Описание задачи 1.
- INVENTORY-1XX Refinement (Уточнение) — Краткое Описание задачи 2.
1 Функциональное назначение
Метод предназначен для восстановления складских запасов в случае ошибочного или случайного фиксации приема пищи пользователем во Flutter-интерфейсе (например, пользователь случайно «съел» в приложении чужой пирог или йогурт). Метод выполняет каскадный откат всей сессии потребления (session_id), содержащей как одну позицию (перекус), так и комплексный набор блюд («1-е, 2-е и компот»).
Метод решает следующие задачи:
- Реверсивный пересчет float-остатков: Извлекает из исторического лога
consumption_logточные объемы и порции, которые были списаны в рамках данной сессии, и математически возвращает их обратно на баланс соответствующихfridge_item_id(например, возвращает0.1250к остатку пирога). - Атомарность операции: Весь комплексный обед восстанавливается строго внутри одной SQL-транзакции. Невозможно восстановить только суп, оставив пирог списанным.
- Очистка исторического следа: Удаляет или маркирует сессию в
consumption_logкак аннулированную, предотвращая искажение метрик КБЖУ для команды MLOps.
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/consume/session/{session_id}/cancel - Формат данных:
application/json
2.1 Спецификация параметров пути (Path Parameters)
| Параметр | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
session_id |
String | Да | Уникальный UUID сессии приема пищи, которую необходимо отменить | sess-meal-99aa-11ef |
2.2 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Authorization |
Да | Токен авторизации (Access Token). Используется для валидации прав на управление холодильником. |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для распределенного логирования | req-consume-cancel-22cc |
2.3 Спецификация тела запроса (Request Body)
Тело запроса является пустым, так как всю структуру списанных продуктов бэкенд вычитывает самостоятельно из исторической таблицы логов по session_id.
2.3.1 Пример сырого JSON-запроса (Payload):
{}3 Схема обработки запроса пользователя (Mermaid)
На диаграмме представлена логика транзакционного реверса сессии: СУБД вычитывает исторический лог, возвращает float-коэффициенты порций и фабричных товаров на баланс холодильника, а затем аннулирует запись сессии.
4 Расшифровка шагов
- Шаг 1 (
User -> APP): Пользователь заходит в журнал питания, видит ошибочно зафиксированный прием пищи (например, комплексный обед сsession_id = sess-meal-99aa-11ef) и нажимает кнопку «Отменить прием пищи». - Шаг 2 (
APP -> API): Приложение через клиентDioотправляет запросHTTP POST /api/v1/consume/session/{session_id}/cancel. Идентификатор сессии передается в URL в качестве Path-параметра. - Шаг 3 (
API -> API): Бэкенд-шлюзFastAPIдекодирует JWT-токен и извлекаетhome_group_id. Система выполняет валидацию безопасности: проверяет, принадлежит ли данная сессия потребления к холодильнику этой семьи или предприятия, исключая подмену ID. - Шаг 4 (
API -> DB): Шлюз открывает атомарную SQL-транзакцию вPostgreSQLи блокирует исторические строки запросомSELECT * FROM consumption_log WHERE session_id = $1 FOR UPDATE. - Шаг 5 (
DB -> API): База данных возвращает полный массив продуктов и точных дробных порций, которые были списаны в рамках этой сессии (например,0.1250от яблочного пирога и1.0000от йогурта). - Шаг 6 (
API -> DB): Запускается цикл реверсивного восстановления остатков. Для каждого элемента массива бэкенд отправляет DML-команду инкремента:UPDATE fridge_inventory SET quantity = quantity + $1 WHERE item_id = $2. Математическое сложение гарантирует, что к остатку пирога вернутся ровно его0.1250долей.- Валидация скрытых минусов: Если фабричный товар ранее ушел в скрытый минус и строка имела отрицательный баланс, этот инкремент вернет её в нулевое или положительное состояние, автоматически восстанавливая корректный баланс склада.
- Шаг 7 (
API -> DB): После успешного возврата всех весов и штук на склад, бэкенд выполняет команду очистки исторического следа:DELETE FROM consumption_log WHERE session_id = $1. Это защищает аналитический контур MLOps от ложных КБЖУ-данных. - Шаг 8 (
API -> API): Атомарная транзакция фиксируется в PostgreSQL (COMMIT). Данные склада и истории синхронизируются. - Шаг 9 (
API -> KAFKA): Бэкенд асинхронно отправляет системное событие в топик Kafkabupar.fridge.events.consume_cancelledдля уведомления смежных BI-модулей об отмене приема пищи. - Шаг 10 (
API -> APP):FastAPIвозвращает успешный ответHTTP 200 OK. Мобильное приложение Flutter скрывает карточку сессии из таймлайна журнала, а общий экран холодильника перерисовывается, отображая восстановленный объем продуктов.
5 Спецификация ответов сервера (Response Body) и ошибок
5.1 1. Успешный ответ (Success Response)
5.1.1 HTTP 200 OK (Ответ на Шаге 10)
Возвращается мобильному приложению после успешного завершения реверсивной транзакции. Все списанные в рамках сессии float-коэффициенты порций и фабричные товары возвращены на баланс холодильника, а исторический лог очищен.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "CONSUME_SESSION_CANCELLED",
"data": {
"session_id": "sess-meal-99aa-11ef",
"home_group_id": "group-772-alpha",
"total_items_restored": 2,
"restored_items": [
{
"product_name": "Яблочный пирог классический",
"restored_quantity_increment": 0.1250,
"unit": "шт"
},
{
"product_name": "Йогурт Danone Клубника",
"restored_quantity_increment": 1.0000,
"unit": "шт"
}
],
"timestamp": "2026-07-01T23:56:00Z"
}
}5.2 2. Спецификация ошибок и вилок исключений (Error Responses)
5.2.1 Ошибка: Сессия не найдена или уже отменена (HTTP 404 Not Found — Шаг 5)
Выбрасывается на Шаге 5, если переданный session_id отсутствует в таблице consumption_log (например, сессия была отменена ранее или передан неверный UUID).
{
"error_code": "ERR-CONSUME-SESSION-NOT-FOUND",
"message": "Указанная сессия приема пищи не найдена в журнале питания.",
"details": {
"requested_session_id": "sess-meal-99aa-11ef",
"reason": "Entity not found or already deleted from consumption_log table."
}
}5.2.2 Ошибка нарушения прав доступа к истории (HTTP 403 Forbidden — Шаг 3)
Выбрасывается на Шаге 3 бэкенд-шлюзом FastAPI, если запрашиваемый session_id принадлежит другой семье или предприятию (home_group_id из JWT-токена не совпадает со скоупом сессии).
{
"error_code": "ERR-CONSUME-HISTORY-ACCESS-DENIED",
"message": "Операция отклонена: у вас нет прав на отмену приемов пищи в чужом контуре.",
"details": {
"reason": "Security token home_group_id mismatch with target consumption session context."
}
}