Драфт для общепита + аэропорт

Техническое задание: PWA-приложение для заказа еды в ресторанах аэропорта. iiko, ЮKassa, три способа получения, 4 месяца разработки.

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: ПРИЛОЖЕНИЕ РЕСТОРАНОВ В АЭРОПОРТУ

СВОДКА ПО ПРОЕКТУ

Что это: Веб-приложение (PWA) в нативной оболочке WebView для заказа еды в ресторанах аэропорта. Оболочка сохраняет сессию — пользователь авторизуется один раз. Доступно через браузер (PWA) и в магазинах приложений (оболочка). Пользователи — пассажиры с ограниченным временем. Рестораны подключаются через iiko (меню, заказы на кухню, стоп-листы, фискализация). Все заказы с предоплатой 100% через ЮKassa (карты, СБП, SberPay, T-Pay).

Три способа получения: самовывоз (забрать в ресторане), доставка к гейту (курьер к выходу на посадку), предзаказ (бронь стола + оплата заранее, еда подаётся сразу).

Кто делает: PM + дизайнер + 2 бэкендера + 2 фронтендера (PWA) + 1 фронтендер (админка) + QA + DevOps.

Сроки: 4 месяца (активная разработка + интеграция). Добавление ресторанов через API не требует доработки продукта.

Стек: HTML/CSS/JS (PWA, SPA), Capacitor (WebView оболочка), React или Vue (админка), Node.js/Express или Django (бэкенд), PostgreSQL, Redis, iikoTransport API, ЮKassa.

Архитектура: PWA (веб-ядро) -> Backend API -> iiko (меню + заказы) + ЮKassa (платежи). Нативная оболочка (Capacitor/WKWebView/WebView) для сессии и публикации в магазинах. Service Worker для кэширования и офлайн-доступа. WebSocket для статусов заказа и чата в реальном времени.

iiko: Каждый ресторан подключается отдельно через админ-панель (API-ключи). Меню и стоп-листы синхронизируются автоматически. Заказы идут на KDS и принтеры ресторана. Статусы через webhook или polling.

Возврат: Полный (отмена пользователем или рестораном) или частичный (блюдо удалено). Возврат через ЮKassa на карту или по СБП. Срок — до 3 рабочих дней. Антифрод — не более 5 отмен в час.

Безопасность: JWT + refresh token, HTTPS/TLS 1.3, 3D Secure 2.0, PCI DSS Level 1 (ЮKassa), шифрование PII (AES-256), rate limiting.

iiko: Каждый ресторан подключается отдельно через админ-панель (API-ключи). Меню и стоп-листы синхронизируются автоматически. Заказы идут на KDS и принтеры ресторана. Статусы через webhook или polling.

Возврат: Полный (отмена пользователем или рестораном) или частичный (блюдо удалено). Возврат через ЮKassa на карту или по СБП. Срок — до 3 рабочих дней. Антифрод — не более 5 отмен в час.

Безопасность: JWT + refresh token, HTTPS/TLS 1.3, 3D Secure 2.0, PCI DSS Level 1 (ЮKassa), шифрование PII (AES-256), rate limiting.


1. ОБЩЕЕ ОПИСАНИЕ

1.1 Концепция

Приложение для заказа еды в ресторанах аэропорта, ориентированное на пассажиров с ограниченным временем между рейсами. Ключевое отличие — три сценария получения: самовывоз, доставка к гейту, предзаказ для обеда в ресторане без очереди.

1.2 Целевая аудитория

Первичная: Пассажиры ожидающие вылет (время ожидания 30–180 мин)
Вторичная: Сопровождающие и встречающие
Третичная: Сотрудники аэропорта

1.3 География запуска

MVP: 1 терминал, 3–5 ресторанов-партнёров
V2: Все терминалы аэропорта
V3: Сеть аэропортов

1.4 Ключевые метрики успеха (KPI)

Среднее время от заказа до получения: менее 15 мин
Конверсия просмотр → заказ: более 15%
Retention Day 7: более 30%
Средний чек: не ниже среднего чека ресторана офлайн
NPS (Net Promoter Score): более 50

1.5 Формат приложения

Приложение состоит из двух частей: веб-ядро (PWA) и нативная оболочка (WebView).

Веб-ядро (PWA):
- Одностраничное веб-приложение (SPA) на HTML/CSS/JS
- Доступно через браузер по URL для быстрого доступа без установки
- Service Worker для кэширования и офлайн-режима
- Push-уведомления через Push API + VAPID
- Манифест для установки на главный экран

Нативная оболочка (Wrapper):
- Тонкий WebView (iOS WKWebView, Android WebView), который загружает веб-ядро
- Размер оболочки: < 5 МБ
- Собирается через Capacitor (рекомендуется) или PWA Builder
- Публикуется в App Store и Google Play
- Единственная задача — держать сессию: WebView сохраняет cookie и localStorage между запусками, пользователь не вводит логин повторно
- Splash screen (логотип приложения) на время загрузки PWA
- Нативные push-уведомления (FCM/APNs) — опционально, можно через Service Worker
- Жёсткая привязка к единственному URL приложения (не браузер, нельзя ввести другой адрес)

Преимущества подхода:
- Весь интерфейс и логика — в веб-коде (один код для всех платформ)
- Обновления через веб — не требуют модерации магазинов приложений
- Сессия сохраняется в WebView (cookie/localStorage) между запусками
- Приложение выглядит как нативное (нет строки браузера, splash screen)
- Можно публиковать в App Store / Google Play для большей видимости

Сборка оболочки:
- Capacitor (рекомендуется): одна кодовая база, сборка под iOS и Android
  npx cap init && npx cap add ios && npx cap add android
  npx cap sync && npx cap open ios/android
- PWA Builder: генерация оболочки через CLI или веб-интерфейс
- Вручную: WKWebView (iOS) / WebView (Android) с загрузкой URL приложения

Технические требования к оболочке:
- iOS: WKWebView, JavaScript enabled, Cookie storage persistent, минимальная версия iOS 15+
- Android: WebView с Chrome, JavaScript enabled, DOM storage enabled, минимальная версия Android 8+
- Настройка User-Agent для идентификации запросов от оболочки
- Обработка деплинков: aiportfood:// для внешних переходов


2. ЦЕЛИ И ЗАДАЧИ

2.1 Бизнес-цели

- Увеличить выручку ресторанов-партнёров на 20% за счёт дополнительного канала продаж
- Сократить среднее время обслуживания пассажира в ресторане на 40%
- Повысить удовлетворённость пассажиров питанием в аэропорту (CSI +15 п.п.)

2.2 Продуктовые задачи

- Предоставить пассажиру полный цикл заказа за менее 30 секунд (от открытия до оплаты для повторного пользователя)
- Минимизировать шаги до первого заказа: цель — регистрация через соцсеть за 2 тапа
- Обеспечить прозрачность статуса заказа в реальном времени
- Дать ресторанам инструмент управления меню и заказами без необходимости в POS-терминале

2.3 Технические задачи

- Обеспечить Time To Interactive (TTI) менее 2 сек на мобильных устройствах среднего сегмента
- Поддержать пиковую нагрузку 1000 RPS (посадка широкофюзеляжного самолёта)
- Гарантировать доставку уведомлений с задержкой менее 5 сек
- Обеспечить отказоустойчивость — при падении бэкенда мобильное приложение показывает кэшированное меню 24 часа


3. ФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ

3.1 Мобильное приложение (пользователь)

3.1.1 Регистрация и профиль

Экраны:

1. Onboarding (3 шага)
   Шаг 1: "Закажи еду до посадки" — иллюстрация + кнопка "Начать"
   Шаг 2: "Выбери способ получения" — три карточки (самовывоз, доставка, предзаказ)
   Шаг 3: "Оплати и получай уведомления" — запрос разрешения на push + геолокацию

2. Регистрация
   - Вход по номеру телефона (SMS-код)
   - Вход через Google / Apple ID / ВКонтакте
   - Вход по email + пароль (опционально)
   - Кнопка "Пропустить" — гостевой режим с ограничением (до 3 заказов)

3. Профиль
   - Аватар, имя, телефон, email
   - История заказов (список + детали)
   - Избранные рестораны
   - Способы оплаты (сохранённые карты, СБП)
   - Настройки уведомлений
   - Текущий язык (русский/английский)

Валидация:
- Номер телефона: маска +7 (999) 999-99-99
- SMS-код: 6 цифр, автосабмит после заполнения
- Время жизни SMS-кода: 5 мин
- Повторная отправка: через 30 сек

API эндпоинты:
POST  /api/v1/auth/send-code        body: { phone }
POST  /api/v1/auth/verify-code      body: { phone, code }
POST  /api/v1/auth/social           body: { provider, token }
POST  /api/v1/auth/refresh          body: { refreshToken }
GET   /api/v1/auth/profile
PATCH /api/v1/auth/profile          body: { name, email, avatar }

3.1.2 Главный экран

Макет:
Строка поиска ресторанов сверху
Текущий терминал (определяется автоматически), например "Терминал D"
Фильтры по статусу: Открыто / Скоро
Категории: горизонтальный скролл иконок (бургеры, азия, кофе, салаты)
Блок "Рекомендуем" — горизонтальный скролл карточек ресторанов с фото
Блок "Все рестораны" — вертикальный список с рейтингом, временем приготовления, статусом

