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 POST /api/v1/menu/custom (JSON структуры меню)
activate MS
note over MS: Шаг 3: Валидация JWT (account_type == OFFICE)
note over MS: Шаг 4: Валидация лимита элементов (макс. 15 позиций)
Note over MS, DB: Старт атомарной SQL-транзакции
MS->>DB: INSERT INTO company_custom_menus (home_group_id, menu_title)
activate DB
DB-->>MS: Возвращает сгенерированный menu_template_id
deactivate DB
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 201 Created (JSON с ID нового меню)
deactivate MS
note over APP: Интерфейс очищается, новое меню добавляется в список доступных обедов
Метод POST /api/v1/menu/custom
Документация API: Создание нового кастомного шаблона меню компании (Create) с регистрацией структуры в СУБД
1 Функциональное назначение
Метод предназначен для проектирования и первичного сохранения нового кастомного шаблона комплексного меню менеджером предприятия (account_type == OFFICE).
Метод решает следующие задачи: 1. Регистрация новой технологической карты: Принимает текстовое название меню и массив ингредиентов (наименования, веса, штуки), которые сотрудники офиса будут списывать за один клик при обеде. 2. Автоматическая изоляция (Multi-Tenancy): Намертво привязывает создаваемый шаблон к home_group_id текущего офиса, извлеченному из JWT-токена. Никакая другая компания в системе не сможет увидеть или использовать этот шаблон. 3. Первичный аудит лимитов: Ограничивает максимальное количество блюд внутри одного меню (не более 15 позиций), чтобы защитить СУБД от раздувания и гарантировать быструю gRPC-передачу данных в будущем.
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/menu/custom - Формат данных:
application/json
2.1 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Указывает на передачу строго типизированного JSON-пакета | application/json |
Authorization |
Да | Токен авторизации (Access Token) для проверки прав администратора офиса |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для связывания логов создания сущности | req-menu-create-11aa |
2.2 Спецификация тела запроса (Request Body)
В массиве items_matrix менеджер передает канонические имена продуктов из мастер-словарей, которые необходимы для сборки этого меню.
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
menu_title |
String | Да | Понятное название комплексного обеда для сотрудников | "Обед Среда: Плов и Компот" |
items_matrix |
Array | Да | Список канонических продуктов, весов/штук и единиц измерения | [..._name: "Рис", qty: 0.200] |
2.2.1 Пример сырого JSON-запроса (Payload):
{
"menu_title": "Обед Среда: Плов и Компот",
"items_matrix": [
{
"product_name": "Рис лазер каноничный",
"required_quantity": 0.2000,
"unit": "кг"
},
{
"product_name": "Мясо говядина",
"required_quantity": 0.1500,
"unit": "кг"
},
{
"product_name": "Компот ягодный готовый",
"required_quantity": 1.0000,
"unit": "шт"
}
]
}3 Схема обработки запроса пользователя (Mermaid)
На диаграмме представлена логика создания нового кастомного меню компании. Происходит атомарная запись в родительскую и дочернюю таблицы Базы Данных с жесткой привязкой к контексту корпоративного home_group_id из JWT.
4 Расшифровка шагов
- Шаг 1 (
Manager -> APP): Менеджер предприятия открывает конструктор корпоративного меню во Flutter-приложении, вводит название («Обед Среда: Плов и Компот»), добавляет строки ингредиентов с указанием их канонических имен, точных весов или штук и нажимает кнопку «Создать шаблон». - Шаг 2 (
APP -> MS): Приложение через клиентDioотправляет запросHTTP POST /api/v1/menu/custom. Все структурированные данные упаковываются в 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, предотвращая избыточную нагрузку на СУБД и будущие gRPC-вызовы. - Шаг 5 (
MS -> DB): Сервис Меню открывает атомарную SQL-транзакцию вPostgreSQL. Первым действием выполняется вставка в родительскую таблицу:INSERT INTO company_custom_menus (home_group_id, menu_title) VALUES ($1, $2) RETURNING id. Параметрhome_group_idберется строго из токена, что исключает фальсификацию данных. - Шаг 6 (
DB -> MS): База данных генерирует и возвращает уникальный последовательный идентификаторmenu_template_idдля созданной шапки меню. - Шаг 7 (
MS -> DB): Запускается цикл формирования технологической карты обеда (связь1:M). Бэкенд итерирует массивitems_matrixи для каждого продукта выполняет DML-команду множественной вставки:INSERT INTO company_menu_items (menu_template_id, product_name, required_quantity, unit) VALUES ($1, $2, $3, $4). - Шаг 8 (
MS -> API): После успешной записи всех дочерних строк транзакция фиксируется в базе данных командойCOMMIT. Новый шаблон становится доступен для использования. - Шаг 9 (
MS -> KAFKA): Сервис Меню публикует асинхронное бизнес-событие в топик Kafkamasterdata.menu.mutated.v1с типом"MENU_CREATED". Это уведомляет ИИ-сервер аналитики о появлении нового паттерна данных, который необходимо проиндексировать в кэше. - Шаг 10 (
MS -> APP): Сервер возвращает мобильному приложению статусHTTP 201 Createdс телом ответа, содержащим ID созданного меню. Во Flutter-приложении форма конструктора очищается, экран обновляется, и новый шаблон появляется в общем списке корпоративных обедов компании.
5 Спецификация ответов сервера (Response Body) и ошибок
5.1 1. Успешный ответ (Success Response)
5.1.1 HTTP 201 Created (Ответ на Шаге 10)
Возвращается мобильному приложению после успешного коммита транзакции создания. В теле ответа возвращаются системный ID нового шаблона меню и полные метаданные.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "MENU_TEMPLATE_CREATED",
"data": {
"menu_template_id": 105,
"home_group_id": "company-office-center",
"menu_title": "Обед Среда: Плов и Компот",
"items_count": 3,
"timestamp": "2026-07-02T00:46:00Z"
}
}5.2 2. Спецификация структуры таблиц PostgreSQL (Связь 1:M)
Для хранения кастомных шаблонов корпоративного меню используется реляционная схема, изолированная по индексу home_group_id.
-- Таблица 1: Шапки кастомных меню компаний (Родительская структура)
CREATE TABLE company_custom_menus (
id SERIAL PRIMARY KEY,
home_group_id VARCHAR(50) NOT NULL, -- UUID или строковый ID офиса/компании
menu_title VARCHAR(255) NOT NULL, -- Название («Обед Среда»)
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Таблица 2: Технологические карты меню (Дочерняя структура)
CREATE TABLE company_menu_items (
id SERIAL PRIMARY KEY,
menu_template_id INT NOT NULL REFERENCES company_custom_menus(id) ON DELETE CASCADE,
product_name VARCHAR(255) NOT NULL, -- Каноническое имя продукта
required_quantity NUMERIC(10, 4) NOT NULL, -- Вес или штуки
unit VARCHAR(20) NOT NULL -- "кг", "шт", "л"
);
-- Индекс для мгновенной выборки меню конкретного предприятия
CREATE INDEX idx_company_menus_group ON company_custom_menus(home_group_id);
-- Индекс для каскадных gRPC-вызовов по ID шаблона
CREATE INDEX idx_menu_items_template ON company_menu_items(menu_template_id);5.3 3. Вилки исключений и обработка ошибок
5.3.1 Ошибка: Превышение максимального лимита позиций в меню (HTTP 400 Bad Request — Шаг 4)
Выбрасывается на Шаге 4, если массив items_matrix содержит более 15 элементов. Это архитектурное ограничение защищает систему от раздувания payload и снижает задержки gRPC.
{
"error_code": "ERR-MENU-ITEMS-LIMIT-EXCEEDED",
"message": "Превышен лимит позиций. Один шаблон меню не может содержать более 15 ингредиентов.",
"details": {
"current_items_count": 18,
"max_allowed_limit": 15
}
}5.3.2 Ошибка валидации структуры (HTTP 422 Unprocessable Entity)
Выбрасывается на Шаге 3, если Pydantic-модель на FastAPI обнаружила отрицательные веса продуктов или пустое название меню.
{
"error_code": "ERR-VALIDATION-FAILED",
"message": "Переданный JSON-пакет не прошел валидацию схемы данных.",
"details": [
{
"loc": ["body", "items_matrix", 0, "required_quantity"],
"msg": "Значение необходимого объема должно быть строго больше 0.0000",
"type": "value_error.number.not_gt"
}
]
}