sequenceDiagram
autonumber
actor Manager as Менеджер (Офисный аккаунт)
participant APP as Мобильное приложение (Flutter)
participant MS as Сервис Меню (FastAPI)
participant DB as База Данных (PostgreSQL)
participant KAFKA as Брокер событий (Kafka)
Manager->>APP: Перезаписывает состав меню и жмет "Обновить полностью"
APP->>MS: HTTP PUT /api/v1/menu/custom/102 (Полный JSON-пакет)
activate MS
note over MS: Шаг 3: Валидация JWT (account_type == OFFICE)
note over MS: Шаг 4: Валидация лимита элементов (макс. 15 позиций)
Note over MS, DB: Старт атомарной SQL-транзакции
MS->>DB: SELECT home_group_id FROM company_custom_menus WHERE id = 102 FOR UPDATE
activate DB
DB-->>MS: Возвращает home_group_id существующего шаблона
deactivate DB
note over MS: Шаг 6: Проверка соответствия home_group_id компании
MS->>DB: UPDATE company_custom_menus SET menu_title = \$1 WHERE id = 102
MS->>DB: DELETE FROM company_menu_items WHERE menu_template_id = 102 (Очистка карты)
loop По каждому новому продукту из массива items_matrix
MS->>DB: INSERT INTO company_menu_items (menu_template_id, product_name, ...)
end
Note over MS, DB: Коммит транзакции (COMMIT)
MS->>KAFKA: send_to_kafka_async("masterdata.menu.mutated.v1")
MS-->>APP: HTTP 200 OK (status: "REPLACED")
deactivate MS
note over APP: Список меню обновляется, старый состав полностью заменяется новым супом-харчо
Метод PUT /api/v1/menu/custom/{id}
Документация API: Полная перезапись технологической карты корпоративного меню (Update) с каскадным замещением строк
1 Функциональное назначение
Метод предназначен для полного обновления (перезаписи) существующего кастомного шаблона комплексного меню компании менеджером предприятия (account_type == OFFICE).
В отличие от точечного метода PATCH, метод PUT реализует паттерн полного замещения состояния (Idempotent Replace) и решает следующие задачи: 1. Каскадное обновление структуры (Связь 1:M): Бэкенд атомарно заменяет старое текстовое название меню и полностью вычищает старую матрицу ингредиентов, записывая вместо неё новый массив продуктов, переданный клиентом. 2. Защита от сиротских записей (Orphan Removal): Гарантирует, что старые строки технологической карты, не упомянутые в новом запросе, будут физически удалены из дочерней таблицы company_menu_items, предотвращая загрязнение базы данных. 3. Инвалидация распределенного кэша: После успешного коммита изменений генерирует событие в Apache Kafka для обновления графов данных на ИИ-сервере аналитики КБЖУ.
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
PUT - Маршрут:
/api/v1/menu/custom/{id} - Формат данных:
application/json
2.1 Спецификация параметров пути (Path Parameters)
| Параметр | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
id |
Integer | Да | Уникальный ID перезаписываемого кастомного меню компании | 102 |
2.2 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Указывает на передачу строго типизированного JSON-пакета | application/json |
Authorization |
Да | Токен авторизации (Access Token) для проверки прав администратора офиса |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для связывания логов операции обновления | req-menu-put-33bb |
2.3 Спецификация тела запроса (Request Body)
При использовании метода PUT клиент обязан передать всю структуру объекта целиком. Если какое-то поле или ингредиент не будут переданы в JSON, система расценит это как команду на их удаление.
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
menu_title |
String | Да | Новое полное название комплексного меню | "Обед Четверг: Суп-харчо и Лаваш" |
items_matrix |
Array | Да | Полный новый список канонических продуктов и порций | [..._name: "Рис", qty: 0.100] |
2.3.1 Пример сырого JSON-запроса (Payload):
{
"menu_title": "Обед Четверг: Суп-харчо и Лаваш",
"items_matrix": [
{
"product_name": "Рис лазер каноничный",
"required_quantity": 0.1000,
"unit": "кг"
},
{
"product_name": "Мясо говядина",
"required_quantity": 0.2000,
"unit": "кг"
},
{
"product_name": "Хлеб Лаваш",
"required_quantity": 1.0000,
"unit": "шт"
}
]
}3 Схема обработки запроса пользователя (Mermaid)
На диаграмме представлена логика полной перезаписи (PUT) корпоративного меню. СУБД внутри атомарной транзакции проверяет права доступа, обновляет шапку меню, полностью вычищает старую технологическую карту (DELETE) и вставляет новые позиции (INSERT), предотвращая появление устаревших данных.
4 Расшифровка шагов
- Шаг 1 (
Manager -> APP): Менеджер предприятия в административной панели полностью переписывает меню (например, заменяет «Пятничный Бургер-День» на комплекс «Обед Четверг: Суп-харчо и Лаваш», полностью меняя и название, и весь перечень продуктов) и нажимает кнопку «Обновить полностью». - Шаг 2 (
APP -> MS): Мобильное приложение отправляет запросHTTP PUT /api/v1/menu/custom/{id}. В теле запроса передается монолитный JSON-пакет, содержащий новое название и полную новую матрицу ингредиентов. В заголовках прокидывается Access Token иX-Request-ID. - Шаг 3 (
MS -> MS): Микросервис Меню (FastAPI) декодирует JWT-токен и проверяет клеймaccount_type. Если запрос пришел из домашнего контура (HOME), сервис мгновенно прерывает операцию с ошибкойHTTP 403 Forbidden, так как управление меню доступно только корпоративным клиентам. - Шаг 4 (
MS -> MS): Валидация бизнес-лимитов: Срабатывает проверка длины нового входящего массиваitems_matrix. Если переданная технологическая карта содержит более 15 позиций, бэкенд отсекает запрос с ошибкойHTTP 400 Bad Request, защищая СУБД от перегрузки. - Шаг 5 (
MS -> DB): Сервис Меню открывает атомарную SQL-транзакцию вPostgreSQLи блокирует строку шаблона:SELECT home_group_id FROM company_custom_menus WHERE id = $1 FOR UPDATE. Это защищает запись от параллельного удаления или списания в контуре потребления. - Шаг 6 (
MS -> MS): Проверка контекста безопасности (Multi-Tenancy): Сервис сверяетhome_group_idиз базы данных сhome_group_id, зашитым в токене менеджера. Если идентификаторы не совпадают (попытка перезаписать меню чужого офиса), транзакция откатывается (ROLLBACK) со статусомHTTP 403 Forbidden. - Шаг 7 (
MS -> DB): Обновление шапки: Сервис выполняет перезапись родительской структуры:UPDATE company_custom_menus SET menu_title = $1, updated_at = NOW() WHERE id = $2. - Шаг 8 (
MS -> DB): Полная очистка старой карты (Orphan Removal): Чтобы предотвратить появление «мусорных» или сиротских строк, которые были в старом меню, но отсутствуют в новом запросе, СУБД выполняет команду:DELETE FROM company_menu_items WHERE menu_template_id = $1. Старая матрица продуктов полностью вычищается. - Шаг 9 (
MS -> DB): Запись нового состояния (Bulk Insert): Бэкенд итерирует новый входящий массивitems_matrixи для каждого продукта выполняет команду вставки:INSERT INTO company_menu_items (menu_template_id, product_name, required_quantity, unit) VALUES ($1, $2, $3, $4). - Шаг 10 (
MS -> API): После успешного завершения всех вставок транзакция фиксируется в базе данных командойCOMMIT. Новое состояние технологической карты становится единственно верным. - Шаг 11 (
MS -> KAFKA): Сервис Меню отправляет асинхронное бизнес-событие в топик Kafkamasterdata.menu.mutated.v1с типом"MENU_REPLACED". Это дает сигнал ИИ-сервису аналитики полностью сбросить старый кэш структуры этого меню и пересчитать КБЖУ под новый суп-харчо. - Шаг 12 (
MS -> APP): Сервер возвращает мобильному приложению статусHTTP 200 OK. Во Flutter-приложении список меню обновляется, старая карточка бургеров исчезает, и вместо неё сотрудники видят актуальный обед четверга.
5 Спецификация ответов сервера (Response Body) и ошибок
5.1 1. Успешный ответ (Success Response)
5.1.1 HTTP 200 OK (Ответ на Шаге 12)
Возвращается мобильному приложению после успешного коммита транзакции полной перезаписи. В теле ответа фиксируются новые метаданные и статус замещения технологической карты.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "MENU_TEMPLATE_FULLY_REPLACED",
"data": {
"menu_template_id": 102,
"home_group_id": "company-office-center",
"new_menu_title": "Обед Четверг: Суп-харчо и Лаваш",
"inserted_items_count": 3,
"timestamp": "2026-07-02T00:52:00Z"
}
}5.2 2. Вилки исключений и обработка ошибок
5.2.1 Ошибка: Шаблон меню не найден при попытке перезаписи (HTTP 404 Not Found — Шаг 5)
Выбрасывается на Шаге 5, если переданный в URL id кастомного меню полностью отсутствует в таблице company_custom_menus. Метод PUT не создает новую запись, если целевой ID не существует (для этого зарезервирован метод POST).
{
"error_code": "ERR-PUT-TARGET-NOT-FOUND",
"message": "Целевой шаблон меню для перезаписи не найден в системе.",
"details": {
"requested_id": 102,
"reason": "Select for update returned 0 rows. Entity cannot be replaced."
}
}5.2.2 Ошибка: Превышение максимального лимита позиций в меню (HTTP 400 Bad Request — Шаг 4)
Выбрасывается на Шаге 4, если новый массив items_matrix содержит более 15 элементов. Архитектурный лимит защищает систему от перегрузки на этапе каскадного удаления и повторного Bulk Insert.
{
"error_code": "ERR-MENU-ITEMS-LIMIT-EXCEEDED",
"message": "Превышен лимит позиций. Новая технологическая карта меню не может содержать более 15 ингредиентов.",
"details": {
"current_items_count": 19,
"max_allowed_limit": 15
}
}5.2.3 Ошибка нарушения бизнес-профиля аккаунта (HTTP 403 Forbidden — Шаг 3)
Выбрасывается бэкенд-шлюзом, если метод вызван под учетной записью с типом HOME.
{
"error_code": "ERR-OFFICE-CONTEXT-REQUIRED",
"message": "Доступ заблокирован: данный эндпоинт предназначен исключительно для корпоративных учетных записей предприятий.",
"details": {
"current_account_type": "HOME"
}
}