Функции:
- Автоопределение терминала по геолокации (GPS + Wi-Fi fingerprint)
- Ручной выбор терминала (Picker)
- Поиск ресторанов по названию/кухне (Debounce 300 мс)
- Сортировка: по рейтингу, по времени приготовления, по расстоянию
- Фильтр: открыто сейчас, принимает предзаказы

API:
GET /api/v1/restaurants?terminal=D&status=open&cuisine=burger&page=1&limit=20

3.1.3 Экран ресторана

Макет:
Шапка: кнопка назад + название ресторана
Фото ресторана 600x300
Рейтинг, количество отзывов, время приготовления, ценовой уровень
Информация: местоположение (D-24), часы работы, телефон
Меню: категории табами (Все, Салаты, Горячее, Десерты, Напитки) с горизонтальным скроллом
Список блюд: название, описание, цена, фото 80x80, кнопка "+"
Sticky bottom bar: корзина с количеством и суммой

Функции:
- Категории с вертикальным скроллом
- Pull-to-refresh для обновления меню
- Индикатор доступности блюда (в наличии / закончилось)
- Кнопка "+" добавляет в корзину с анимацией
- Долгий тап на "+" -> выбор количества (1–20)
- Алергены: значок рядом с блюдом
- Пищевая ценность: по тапу на блюдо (КБЖУ)

API:
GET /api/v1/restaurants/:id
GET /api/v1/restaurants/:id/menu?category=salads

3.1.4 Корзина

Экран:
Кнопка назад + заголовок "Корзина"
Список блюд: название, количество, цена, кнопки +/-/удалить
Комментарий к заказу (textarea)
Промокод: поле ввода + кнопка "Применить"
Итого: сумма, доставка, скидка, к оплате
Кнопка "Выбрать способ получения"

Функции:
- Изменение количества блюд (шаг 1)
- Удаление блюда (свайп влево или кнопка)
- Применение промокода
- Комментарий к заказу
- Авторасчёт доставки (бесплатно от суммы)
- Сlick&Collect — возможность выбрать другой ресторан (один заказ = один ресторан)
- Сохранение корзины при перезапуске приложения

API:
POST   /api/v1/cart          body: { restaurantId, items: [{menuItemId, quantity}] }
GET    /api/v1/cart
PATCH  /api/v1/cart/item     body: { menuItemId, quantity }
DELETE /api/v1/cart/item/:id
POST   /api/v1/cart/promo    body: { code }

3.1.5 Выбор способа получения

Три карточки:

Самовывоз
- Заберите в ресторане
- Готовность: ~10 мин
- Кнопка "Выбрать"

Доставка к гейту
- Пользователь указывает номер выхода на посадку (автоподстановка по геолокации)
- Выбирает время получения (дискретные слоты: через 15, 30, 45, 60 мин)
- Курьер (сотрудник ресторана или аутсорс) доставляет к указанному гейту
- Push-уведомление: "Ваш заказ доставлен к гейту D-24"
- Геометка прибытия курьера

Предзаказ в ресторане
- Бронь столика на определённое время
- Заказ уже оплачен, подаётся сразу по прибытии
- Можно выбрать количество персон
- Приоритетное обслуживание

API:
POST /api/v1/order/fulfillment   body: { orderId, type: 'pickup'|'delivery'|'preorder', details: {...} }

3.1.6 Оплата

Экран:
Сумма к оплате
Способы оплаты:
  - Банковская карта (Мир, Visa, Mastercard)
  - СБП (Система Быстрых Платежей)
  - ЮMoney
  - SberPay / T-Pay (через ЮKassa)
  - Наличные (в ресторане) — только для самовывоза и предзаказа
Детали заказа (список блюд + суммы)
Кнопка "Оплатить"

Интеграции платежей:
- ЮKassa (ЮMoney): карты Мир, Visa, Mastercard, СБП, SberPay, T-Pay
- Все платежи проходят через российских платёжных провайдеров
- Фискализация через онлайн-кассы (54-ФЗ) встроена в провайдера

Безопасность:
- Все платежи через ЮKassa (PCI DSS Level 1)
- Токенизация карт через ЮKassa
- 3D Secure 2.0 для всех онлайн-платежей
- Никакие данные карт не хранятся на сервере приложения

API:
POST /api/v1/order/:id/pay      body: { paymentMethodId, saveCard: true }
POST /api/v1/payment/methods    body: { token }
DELETE /api/v1/payment/methods/:id

3.1.7 Статус заказа

Экран отслеживания:
Номер заказа
Название ресторана
Текущий статус с иконкой
Timeline:
  - Заказ принят (время)
  - Готовится (текущий шаг, анимация пульсации)
  - Готов к выдаче
  - Завершён
Таймер обратного отсчёта
Кнопка "Чат с рестораном"
Кнопка "Отменить заказ" (доступна только до статуса "Готовится")

Уведомления (4 push):
1. "Заказ принят" — сразу после оплаты
2. "Готовится" — администратор начал готовить
3. "Готов к выдаче / Доставлен" — для самовывоза/доставки
4. "Приятного аппетита" — после завершения + запрос оценки

3.1.8 Чат с рестораном

- Чат доступен после подтверждения заказа
- Сообщения хранятся 7 дней после завершения заказа
- Отправка текста + изображений (до 10 МБ)
- Push-уведомление при новом сообщении от ресторана
- Быстрые ответы: "Не могу найти ресторан", "Где мой заказ?", "Отменить, пожалуйста"

API:
POST /api/v1/order/:id/chat      body: { text, image? }
GET  /api/v1/order/:id/chat?page=1&limit=50

3.1.9 Отзывы

- После завершения заказа -> экран оценки (1–5 звёзд + текст)
- Возможность оценить ресторан и отдельные блюда
- Фото в отзыве (до 3 шт.)
- Модерация: автоматическая фильтрация мата, ручная — для подозрительных

API:
POST /api/v1/restaurants/:id/reviews   body: { rating, text, photos?, menuItemRatings?: [...] }
GET  /api/v1/restaurants/:id/reviews?page=1

3.2 Административная панель (веб + мобильная)

3.2.1 Авторизация

- Вход по email + пароль
- Доступ по ролям: admin (полный доступ), manager (меню + заказы), viewer (только просмотр)
- Двухфакторная аутентификация (TOTP)

3.2.2 Дашборд

Виджеты:
1. Заказы сегодня (всего / активные / завершённые)
2. Выручка сегодня (всего / средний чек / прогноз до закрытия)
3. Среднее время приготовления (факт vs план)
4. Рейтинг сегодня (средний за день)
5. График заказов по часам (сравнение с предыдущим днём)
6. Топ-5 популярных блюд
7. Список текущих заказов с действиями

3.2.3 Управление меню

CRUD для категорий:
{ id, name, sortOrder, image?, isActive }

CRUD для блюд:
{ id, categoryId, name, description, price, image, isAvailable, sortOrder, allergens, nutritionalInfo: { kcal, protein, fat, carbs }, prepTime, modifiers? }

Экран редактирования блюда:
Фото + кнопка "Загрузить фото"
Поля: Название, Описание, Категория (выпадающий список), Цена, Время приготовления
Переключатель "Доступно"
Алергены (чекбоксы)
КБЖУ (поля ввода)
Кнопки: "Сохранить" / "Отмена"

Bulk-операции:
- Импорт меню из Excel/CSV
- Экспорт меню в Excel
- Копирование меню из другого ресторана сети

API:
GET    /api/v1/admin/menu/categories
POST   /api/v1/admin/menu/categories   body: { name, image?, sortOrder }
PATCH  /api/v1/admin/menu/categories/:id
DELETE /api/v1/admin/menu/categories/:id
GET    /api/v1/admin/menu/items
POST   /api/v1/admin/menu/items
PATCH  /api/v1/admin/menu/items/:id
DELETE /api/v1/admin/menu/items/:id
POST   /api/v1/admin/menu/import       body: { file: CSV }

3.2.4 Управление заказами

Список заказов с табами: Новые / Готовятся / Готовы / Все
Каждая карточка заказа: номер, время, способ получения, статус, список блюд, таймер
Детали заказа (модалка): информация о пользователе, блюда, оплата, способ получения, таймер

Действия над заказом:
- Подтвердить (принять в работу)
- Отметить готовым
- Завершить
- Отменить (с указанием причины — нет ингредиентов, закрылись)
- Связаться с клиентом (чат)
- Перенаправить на другой ресторан

API:
GET    /api/v1/admin/orders?status=new&page=1
GET    /api/v1/admin/orders/:id
PATCH  /api/v1/admin/orders/:id/status   body: { status }

3.2.5 Управление рестораном

- Часы работы (основные + исключения)
- Статус (открыт / закрыт / перерыв)
- Время приготовления по умолчанию
- Настройка радиуса доставки (по умолчанию 500 м от ресторана)
- Стоимость доставки и бесплатный порог
- Информация: название, адрес, телефон, фото
- Связь с терминалом и номером зоны

API:
GET    /api/v1/admin/restaurant
PATCH  /api/v1/admin/restaurant   body: { ... }

