sequenceDiagram
autonumber
actor Employee as Сотрудник / Менеджер
participant APP as Мобильное приложение (Flutter)
participant MS as Сервис Меню (FastAPI)
participant DB as База Данных (PostgreSQL)
Employee->>APP: Переходит на экран "Выбор Меню / Обеды"
APP->>MS: HTTP GET /api/v1/menu/templates?limit=20 (JWT в Header)
activate MS
note over MS: Шаг 3: Валидация JWT (account_type == OFFICE)
MS->>DB: Вызов SQL-запроса UNION по home_group_id с пагинацией
activate DB
note over DB: Шаг 5: Выборка из master_menu_templates со scope='SYSTEM'
note over DB: Шаг 6: Выборка из company_custom_menus по home_group_id со scope='COMPANY'
note over DB: Шаг 7: Объединение, сортировка по дате и LIMIT 20
DB-->>MS: Возвращает агрегированный плоский массив шапок меню
deactivate DB
MS-->>APP: HTTP 200 OK (JSON со списком гибридных шаблонов)
deactivate API
note over APP: Экран отрисовывает карточки: у системных меню заблокированы кнопки "Удалить/Изменить"
Метод GET /api/v1/menu/templates
Документация API: Выгрузка и агрегация глобальных и кастомных корпоративных меню с поддержкой пагинации
1 Функциональное назначение
Метод предназначен для вывода полного списка доступных комплексных меню на экране мобильного приложения Flutter. Он используется сотрудниками и менеджерами предприятий для выбора сета перед обедом или для администрирования.
Метод решает три ключевые задачи: 1. Гибридный UNION-вывод: Объединяет в единый упорядоченный список глобальные неизменяемые мастер-меню (master_menu_templates), доступные всем компаниям в системе, и кастомные меню (company_custom_menus), принадлежащие строго текущему предприятию (home_group_id). 2. Маркировка происхождения (scope): Каждая запись в JSON-ответе помечается флагом происхождения ("SYSTEM" или "COMPANY"). Это позволяет Flutter-интерфейсу блокировать кнопки редактирования/удаления для системных меню и разрешать их для личных. 3. Безопасная пагинация (Safe Fetch): Ограничивает размер выборки через Query-параметры limit/offset (Максимум = 100), предотвращая просадки производительности СУБД.
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
GET - Маршрут:
/api/v1/menu/templates - Формат данных:
application/json(только ответ)
2.1 Спецификация параметров запроса (Query Parameters)
| Параметр | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
limit |
Integer | Нет | Количество возвращаемых шаблонов меню (Максимум = 100) |
20 |
offset |
Integer | Нет | Смещение для постраничного бесконечного скролла | 0 |
2.2 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Authorization |
Да | Токен авторизации (Access Token). Бэкенд извлекает home_group_id и проверяет контекст OFFICE. |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для замера скорости тяжелого объединяющего запроса | req-menu-read-22bb |
2.3 Спецификация структуры ответа (Response Body)
Метод возвращает плоский массив шапок доступных меню. Вложенная матрица ингредиентов здесь не выгружается для экономии трафика; для просмотра состава используется точечный gRPC-канал или отдельный детальный эндпоинт.
| Поле | Тип | Описание | Пример значения |
|---|---|---|---|
template_id |
Integer | Уникальный ID шаблона меню в рамках его таблицы | 102 |
scope |
String | Контекст происхождения шаблона (SYSTEM или COMPANY) |
"COMPANY" |
2.3.1 Пример JSON-ответа (Payload):
{
"home_group_id": "company-office-center",
"total_count": 15,
"limit": 20,
"offset": 0,
"templates": [
{
"template_id": 1,
"scope": "SYSTEM",
"menu_title": "Завтрак Фитнес: Овсянка и Ягоды",
"created_at": "2026-01-01T10:00:00Z"
},
{
"template_id": 102,
"scope": "COMPANY",
"menu_title": "Пятничный Бургер-День v2",
"created_at": "2026-07-01T23:28:00Z"
}
]
}3 Схема обработки запроса пользователя (Mermaid)
На диаграмме представлена логика параллельного извлечения данных из таблиц глобальных системных шаблонов и кастомных меню конкретной компании с использованием оператора UNION на уровне СУБД.
4 Расшифровка шагов
- Шаг 1 (
Employee -> APP): Пользователь (сотрудник офиса во время обеда или менеджер в административной панели) открывает вкладку «Корпоративные обеды / Меню». Мобильное приложение Flutter мгновенно инициирует фоновый HTTP-запрос для вывода доступных шаблонов. - Шаг 2 (
APP -> MS): Приложение отправляет запросHTTP GET /api/v1/menu/templates?limit=20&offset=0. Параметры пагинации передаются в Query String. В заголовкеAuthorizationпрокидывается Access Token. - Шаг 3 (
MS -> MS): Микросервис Меню (FastAPI) расшифровывает JWT-токен и проверяет клеймaccount_type. Если запрос отправлен из домашнего контура (HOME), сервис возвращает ошибкуHTTP 403 Forbidden, так как контур комплексных меню зарезервирован исключительно для предприятий. - Шаг 4 (
MS -> DB): Шлюз отправляет в базу данныхPostgreSQLоптимизированный SQL-запрос, связывающий две независимые таблицы через механизм сквозного объединения. - Шаг 5 (
DB -> DB): На первом этапе СУБД сканирует таблицу глобального справочника диетологовmaster_menu_templates. Из неё извлекаются записи, доступные абсолютно всем пользователям системы без фильтрации по компании, и жестко маркируются текстовой константой'SYSTEM'в полеscope. - Шаг 6 (
DB -> DB): На втором этапе СУБД сканирует таблицу кастомного инвентаря менюcompany_custom_menus, применяя жесткий фильтр по индексуhome_group_id = $1. Полученные строки маркируются текстовой константой'COMPANY'в полеscope. Это гарантирует, что офис увидит свои уникальные бургер-дни или плов-дни, скрытые от других компаний. - Шаг 7 (
DB -> DB): СУБД выполняет операторUNION ALL, склеивая оба потока данных в единую плоскую таблицу. К итоговому набору применяется глобальная сортировкаORDER BY created_at DESC, а также переданные параметры безопасностиLIMIT 20 OFFSET 0. - Шаг 8 (
DB -> MS): База данных возвращает бэкенду отсортированный гибридный массив шапок меню, не превышающий лимит размера страницы. - Шаг 9 (
MS -> APP):FastAPIупаковывает результат в Pydantic-модель списка и возвращает клиенту статусHTTP 200 OK. Мобильное приложение Flutter принимает JSON-пакет. Стейт-менеджер перерисовывает экран: благодаря флагуscopeинтерфейс четко разграничивает системные карточки и личные шаблоны компании, блокируя элементы управления (кнопки «Изменить», «Удалить») на системных меню.
5 Спецификация серверной логики (SQL) и вилок исключений
5.1 1. Архитектурный SQL-запрос сквозного объединения (Шаги 5–7)
Для реализации бизнес-логики (параллельное извлечение общесистемных меню и кастомных шаблонов конкретного офиса в единую ленту выдачи) на уровне PostgreSQL используется оператор UNION ALL. Данное решение работает быстрее стандартного UNION, так как не тратит вычислительные ресурсы сервера на поиск и исключение дубликатов между заведомо изолированными таблицами.
(
-- Шаг 5: Выгрузка глобальных системных шаблонов меню
SELECT
id AS template_id,
'SYSTEM' AS scope, -- Хардкод-маркер происхождения для UI Flutter
menu_title,
created_at
FROM master_menu_templates
)
UNION ALL
(
-- Шаг 6: Выгрузка уникальных кастомных шаблонов конкретного предприятия
SELECT
id AS template_id,
'COMPANY' AS scope, -- Хардкод-маркер происхождения для UI Flutter
menu_title,
created_at
FROM company_custom_menus
WHERE home_group_id = \$1 -- Multi-Tenancy изоляция данных
)
-- Шаг 7: Глобальная сортировка объединенного потока данных и пагинация
ORDER BY created_at DESC
LIMIT \$2
OFFSET \$3;5.2 2. Спецификация ответов сервера (Response Body)
5.2.1 HTTP 200 OK (Ответ на Шаге 9)
Возвращается при успешной выборке гибридного реестра. Отсутствие вложенного массива items_matrix гарантирует компактность payload и микросекундный парсинг JSON-пакета на мобильном устройстве.
{
"home_group_id": "company-office-center",
"total_count": 2,
"limit": 20,
"offset": 0,
"templates": [
{
"template_id": 102,
"scope": "COMPANY",
"menu_title": "Пятничный Бургер-День v2",
"created_at": "2026-07-01T23:28:00Z"
},
{
"template_id": 1,
"scope": "SYSTEM",
"menu_title": "Завтрак Фитнес: Овсянка и Ягоды",
"created_at": "2026-01-01T10:00:00Z"
}
]
}5.3 3. Вилки исключений и обработка ошибок
5.3.1 Ошибка: Превышение системного лимита размера страницы (HTTP 400 Bad Request)
Выбрасывается на Шаге 3 бэкенд-шлюзом FastAPI, если клиент передал в Query-параметрах limit со значением больше 100, нарушая архитектурный стандарт безопасности API.
{
"error_code": "ERR-MENU-FETCH-LIMIT-EXCEEDED",
"message": "Превышен максимальный лимит размера страницы (максимум — 100 шаблонов меню за один запрос).",
"details": {
"requested_limit": 500,
"allowed_max_limit": 100
}
}5.3.2 Ошибка: Невалидный формат смещения пагинации (HTTP 422 Unprocessable Entity)
Выбрасывается, если клиент передал в Query-параметрах отрицательное значение смещения (offset=-20), нарушающее встроенную валидацию типов Pydantic.
{
"error_code": "ERR-VALIDATION-FAILED",
"message": "Передан некорректный параметр смещения offset страницы.",
"details": [
{
"loc": ["query", "offset"],
"msg": "Input should be greater than or equal to 0",
"type": "less_than_equal_to_failed"
}
]
}