3.2.6 Отчёты и аналитика

Доступные отчёты:
1. Продажи: за сегодня / неделю / месяц / произвольный период, по дням недели, по часам, динамика среднего чека
2. Популярность блюд: топ-10 по продажам, наименее продаваемые, конверсия просмотр -> заказ
3. Эффективность: среднее время приготовления, количество отмен, загруженность по часам
4. Финансы: выручка брутто / нетто, комиссия платёжных систем, налоговый отчёт
5. Отток блюд: анализ блюд которые часто заказывают но не оплачивают

Экспорт: PDF, Excel (.xlsx), CSV

API:
GET /api/v1/admin/reports/sales?from=&to=&groupBy=day
GET /api/v1/admin/reports/top-items?from=&to=&limit=10
GET /api/v1/admin/reports/performance?from=&to=

3.2.7 Пользователи (суперадмин)

- Список пользователей приложения
- Блокировка / разблокировка
- История заказов пользователя
- Роли администраторов: superadmin, owner, manager, viewer
- Логи действий администраторов (audit trail)

API:
GET    /api/v1/admin/users?page=1
PATCH  /api/v1/admin/users/:id   body: { isBlocked: true }
GET    /api/v1/admin/admins
POST   /api/v1/admin/admins      body: { email, role }
PATCH  /api/v1/admin/admins/:id  body: { role }

3.3 Интеграция с iiko

3.3.1 Назначение интеграции

iiko — это система автоматизации ресторанов (POS/ERP), которая управляет меню, ценами, складом, производством, доставкой, оплатами и отчётами. Интеграция с iiko позволяет:

- Автоматически синхронизировать меню и цены из iiko в приложение (без ручного ввода в админке)
- Отправлять заказы из приложения напрямую на кухню через iiko (печать чеков на R-Keeper, отображение в KDS)
- Получать актуальный статус заказа (готовится, готов, отменён) из iiko в реальном времени
- Сверять остатки (стоп-лист) и скрывать недоступные блюда
- Вести единый финансовый учёт: все заказы проходят через кассу iiko (фискализация, налоги, отчёты)
- Использовать существующую инфраструктуру ресторана (сканеры QR, принтеры, KDS, кассы)

3.3.2 Версии iiko и способы подключения

iikoCloud (рекомендуемая):
- Облачная версия, доступ через REST API (iikoTransport)
- Не требует установки сервера в ресторане
- Поддерживает webhook для событий в реальном времени
- Аутентификация: API-ключ (токен) из личного кабинета iikoCloud
- Документация: https://api-ru.iiko.services

iikoServer (on-premise):
- Локальная установка в ресторане
- Доступ через iikoTransport API или напрямую через COM-интерфейс (legacy)
- Требует статического IP или VPN-туннеля
- Для подключения через интернет нужен iikoTransport

iikoTransport (общий транспортный слой):
- Прослойка между приложением и iiko (как Cloud, так и Server)
- REST API, формат JSON
- Авторизация по токену
- Синхронные и асинхронные запросы (через correlationId)

3.3.3 Сценарии интеграции

Сценарий 1: Синхронизация меню

1. Система отправляет GET-запрос в iikoTransport /api/1/nomenclature
2. iiko возвращает дерево номенклатуры: группы (категории) + блюда (товары) + модификаторы
3. Бэкенд приложения маппит данные iiko в свою БД:
   iiko-группа -> menu_categories
   iiko-блюдо -> menu_items
   iiko-модификатор -> modifiers (JSONB)
4. При каждом обновлении (по расписанию или по вебхуку) система проверяет revisionId:
   - Если revision совпадает — меню не менялось, пропускаем
   - Если revision отличается — загружаем полное меню или дельту
5. Цены и остатки обновляются отдельным запросом: /api/1/stop_lists

Периодичность:
- Полная синхронизация: 1 раз в час
- Синхронизация цен и стоп-листов: каждые 5 минут
- Webhook от iiko: моментально при изменении меню

Сценарий 2: Отправка заказа на кухню

Когда пользователь оплатил заказ в приложении:

1. Бэкенд создаёт заказ в своей БД (статус: pending)
2. Бэкенд отправляет заказ в iiko через /api/1/deliveries/create или /api/1/orders/create
   Тело запроса содержит:
   - Информацию о заказе: номер, список блюд с модификаторами, количество
   - Данные клиента: имя, телефон
   - Тип доставки: самовывоз / доставка / предзаказ
   - Время желаемой готовности (для предзаказа)
   - Комментарий
3. iiko принимает заказ, фискализирует чек (если оплачено онлайн), отправляет на KDS (кухонный дисплей) и на принтер
4. iiko возвращает orderId (iiko_order_id) для дальнейшего отслеживания
5. Бэкенд сохраняет iiko_order_id в записи заказа и обновляет статус на "confirmed"

Сценарий 3: Получение статуса заказа из iiko

Вариант A — Webhook (рекомендуемый):
1. iiko отправляет POST-запрос на настроенный URL (вебхук) при изменении статуса заказа
2. Тело вебхука: { "organizationId": "...", "orderId": "...", "status": "cancelled", "revision": 123 }
3. Бэкенд обновляет статус заказа и отправляет push-уведомление пользователю
4. Настройка вебхука: в личном кабинете iikoCloud -> Настройки API -> Webhook URL

Вариант B — Polling:
1. Бэкенд периодически опрашивает iiko: GET /api/1/deliveries/by_id?order_id=...
2. iiko возвращает текущий статус: "New", "Waiting", "Cooking", "Ready", "Delivered", "Closed", "Cancelled"
3. Маппинг статусов: iiko -> приложение
4. Частота опроса: каждые 15 секунд для активных заказов

Маппинг статусов iiko:
iiko "New" -> приложение "confirmed" (подтверждён)
iiko "Waiting" -> приложение "confirmed" (ожидает)
iiko "Cooking" -> приложение "cooking" (готовится)
iiko "Ready" -> приложение "ready" (готов)
iiko "Delivered" -> приложение "delivered" (доставлен)
iiko "Closed" -> приложение "completed" (завершён)
iiko "Cancelled" -> приложение "cancelled" (отменён)

Сценарий 4: Отмена заказа

1. Пользователь нажимает "Отменить" в приложении (доступно только до статуса "cooking")
2. Бэкенд отправляет запрос в iiko: POST /api/1/deliveries/cancel { orderId: "...", cancelCause: "...", removeFromQueue: true }
3. iiko отменяет заказ, снимает его с производства, возвращает результат
4. Бэкенд обновляет статус, инициирует возврат платежа (refund)
5. Пользователь получает push: "Заказ отменён. Возврат средств осуществляется в течение 3–5 дней"

Сценарий 5: Стоп-лист (нет в наличии)

1. Ресторан в iiko отмечает блюдо как "закончилось" / "стоп"
2. iiko отправляет webhook или бэкенд при следующем поллинге получает stop_list:
   GET /api/1/stop_lists?organizationIds={orgId}
   Ответ: { "organizationId": "...", "items": [{ "productId": "...", "balance": 0, "isDeleted": false }] }
3. Бэкенд обновляет поле is_available = false для соответствующих menu_items
4. В приложении блюдо помечается как "Нет в наличии", кнопка "+" неактивна
5. Если блюдо уже в корзине пользователя — показываем предупреждение: "Блюдо закончилось. Удалить из корзины?"

Сценарий 6: Получение информации о ресторане из iiko

1. Бэкенд запрашивает данные об организации: GET /api/1/organizations
   Ответ: список ресторанов с id, name, address, phone, типом, графиком работы
2. График работы парсится в JSONB поле schedule таблицы restaurants
3. Из iiko также получаем: валюту, налоги, типы оплат, терминалы

3.3.4 API iiko (iikoTransport) — основные эндпоинты (полный список — см. Приложение C)

Аутентификация по токену (POST /api/1/access_token), номенклатура (GET /api/1/nomenclature), заказы (POST /api/1/deliveries/create/cancel/by_id), стоп-листы, вебхуки. Лимит: 10 RPS на токен.

3.3.5 Архитектура интеграции

Мобильное приложение -> Бэкенд приложения -> iikoTransport API -> iikoCloud / iikoServer

Бэкенд приложения выступает в роли посредника (middleware):
- Хранит маппинг id: menu_item_id <-> iiko_product_id, order_id <-> iiko_order_id
- Кэширует меню и стоп-листы
- Обрабатывает вебхуки и транслирует статусы
- Обеспечивает отказоустойчивость: если iiko недоступен, заказ сохраняется в локальной БД и отправляется позже

БД: дополнительные поля и таблицы

В таблицу restaurants добавляется:
  iiko_org_id VARCHAR(255)  — ID организации в iiko
  iiko_terminal_group_id VARCHAR(255) — ID группы терминалов
  last_menu_revision BIGINT — ревизия меню

В таблицу menu_items добавляется:
  iiko_product_id VARCHAR(255) — ID продукта в iiko
  iiko_size_id VARCHAR(255) — ID размера (для блюд с размерами)

В таблицу orders добавляется:
  iiko_order_id VARCHAR(255) — ID заказа в iiko

Новая таблица iiko_tokens:
  id (UUID, PK), restaurant_id (FK), access_token, expires_at, created_at, updated_at

3.3.6 Обработка ошибок и компенсационные действия

Если iiko недоступен при создании заказа:
1. Заказ сохраняется в БД со статусом "pending_sync"
2. Бэкенд ставит задачу в очередь (RabbitMQ / Redis Queue) на повторную отправку
3. Повторные попытки: через 10 сек, 30 сек, 60 сек, 5 мин, 15 мин
4. Если после 5 попыток iiko не ответил:
   - Заказ помечается как "sync_failed"
   - Администратору ресторана отправляется уведомление (в админку и email)
   - Пользователю отправляется push: "Заказ принят, но возможна задержка в обработке"
   - В админке заказ отображается с жёлтым предупреждением "Не отправлен в iiko"
5. Ручная отправка: администратор может нажать "Отправить в iiko" в админ-панели

Если iiko отклонил заказ (например, блюда нет в стоп-листе, но iiko вернул ошибку):
1. Бэкенд парсит ошибку iiko (код + сообщение)
2. Заказ помечается статусом "rejected_by_iiko", причина сохраняется
3. Пользователю отправляется push: "Произошла ошибка при обработке заказа. Напишите в чат ресторану"
4. Администратор видит заказ с красным предупреждением и деталями ошибки
5. Администратор может вручную создать заказ в iiko или связаться с клиентом

3.3.7 Процесс подключения ресторана к iiko (пошагово)

Каждый ресторан подключается к iiko отдельно, со своими учётными данными. Процесс подключения происходит через административную панель и состоит из следующих шагов:

Шаг 1: Запрос учётных данных у ресторана
Администратор аэропорта (суперадмин) или владелец ресторана заполняет в админ-панели:
- Название ресторана (если ещё не создан)
- Версия iiko: Cloud / Server
- API Login (выдаётся iiko для каждого ресторана)
- API Key (секретный ключ, выдаётся iiko)
- Terminal Group ID (группа терминалов ресторана в iiko)

Шаг 2: Проверка соединения
Бэкенд отправляет POST-запрос в iikoTransport:
POST /api/1/access_token { apiLogin, apiKey }
- Если ответ успешный (токен получен) -> переход к шагу 3
- Если ошибка (неверный логин/пароль, iiko недоступен) -> показывается сообщение об ошибке, подключение не сохраняется

Шаг 3: Выбор организации
Бэкенд запрашивает список организаций, к которым имеет доступ данный API-ключ:
POST /api/1/organizations { organizationIds: null }
Ответ: массив организаций. Администратор выбирает нужную из выпадающего списка.
Если организация одна — выбирается автоматически.

После выбора организации запрашиваются группы терминалов:
POST /api/1/terminal_groups { organizationIds: [orgId] }
Администратор выбирает группу терминалов (обычно одна на ресторан).

Шаг 4: Генерация webhook URL
Бэкенд автоматически генерирует уникальный URL для webhook:
POST /api/admin/iiko/webhook/register { restaurantId }
Регистрирует этот URL в iikoCloud (для iikoServer webhook не поддерживается, используется polling).
Пример URL: https://api.airport-food.com/api/v1/webhooks/iiko/{restaurantId}

Шаг 5: Первичная синхронизация меню
Бэкенд запускает полную синхронизацию меню:
1. GET /api/1/nomenclature { organizationId, revisionId: 0 }
2. Полученное дерево номенклатуры преобразуется в категории и блюда
3. Каждое блюдо сохраняется с маппингом iiko_product_id
4. Цены и стоп-лист синхронизируются: POST /api/1/stop_lists
5. Администратору показывается результат: "Загружено N категорий, M блюд"

Шаг 6: Подтверждение и активация
- Все настройки сохраняются в таблице restaurants (зашифрованный API key)
- Статус iiko_status меняется на "connected"
- Запускается регулярная синхронизация по расписанию
- Ресторан появляется в списке доступных в мобильном приложении (если is_active = true)

Интерфейс страницы подключения в админ-панели:

Страница: Настройки ресторана -> Вкладка "iiko"

Если ресторан ещё не подключён:
[Форма подключения]
  Версия iiko: [Cloud ▼]
  API Login:  [________________________]
  API Key:    [________________________]
  [Проверить соединение]
  [Подключить]  — активна только после успешной проверки

Если ресторан подключён:
[Статус: Connected] [Отключить]
  Организация:  Pizza Express (Домодедово)
  Terminal ID:  TERMINAL_001
  Последняя синхронизация: 12:34:56
  Блюд в меню: 47
  Очередь заказов: 0
[Синхронизировать меню] [Синхронизировать стоп-лист]
[Настройки синхронизации]
  Интервал синхронизации меню: [60] мин
  Интервал синхронизации стоп-листов: [5] мин
  Webhook URL: https://api.airport-food.com/api/v1/webhooks/iiko/abc123
  [Скопировать]

3.3.8 Предоплата и финансовый поток

Все заказы в приложении оплачиваются заранее (предоплата 100%). Ниже описана схема прохождения платежа и взаимодействия с iiko.

Схема финансового потока:

Пользователь -> Платежный шлюз (ЮKassa) -> Бэкенд приложения -> iiko (регистрация платежа как внешнего)

Шаг 1: Пользователь оплачивает заказ в приложении
- Платеж проходит через ЮKassa (карта, СБП, ЮMoney)
- Деньги поступают на счёт платформы (мерчант в ЮKassa)
- Бэкенд получает подтверждение: payment.succeeded (вебхук от ЮKassa)
- Статус заказа: paid

Шаг 2: Заказ отправляется в iiko
Бэкенд создаёт заказ в iiko через:
POST /api/1/deliveries/create
В теле запроса передаётся информация о том, что заказ уже оплачен:
"payments": [{
  "paymentTypeId": "{id внешнего платежа}",
  "sum": 1650,
  "isProcessedExternally": true,
  "paymentData": {
    "paymentMethod": "bank_card",
    "provider": "yookassa",
    "transactionId": "2a3b4c..."
  }
}]
iiko регистрирует заказ как "Оплачено внешним платежом" — касса не требует повторной оплаты, фискальный чек формируется с пометкой "Электронный платёж".

Шаг 3: Фискализация
- iiko формирует фискальный чек (в соответствии с 54-ФЗ) на сумму заказа
- Чек отправляется на email пользователя (если указан) и сохраняется в истории заказов
- Если в iiko настроена онлайн-касса, чек пробивается автоматически при создании заказа
- Фискальные данные: { orderId, fiscalDriveNumber, fiscalDocumentNumber, fiscalSign, link }

Шаг 4: Комиссия платформы
- Платформа удерживает комиссию за обработку заказа (например, 5–10%)
- Расчёт с рестораном производится постфактум:
  - Ежедневно: перевод суммы заказов за вычетом комиссии
  - Или еженедельно / ежемесячно по договорённости
- Вариант 1: Платформа получает всю сумму -> переводит ресторану за вычетом комиссии
- Вариант 2: Деньги сразу идут ресторану, платформа выставляет счёт за комиссию (сложнее технически)

3.3.9 Возврат средств (refund)

Возврат может быть инициирован в трёх случаях:
A. Пользователь отменил заказ до начала приготовления
B. Ресторан отклонил заказ (нет ингредиентов, закрылись)
C. Системный сбой (двойной платёж, ошибка в сумме)

Сценарий A: Отмена пользователем

Условие: статус заказа в iiko — "New" или "Waiting" (ещё не начали готовить)

1. Пользователь нажимает "Отменить заказ" в мобильном приложении
2. Бэкенд проверяет статус заказа в iiko:
   POST /api/1/deliveries/by_id { organizationId, orderIds: [iiko_order_id] }
   Если статус "Cooking" или "Ready" — отмена недоступна, пользователю показывается сообщение: "Заказ уже готовится. Отмена невозможна. Свяжитесь с рестораном через чат."
3. Если статус позволяет отмену, бэкенд:
   3.1 Отменяет заказ в iiko:
       POST /api/1/deliveries/cancel { organizationId, orderId, cancelCause: "Cancelled by client", removeFromQueue: true }
   3.2 Инициирует возврат платежа через ЮKassa:
       POST /v1/refunds { payment_id: "2a3b4c...", amount: { value: "1650.00", currency: "RUB" } }
   3.3 iiko формирует чек возврата (фискальный)
   3.4 Статус заказа в БД: "cancelled"
4. Пользователь получает push:
   "Заказ #2841 отменён. Возврат ₽1650 обрабатывается. Средства поступят в течение 1–3 рабочих дней в зависимости от банка."
5. В истории заказов отображается статус "Отменён" с суммой возврата
6. Администратор ресторана видит в админке: заказ отменён, причина — "Клиент отменил"

Сценарий B: Отмена рестораном

Условие: ресторан не может выполнить заказ (блюдо закончилось, технический сбой, закрытие)

1. Администратор в админ-панели нажимает "Отменить заказ" и выбирает причину:
   - Нет в наличии ингредиентов
   - Ресторан закрыт (аварийно)
   - Техническая проблема
   - Другая причина (поле ввода)
2. Бэкенд:
   2.1 Отменяет заказ в iiko с указанием причины
   2.2 Инициирует полный возврат платежа через платежный шлюз
   2.3 Статус заказа: "cancelled_by_restaurant"
3. Пользователь получает push:
   "Ресторан Pizza Express не может выполнить заказ #2841. Возврат ₽1650 уже инициирован. Приносим извинения за неудобства."
   В push добавляется кнопка: [Выбрать другой ресторан] — которая ведёт на главный экран
4. В чате с рестораном автоматически отправляется сообщение от системы:
   "Заказ отменён рестораном. Причина: [причина]"
5. Администратор видит в дашборде отметку об отмене

Сценарий C: Частичный возврат

Если ресторан может выполнить заказ частично (например, одного блюда нет в наличии):

1. Администратор связывается с пользователем через чат
2. Администратор может предложить:
   - Заменить блюдо на аналогичное по цене
   - Удалить блюдо из заказа с частичным возвратом
3. Если пользователь соглашается на замену — администратор редактирует заказ в iiko
4. Если пользователь соглашается на частичный возврат:
   4.1 Администратор отмечает блюдо как недоступное в админке
   4.2 Бэкенд инициирует частичный возврат через ЮKassa:
       POST /v1/refunds { payment_id: "...", amount: { value: "450.00", currency: "RUB" } }
   4.3 Заказ продолжает готовиться с оставшимися блюдами
5. Пользователь получает push:
   "Блюдо 'Цезарь' удалено из заказа. Возврат ₽450 инициирован."

Техническая реализация возврата через ЮKassa:

Полный возврат: POST /v1/refunds { payment_id: "...", amount: { value: "1650.00", currency: "RUB" } }
Частичный возврат: POST /v1/refunds { payment_id: "...", amount: { value: "450.00", currency: "RUB" } }
Срок зачисления: 1–3 рабочих дня (зависит от банка-эмитента)
Комиссия ЮKassa за возврат: не взимается (комиссия за оригинальный платёж не возвращается)

СБП (возврат через СБП):
Если платеж прошёл через СБП, возврат осуществляется на тот же счёт
Срок зачисления: до 3 рабочих дней
Комиссия за возврат через СБП: 0%

Статусы возврата в БД:

В таблицу orders добавляется поле:
  refund_status VARCHAR(20) — none / pending / partial / completed / failed
  refund_amount DECIMAL(10,2) — сумма возврата
  refund_reason TEXT — причина возврата
  refunded_at TIMESTAMP — дата возврата

Жизненный цикл статусов возврата:
pending -> completed (успешно)
pending -> failed (ошибка, требуется ручное вмешательство)

При failed:
- Бэкенд отправляет уведомление администратору аэропорта (суперадмину)
- Администратор вручную обрабатывает возврат через личный кабинет ЮKassa
- В админке отображается: "Возврат не удался. Обработать вручную: [Ссылка на ЮKassa]"

Отображение в мобильном приложении:

На экране статуса заказа после возврата:
- Заказ отменён / частично возвращён
- Сумма возврата: ₽450
- Статус: "Средства возвращены" (или "Возврат обрабатывается")
- Чек возврата: [PDF]

На экране истории заказов:
- Заказ помечен иконкой ↺ и текстом "Возврат"
- При тапе — детали возврата

Блокировка повторного заказа после отмены:
- Пользователь может сразу сделать новый заказ (в том же или другом ресторане)
- При множественных отменах (> 5 в час) — временная блокировка на 1 час (антифрод)

3.3.10 Тестирование интеграции

1. Тест соединения: проверка, что токен валиден и iiko отвечает
2. Тест синхронизации меню: загрузить тестовое меню из iiko, проверить маппинг категорий и блюд
3. Тест создания заказа: создать тестовый заказ в приложении -> проверить что он появился в iiko (на KDS / в заказах)
4. Тест статусов: пройти все статусы в iiko -> проверить что в приложении статусы обновляются (через webhook и polling)
5. Тест стоп-листа: отметить блюдо как "стоп" в iiko -> проверить что в приложении оно отображается как недоступное
6. Тест отмены: отменить заказ в приложении -> проверить отмену в iiko и возврат платежа
7. Тест ошибок: отключить iiko -> создать заказ -> убедиться что заказ сохранился и встал в очередь
8. Нагрузочный тест: 50 одновременных заказов -> проверить что iiko обработал все


4. НЕФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ

4.1 Доступность (SLA)

Uptime (доступность API): 99.9%
Uptime мобильного приложения: работает офлайн (кэш)
Время восстановления (RTO): менее 1 часа
Recovery Point Objective (RPO): менее 5 мин
Плановое обслуживание: не чаще 1 раза в месяц, окно 02:00–04:00

4.2 Производительность

P95 время ответа API: менее 800 мс
P99 время ответа API: менее 2000 мс
TTI мобильного приложения: менее 2 сек
Max concurrent users: 5000
Max RPS: 1000
Время доставки push-уведомления: менее 5 сек (P95)

4.3 Нагрузочное тестирование

Сценарии:
- Массовая посадка: 400 заказов за 3 минуты (A330)
- Час пик (завтрак/обед): 200 RPS continuous
- Staggered ramp-up: 50 -> 500 -> 1000 за 5 мин

4.4 Безопасность

Критично:
- HTTPS/TLS 1.3 обязательно
- JWT access token (15 мин) + refresh token (30 дней, httpOnly cookie)
- Rate limiting: 100 req/min на пользователя, 1000 req/min на IP
- CORS — только доверенные origins
- Антифрод: не более 5 отмен заказа в час, флаг подозрительного поведения
- Шифрование PII в покое (AES-256)
- SQL-инъекции: ORM с параметризованными запросами
- XSS: Content-Security-Policy, sanitize user input
- CSRF: double-submit cookie pattern

PCI DSS (платежи):
- SAQ A — если используется iframe платёжного провайдера
- Никакого хранения PAN, CVV, трека магнитной полосы
- Tokenization через ЮKassa

4.5 Совместимость

PWA (основное приложение):
- Chrome 90+, Firefox 90+, Safari 15.4+, Edge 90+
- iOS 15+ (установка через Safari, push с iOS 16.4+)
- Android 8+ (установка через Chrome, push через FCM)
- Адаптивный дизайн: mobile-first, от 320px до 1920px

Веб-админка:
- Chrome 90+, Firefox 90+, Safari 15+, Edge 90+
- Минимальная ширина экрана: 1024px (desktop-first)

4.6 Локализация

- Русский (основной)
- Английский
- Определение языка: настройка устройства -> автовыбор
- Ручное переключение в приложении
- В админке: только русский

4.7 Офлайн-режим (PWA)

- Service Worker кэширует статику (HTML/CSS/JS) по стратегии Cache-First
- Кэш меню (TTL 24 часа) по стратегии Network-First с fallback на кэш
- Кэш списка ресторанов (TTL 6 часов)
- Просмотр истории заказов офлайн (localStorage + IndexedDB)
- При попытке заказа без интернета -> сообщение "Требуется подключение к интернету"
- Offline-страница с кэшированным меню при полном отсутствии соединения


5. ЭТАПЫ РАЗРАБОТКИ

Общий план: 4 месяца. Первые 3 месяца — активная разработка продукта (все модули и функции). Последний месяц — активная интеграция с ресторанами (подключение к iiko, настройка меню, тестирование с реальными партнёрами).

Добавление новых ресторанов после запуска не требует доработки продукта — всё через админ-панель и API iiko.

5.1 Предпроект (2 недели)

Задачи:
- Утверждение ТЗ с заказчиком
- Дизайн-концепция (3 ключевых экрана)
- Выбор технологического стека и хостинга
- Формирование команды:
  1 PM (продукт-менеджер)
  1 UX/UI дизайнер
  2 бэкенд-разработчика (Node.js/Python)
  2 фронтенд-разработчика (PWA, HTML/CSS/JS)
  1 QA инженер
  1 DevOps (part-time)

Результат: Подписанное ТЗ, дизайн-концепция, проектная команда

5.2 Этап 1: Исследование и проектирование (3 недели)

Неделя 1 — UX-исследование:
- Интервью с 10 пассажирами (реальные пользователи)
- CJM (Customer Journey Map) для каждого сценария
- Аудит конкурентов: GrabFood, UberEats аэропортовые версии
- Технический аудит ресторанов (какие POS, какие проблемы)

Неделя 2 — Прототипирование:
- Lo-fi wireframes (Figma) — все экраны
- Hi-fi прототип (ключевые сценарии)
- Юзабилити-тестирование прототипа (5 пользователей)
- Итерация по результатам

Неделя 3 — Дизайн-система:
- Style guide (цвета, типографика, иконки)
- UI kit (компоненты: buttons, inputs, cards, modals, toasts)
- Дизайн всех экранов в Figma
- Подготовка JSON-моков для фронтенда

Результат: Интерактивный прототип в Figma, дизайн-система, утверждённые макеты

5.3 Этап 2: Разработка бэкенда (8 недель)

Спринт 1 (2 нед): Инфраструктура
- Настройка CI/CD (GitHub Actions)
- Развёртывание PostgreSQL + Redis
- Настройка Kubernetes (или Render/Railway для первого этапа)
- Базовая структура API (Express/FastAPI)
- Health-check + мониторинг (Sentry + Grafana)
- Настройка Firebase Cloud Messaging

Спринт 2 (2 нед): Аутентификация + Пользователи
- Регистрация по телефону (SMS через Twilio/Vonage)
- Вход через соцсети (Google, Apple, VK)
- JWT + Refresh tokens
- Профиль пользователя
- Гостевой режим

Спринт 3 (2 нед): Рестораны + Меню + Заказы
- CRUD рестораны, категории, блюда
- CRUD заказы
- Система статусов заказа (FSM)
- Промокоды
- Поиск + фильтры

Спринт 4 (2 нед): Платежи + Чат + Уведомления
- Интеграция ЮKassa
- Чат (WebSocket)
- Push-уведомления (FCM/APNs)
- Отмена заказа и возврат платежа

Результат: Работающий API (Postman collection), протестированные эндпоинты

5.4 Этап 3: Разработка фронтенда (8 недель, параллельно с бэкендом)

Спринт 1 (2 нед): Онбординг + Регистрация (PWA)
- Onboarding screens (3 шага)
- Экран входа (телефон/SMS/соцсети)
- Профиль
- Навигация (Tab bar: Main, Orders, Profile)
- Service Worker + манифест PWA
- Настройка Capacitor (WebView оболочка)

Спринт 2 (2 нед): Главный экран + Ресторан + Меню
- Главная с ресторанами
- Поиск + фильтры
- Экран ресторана (информация + меню)
- Pull-to-refresh (touch events), Skeleton loading
- Кэширование через Service Worker

Спринт 3 (2 нед): Корзина + Оформление заказа
- Корзина (CRUD items)
- Выбор способа получения
- Экран оплаты (iframe ЮKassa / платёжная форма)
- Экран статуса заказа

Спринт 4 (2 нед): Чат + Отзывы + Админка
- Чат (WebSocket)
- Отзывы и оценки
- Административная веб-панель (React/Vue)
- Дашборд администратора

Результат: Собранное приложение, готовое к тестированию

5.5 Этап 4: Активное тестирование (3 недели, заканчивается в первые 2 недели этапа интеграции)

Неделя 1: Функциональное тестирование
- Все сценарии пользователя
- Граничные случаи (пустая корзина, отмена, ошибка оплаты)
- Локализация (RU/EN)
- Тестирование чата и уведомлений

Неделя 2: Нагрузочное тестирование
- k6 / Locust сценарии
- Оптимизация медленных запросов (индексы, кэш)
- Тестирование при пиковой нагрузке
- Оптимизация TTFB и TTI

Неделя 3: Безопасность
- Security audit кода
- Проверка платежей (PCI DSS compliance)
- Fix найденных уязвимостей

Результат: Стабильное приложение, готовое к интеграции с ресторанами

5.6 Этап 5: Активная интеграция с ресторанами (4 недели, последний месяц)

Этот этап идёт параллельно с финальным тестированием и запуском. Подключение каждого нового ресторана — это настройка iiko + синхронизация меню, без доработки кода продукта.

Неделя 1-2: Подключение первых ресторанов
- Подключение 1-2 ресторанов к iiko (тестовый режим)
- Проверка сквозного сценария: заказ в PWA -> iiko -> KDS/принтер -> статусы -> уведомления
- UAT с реальными ресторанами (User Acceptance Testing)
- Исправление критических багов по результатам UAT

Неделя 3-4: Расширение + запуск
- Подключение 3-5 ресторанов
- Бета-тестирование с реальными пассажирами (10-20 чел)
- Сбор фидбека
- Публикация PWA + оболочки в магазинах
- Мониторинг после запуска (24/7 первые 3 дня)

Результат: Работающее приложение с 3-5 подключёнными ресторанами

5.7 Пост-релиз (непрерывно)
- Исправление багов по результатам мониторинга
- Сбор метрик и аналитики
- Сбор фидбека от ресторанов и пользователей
- Планирование следующих этапов


6. ПОЛЬЗОВАТЕЛЬСКИЕ СЦЕНАРИИ

Сценарий 1: Пассажир с пересадкой (40 мин)

1. Пользователь открывает приложение после прохождения паспортного контроля
2. Приложение определяет терминал D по геолокации
3. Пользователь вводит номер выхода на посадку (D-37)
4. Выбирает фильтр "~10 мин" (быстрое приготовление)
5. Видит Burger King — добавляет Воппер и картошку в корзину
6. Выбирает "Доставка к гейту D-37, через 20 мин"
7. Оплачивает картой Мир / СБП (560 руб)
8. Получает push "Заказ принят", затем "Готовится", затем "Доставлен к гейту D-37"
9. Забирает еду и идёт на посадку

Сценарий 2: Семья с детьми (2 часа до рейса)

1. Выбирают ресторан с детским меню
2. Оформляют предзаказ на 12:30, 4 персоны
3. Приходят в ресторан — заказ уже на столе
4. Оплачивают на месте десерты дополнительно (если не включены в предзаказ)

Сценарий 3: Пассажир с задержкой рейса (3+ часа)

1. Видит что рейс задержан на 3 часа
2. Решает поесть в ресторане
3. Выбирает ресторан, смотрит меню
4. Делает предзаказ с бронью столика на 19:00
5. Приходит, садится, еда уже подана
6. После еды оставляет отзыв 5 звёзд

Сценарий 4: Администратор ресторана (начало смены)

1. Входит в веб-админку
2. Видит дашборд — 15 заказов в очереди
3. Отмечает пункты меню как "закончились" (3 позиции)
4. Подтверждает первый заказ -> меняет статус на "Готовится"
5. По готовности нажимает "Готово" -> уведомление клиенту
6. Получает сообщение в чате: "Я у выхода D-24, где ресторан?"
7. Отвечает: "Поверните налево, после кофейни направо"
8. Заказ завершён


7. ТЕХНИЧЕСКАЯ АРХИТЕКТУРА

7.1 Компонентная диаграмма

PWA (SPA) -----------\
(HTML/CSS/JS)         --> HTTPS/REST + WSS -> API Gateway (Kong/Nginx)
Web Admin (React/Vue) --/                              |
                                                       v
                                              Backend (Node.js/Express или Django/Python)
                                              Services: Auth, Orders, Menu, Chat, Payment,
                                                        Notification, User, Analytics, IIkoSync
                                                       |
                                              +--------+--------+
                                              v                 v
                                         PostgreSQL         Redis
                                         (Primary +         (Cache +
                                          Replica)           WS pub/sub)
                                                       |
                                                       v
                                              +------------------+
                                              |  iikoTransport   |
                                              |  REST API        |
                                              +--------+---------+
                                                       |
                                              +--------+---------+
                                              v                   v
                                         iikoCloud        iikoServer
                                         (SaaS)           (on-premise)
                                              |                   |
                                              v                   v
                                         KDS (кухонный     Принтеры
                                         дисплей)          R-Keeper
                                                            Кассы

7.2 База данных (детальная схема — см. Приложение B)

PostgreSQL. Основные таблицы: users, restaurants, menu_categories, menu_items, orders, chat_messages, restaurant_admins, promo_codes, iiko_tokens. Партиционирование orders по дате. Индексы на user_id, restaurant_id, status, created_at.

7.3 API Design (полный список эндпоинтов — см. Приложение A)

Базовый URL: https://api.airport-food.com/api/v1
Формат: JSON, пагинация через page/limit, ошибки в стандартном формате.
Аутентификация: Authorization: Bearer 
Группы эндпоинтов: Auth, Restaurants, Cart, Orders, Chat, Notifications, Admin, IIko Integration.

7.4 WebSocket события

Соединение: wss://api.airport-food.com/ws?token=

События:
Сервер -> клиент: { type: 'order.status', orderId, status, eta }
Сервер -> клиент: { type: 'order.chat', orderId, message }
Сервер -> клиент: { type: 'notification', notification }
Клиент -> сервер: { type: 'chat.send', orderId, text }
Клиент -> сервер: { type: 'chat.read', orderId }

7.5 Сторонние сервисы

ЮKassa - Приём платежей (карты Мир/Visa/Mastercard, СБП, SberPay, T-Pay, ЮMoney)
Twilio / Vonage - SMS для верификации | Альтернатива: SMS-Провайдер.РФ
Firebase Cloud Messaging - Push-уведомления (Android)
APNs (Apple Push Notification) - Push-уведомления (iOS)
Google Maps API - Геолокация, определение терминала | Альтернатива: Yandex Maps (РФ)
Sentry - Мониторинг ошибок
Grafana + Prometheus - Мониторинг инфраструктуры
AWS S3 / Cloud Storage - Хранение изображений
CloudFront / CDN - Раздача контента
iikoCloud / iikoTransport - Ресторанная POS-система (меню, заказы, кухня, касса, отчёты)
iiko KDS - Кухонный дисплей для отображения заказов поварам

7.6 DevOps и инфраструктура

Среда: Dev, Staging, Production — полная изоляция
Docker-контейнеризация
Kubernetes (EKS/GKE/Managed K8s) или Docker Compose для первого этапа

CI/CD:
GitHub Actions / GitLab CI
Автоматический запуск тестов на PR
Автоматический деплой на Staging после merge в develop
Деплой на Production по тегу (Manual approval)

Мониторинг:
Uptime monitoring: Better Uptime / Checkly
APM: Sentry Performance
Логи: ELK Stack (Elasticsearch, Logstash, Kibana)
Метрики: Node exporter + Prometheus + Grafana
Алерты в Telegram/Slack

Резервное копирование:
PostgreSQL: pg_dump каждые 6 часов, retention 30 дней
WAL архивация (Point-in-Time Recovery)
Резервное копирование в отдельный регион


8. МАСШТАБИРОВАНИЕ И ПРОИЗВОДИТЕЛЬНОСТЬ

8.1 Горизонтальное масштабирование

API:
- Stateless сервисы -> горизонтальное масштабирование через K8s HPA (Horizontal Pod Autoscaler)
- Метрика автоскейлинга: CPU > 70% или RPS > 500 на под
- Min replicas: 3, Max replicas: 20

База данных:
- Read replicas (2–3) для запросов чтения (список ресторанов, меню)
- Primary для записи (заказы, платежи)
- Connection pooling через PgBouncer

Redis:
- Кластер для кэша + pub/sub
- Режим sentinel для высокой доступности

8.2 Кэширование

Список ресторанов: TTL 5 мин, инвалидация при изменении ресторана
Меню ресторана: TTL 3 мин, инвалидация при изменении меню
Пользовательский профиль: TTL 10 мин, инвалидация при обновлении профиля
Токены (блоклист): TTL 15 мин, по истечении

8.3 Оптимизация изображений

Форматы: WebP (основной), JPEG fallback
Размеры: 320px (превью), 800px (детали), 1600px (макс)
Автоматическая конвертация и ресайз при загрузке
CDN с кэшированием изображений

8.4 База данных

- Партиционирование таблицы orders по дате (ежемесячно)
- Архивация заказов старше 3 месяцев (отдельная таблица orders_archive)
- VACUUM ANALYZE ежедневно
- PgBouncer для управления соединениями
- Индексы для всех внешних ключей и полей в WHERE/JOIN


9. ОТЧЕТЫ И АНАЛИТИКА

9.1 Встроенные отчёты (веб-админка)

- Ежедневный дайджест — email/Push в 22:00 с итогами дня
- Отчёт по заказам — все заказы за период + детализация
- Финансовый отчёт — выручка, комиссии, возвраты, налоги
- Маркетинговый отчёт — использование промокодов, конверсия
- Отчёт по отменам — количество, причины, потери выручки

9.2 Аналитика (Firebase / Google Analytics)

События (event tracking):
app_open — открытие приложения
restaurant_view — просмотр ресторана { restaurantId, source }
menu_item_view — просмотр блюда { itemId, restaurantId }
add_to_cart — добавление в корзину { itemId, quantity, price }
cart_checkout — начало оформления { cartTotal, itemCount }
fulfillment_selected — выбор способа получения { type }
payment_initiated — начало оплаты { method, amount }
payment_success — успешная оплата { method, amount, time }
payment_failed — ошибка оплаты { method, amount, error }
order_placed — заказ создан { orderId, amount, restaurantId }
order_cancelled — отмена { orderId, reason }
order_status_change — смена статуса { orderId, status, time }
chat_opened — открытие чата { orderId }
chat_message_sent — сообщение в чат
review_submitted — отзыв { restaurantId, rating }

Dashboards:
Retention (D1, D7, D30)
Funnel конверсии: просмотр -> корзина -> оплата -> получение
Сегментация пользователей (новые / вернувшиеся / VIP)
Геоаналитика (терминал, гейт, зона)


10. БЕЗОПАСНОСТЬ

10.1 Аутентификация и авторизация

- JWT access token: 15 минут, хранится в памяти приложения (не в localStorage)
- Refresh token: 30 дней, httpOnly cookie (secure, sameSite=strict)
- При регистрации через соцсети — проверка origin token
- Rate limit на /send-code: 5 запросов в час на номер телефона
- Блокировка после 5 неудачных попыток ввода кода на 30 минут
- Refresh token rotation — старый refresh невалидируется после использования

10.2 API безопасность

- CORS: белый список origins
- Content-Security-Policy: strict CSP заголовки
- X-Content-Type-Options: nosniff
- Strict-Transport-Security: max-age=31536000; includeSubDomains
- Запросы тела: лимит 1 MB
- Валидация всех входных данных (Joi / Zod / Pydantic)
- SQL-инъекции: ORM с параметризованными запросами
- IDOR protection: проверка ownership на каждый ресурс

10.3 Платежи

- Использование ЮKassa (iframe/API — PCI DSS Level 1)
- Никаких данных карт на сервере
- Возврат: refund через API ЮKassa (полный или частичный)
- Антифрод проверки перед оплатой:
  - Подозрительно быстрые заказы (< 10 сек от открытия до оплаты)
  - Множественные отмены с одного аккаунта
  - Несовпадение геолокации с терминалом

10.4 Хранение данных

- PII (телефон, email, имя): AES-256 шифрование на уровне БД
- Пароли: bcrypt (cost factor 12)
- Токены: SHA-256 хеш в БД
- Логи: без PII (маскированный телефон)
- Retention: заказы 3 года, логи 6 мес, чаты 30 дней

10.5 Security Headers (полный список — см. Приложение E)

Все ответы сервера содержат HSTS, X-Content-Type-Options, X-Frame-Options, CSP, Referrer-Policy, Permissions-Policy.


11. ТЕСТИРОВАНИЕ

11.1 Виды тестов

Unit (бэкенд): Jest / Pytest, покрытие > 80%
Integration (бэкенд): Supertest / pytest, покрытие > 70%
Component: React Testing Library, покрытие > 60%
E2E (PWA): Playwright / Cypress, критические сценарии
E2E (веб): Playwright / Cypress, CRUD меню, заказы
Нагрузочные: k6 / Locust, пиковая нагрузка
Security: OWASP ZAP, OWASP Top 10

11.2 Критические сценарии (E2E)

1. Полный цикл заказа (самовывоз):
   Регистрация -> выбор ресторана -> добавление в корзину -> оформление -> оплата -> получение push -> отметка "готово" админом

2. Доставка к гейту:
   Выбор доставки -> указание гейта -> оплата -> получение push о доставке

3. Предзаказ:
   Предзаказ -> бронь стола -> приход в ресторан -> мгновенная подача

4. Отмена заказа:
   Создание заказа -> отмена -> возврат средств -> проверка статуса

5. Админка:
   Вход -> создание блюда -> публикация -> проверка в приложении -> изменение статуса заказа

11.3 Тестовые данные и окружения (детали — см. Приложение D)

Набор: 15 ресторанов, 200+ блюд, 100 пользователей, 500 заказов, 10 промокодов.
Окружения: Local (Docker), Dev, Staging, Production.


12. ЗАКЛЮЧЕНИЕ

12.1 Оценка сроков и ресурсов

Предпроект: 2 нед, команда: PM + Designer
Исследование и дизайн: 3 нед, команда: PM + Designer + 1 Backend
Бэкенд: 8 нед, команда: 2 Backend + DevOps (part-time)
Фронтенд (PWA): 8 нед (параллельно), команда: 2 Frontend (PWA)
Фронтенд (админка): 4 нед (внутри этапа 3), команда: 1 Frontend (Web)
Тестирование: 4 нед, команда: 1 QA + вся команда
Запуск: 1 нед, команда: DevOps + вся команда

Итого: ~16 недель (4 месяца) на активную разработку + интеграцию

12.2 Этапы продукта

Этап активной разработки (первые 3 месяца):
- Регистрация по телефону / соцсети
- Просмотр меню + корзина
- Оплата (ЮKassa: карты Мир/Visa/Mastercard, СБП)
- Три способа получения (самовывоз, доставка к гейту, предзаказ)
- Push-уведомления
- Веб-админка (CRUD меню + статусы заказов)
- Чат с рестораном
- Интеграция с iiko (меню, заказы, стоп-листы)
- Система возвратов (полный и частичный)
- Отзывы и оценки

Этап активной интеграции (последний месяц + пост-релиз):
- Подключение ресторанов через iiko API (не требует доработки продукта)
- Добавление новых терминалов и зон
- SberPay / T-Pay (через ЮKassa)
- Промокоды
- Мультиязычность
- Аналитика и отчёты
- Экспорт меню (Excel)
- 2FA для админов

12.3 Риски и mitigation (полная таблица — см. Приложение F)

Основные риски: низкое принятие ресторанами, проблемы с интеграцией платежей, отток пользователей после первого заказа.

12.4 Юридические аспекты

- Пользовательское соглашение — оферта, правила использования
- Политика обработки персональных данных — 152-ФЗ / GDPR compliance
- Публичная оферта для ресторанов — договор на оказание услуг
- Фискализация — онлайн-кассы (54-ФЗ) через платёжного провайдера
- Товарный чек — электронный чек на email/в приложении
- Возврат — условия возврата согласно ЗоЗПП (статья 26.1)
- Ответственность — ресторан отвечает за качество еды, платформа за обработку заказа


ПРИЛОЖЕНИЕ A — Полный список API эндпоинтов

Базовый URL: https://api.airport-food.com/api/v1

Формат:
- Все запросы и ответы в JSON
- Пагинация: ?page=1&limit=20 -> { data: [...], meta: { page, limit, total, pages } }
- Ошибки: { error: { code: 'VALIDATION_ERROR', message: '...', details: [...] } }
- Аутентификация: Authorization: Bearer 

Auth:
POST /auth/send-code
POST /auth/verify-code
POST /auth/social
POST /auth/refresh
GET /auth/profile
PATCH /auth/profile
DELETE /auth/logout

Restaurants:
GET /restaurants
GET /restaurants/:id
GET /restaurants/:id/menu
GET /restaurants/:id/reviews
POST /restaurants/:id/reviews

Cart:
GET /cart
POST /cart
PATCH /cart/item
DELETE /cart/item/:menuItemId
POST /cart/promo
DELETE /cart/promo

Orders:
POST /orders
GET /orders
GET /orders/:id
POST /orders/:id/pay
POST /orders/:id/cancel
POST /orders/:id/fulfillment

Chat:
GET /orders/:id/chat
POST /orders/:id/chat
POST /orders/:id/chat/read

Notifications:
GET /notifications
PATCH /notifications/:id/read
PATCH /notifications/read-all

Admin:
GET /admin/dashboard
GET /admin/orders
PATCH /admin/orders/:id/status
GET /admin/menu/categories
POST /admin/menu/categories
PATCH /admin/menu/categories/:id
DELETE /admin/menu/categories/:id
GET /admin/menu/items
POST /admin/menu/items
PATCH /admin/menu/items/:id
DELETE /admin/menu/items/:id
POST /admin/menu/import
GET /admin/restaurant
PATCH /admin/restaurant
GET /admin/reports/sales
GET /admin/reports/top-items
GET /admin/reports/performance
GET /admin/users
PATCH /admin/users/:id
GET /admin/admins
POST /admin/admins
PATCH /admin/admins/:id

IIko Integration:
POST /admin/iiko/connect          body: { restaurantId, login, key }
POST /admin/iiko/disconnect        body: { restaurantId }
GET  /admin/iiko/organizations     body: { restaurantId }
POST /admin/iiko/sync-menu         body: { restaurantId }
POST /admin/iiko/sync-stoplist     body: { restaurantId }
GET  /admin/iiko/status            params: restaurantId
GET  /admin/iiko/pending-orders    params: restaurantId


ПРИЛОЖЕНИЕ B — Детальная схема базы данных

Все таблицы используют UUID в качестве первичного ключа, created_at и updated_at с автоустановкой.

users — Пользователи
  id (UUID, PK), phone VARCHAR(20) UNIQUE, email VARCHAR(255) UNIQUE, password_hash VARCHAR(255), name VARCHAR(100), avatar_url TEXT, social_providers JSONB, is_guest BOOLEAN DEFAULT true, is_blocked BOOLEAN DEFAULT false, locale VARCHAR(5) DEFAULT 'ru', created_at, updated_at

restaurants — Рестораны
  id (UUID, PK), name VARCHAR(255) NOT NULL, description TEXT, image_url TEXT, terminal VARCHAR(10) NOT NULL, zone VARCHAR(10), latitude DECIMAL(10,7), longitude DECIMAL(10,7), rating DECIMAL(2,1) DEFAULT 0, review_count INT DEFAULT 0, avg_prep_time INT DEFAULT 600, delivery_fee DECIMAL(10,2) DEFAULT 0, free_delivery_from DECIMAL(10,2) DEFAULT 1000, delivery_radius INT DEFAULT 500, is_active BOOLEAN DEFAULT true, schedule JSONB, iiko_org_id VARCHAR(255), iiko_terminal_group_id VARCHAR(255), iiko_login VARCHAR(255), iiko_key VARCHAR(255), last_menu_revision BIGINT, last_sync_at TIMESTAMP, iiko_status VARCHAR(20) DEFAULT 'disconnected', created_at

menu_categories — Категории меню
  id (UUID, PK), restaurant_id (FK), name VARCHAR(100) NOT NULL, image_url TEXT, sort_order INT DEFAULT 0, is_active BOOLEAN DEFAULT true

menu_items — Блюда
  id (UUID, PK), restaurant_id (FK), category_id (FK), name VARCHAR(255) NOT NULL, description TEXT, price DECIMAL(10,2) NOT NULL, image_url TEXT, is_available BOOLEAN DEFAULT true, sort_order INT DEFAULT 0, allergens TEXT[], nutritional_info JSONB, prep_time INT DEFAULT 300, modifiers JSONB, iiko_product_id VARCHAR(255), iiko_size_id VARCHAR(255), created_at, updated_at

orders — Заказы
  id (UUID, PK), order_number VARCHAR(6) UNIQUE NOT NULL, user_id (FK), restaurant_id (FK), iiko_order_id VARCHAR(255), iiko_status VARCHAR(50), status VARCHAR(20) DEFAULT 'pending', fulfillment_type VARCHAR(20), fulfillment_details JSONB, items JSONB, comment TEXT, promo_code VARCHAR(50), promo_discount DECIMAL(10,2), subtotal DECIMAL(10,2), delivery_fee DECIMAL(10,2), total DECIMAL(10,2), payment_status VARCHAR(20) DEFAULT 'pending', payment_method VARCHAR(50), refund_status VARCHAR(20) DEFAULT 'none', refund_amount DECIMAL(10,2), refund_reason TEXT, paid_at TIMESTAMP, refunded_at TIMESTAMP, cancelled_at TIMESTAMP, cancel_reason TEXT, created_at, updated_at

chat_messages — Чат
  id (UUID, PK), order_id (FK), sender VARCHAR(10), text TEXT, image_url TEXT, created_at

restaurant_admins — Администраторы ресторана
  id (UUID, PK), restaurant_id (FK), user_id (FK), role VARCHAR(20) DEFAULT 'manager', created_at

promo_codes — Промокоды
  id (UUID, PK), code VARCHAR(50) UNIQUE NOT NULL, discount_type VARCHAR(10) NOT NULL, discount_value DECIMAL(10,2) NOT NULL, min_order DECIMAL(10,2) DEFAULT 0, max_uses INT DEFAULT 100, used_count INT DEFAULT 0, expires_at TIMESTAMP, is_active BOOLEAN DEFAULT true

iiko_tokens — Токены iiko
  id (UUID, PK), restaurant_id (FK), access_token TEXT, expires_at TIMESTAMP, created_at, updated_at

Индексы:
  orders(user_id), orders(restaurant_id), orders(status), orders(created_at)
  menu_items(restaurant_id), menu_items(category_id)
  restaurants(terminal)


ПРИЛОЖЕНИЕ C — API iiko (iikoTransport)

Аутентификация:
POST /api/1/access_token       body: { apiLogin, apiKey }
Ответ: { token, expirationMinutes: 60 }

Номенклатура и меню:
GET  /api/1/nomenclature         params: organizationId, revisionId
POST /api/1/stop_lists          body: { organizationIds }
POST /api/1/organizations       body: { organizationIds }

Заказы и доставки:
POST /api/1/deliveries/create   body: { organizationId, order, delivery, payments }
POST /api/1/deliveries/cancel   body: { organizationId, orderId, cancelCause, removeFromQueue }
POST /api/1/deliveries/by_id    body: { organizationId, orderIds }

Оплаты:
POST /api/1/payments/pay        body: { organizationId, orderId, payments }

Webhook:
POST -> бэкенд                  body: { organizationId, orderId, status, revision, timestamp }

Лимиты:
- Максимум 10 запросов в секунду на один токен
- Максимум 5 одновременных запросов на один токен
- Токен живёт 60 минут, требуется периодическое обновление


ПРИЛОЖЕНИЕ D — Тестовые данные и окружения

Набор тестовых данных:
- 15 ресторанов (5 в каждом терминале)
- 200+ блюд (по 15-20 на ресторан)
- 100 пользователей
- 500 заказов (в разных статусах)
- 10 промокодов

Окружения:
Local: Docker PostgreSQL, тестовые данные (seed), доступ: разработчики
Dev: Managed PostgreSQL, тестовые данные (seed), доступ: команда
Staging: Managed PostgreSQL, анонимизированные продакшен данные, доступ: QA + Заказчик
Production: Production PostgreSQL, реальные данные


ПРИЛОЖЕНИЕ E — Security Headers

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(self), camera=(), microphone=()


ПРИЛОЖЕНИЕ F — Риски

Риск: Низкая скорость принятия ресторанами | Вероятность: средняя | Влияние: высокое | Mitigation: обучение персонала, простой интерфейс админки
Риск: Проблемы с интеграцией платежей | Вероятность: низкая | Влияние: высокое | Mitigation: ЮKassa как primary, резервный провайдер (Сбербанк / Т-Банк)
Риск: Плохое покрытие Wi-Fi в аэропорту | Вероятность: средняя | Влияние: среднее | Mitigation: кэширование офлайн, QR-меню
Риск: Задержки в модерации App Store | Вероятность: низкая | Влияние: среднее | Mitigation: подача за 2 недели до релиза
Риск: Отток пользователей после 1 заказа | Вероятность: высокая | Влияние: высокое | Mitigation: Push-акции, напоминания перед следующим рейсом
Риск: Проблемы с определением терминала по GPS | Вероятность: низкая | Влияние: низкое | Mitigation: ручной выбор + Wi-Fi fingerprint