# KILN website

Production-сайт ресторана KILN на Next.js App Router.

Перед любыми изменениями прочитайте `AGENTS.md` и `TECHNICAL_CONTEXT.md`.
Администрирование меню описано в `ADMIN_MENU.md`, production-развёртывание —
в `DEPLOY.md`. Сервер-сервер контракт с ботом находится только в
`/srv/kiln-bot/api_site_contract.md`.

## Запуск

Используйте отдельную development-машину или checkout. На production-хосте не
запускайте обычный `docker compose up`: он может пересоздать действующий Compose
project. Для изолированной локальной среды:

```bash
npm ci
docker compose -p kiln-dev up -d --wait postgres
DATABASE_URL=postgres://kiln:kiln@localhost:55432/kiln npm run db:migrate
DATABASE_URL=postgres://kiln:kiln@localhost:55432/kiln npm run dev -- --port 3001
```

Откройте `http://localhost:3001`. Скрипт `dev:3001` запускает фиксированный порт и не должен незаметно переключать демонстрацию на другой адрес.

## Где менять данные

- Общие данные ресторана, ссылки, рейтинг, часы, feature flags: `src/config/site.ts`.
- Меню из PDF 2026 года: `src/data/menu.ts`.
- Типы: `src/lib/types.ts`.
- Используемые изображения сайта находятся в `public/brand`, `public/dishes`,
  `public/gallery`, `public/legend`, `public/og`; исходные макеты и старые версии
  графики в репозитории не хранятся.
- Основной favicon — `public/favicon.svg`; PNG с суффиксом `-v2` служат только
  fallback для Apple/PWA и браузеров без SVG favicon.
- Локальные файлы шрифта, необходимые сборке: `files/Fonts/`.

## Feature flags

В `src/config/site.ts` можно включать и выключать:

- `deliveryEnabled`;
- `pickupEnabled`;
- `bookingEnabled`;
- `eventsEnabled`;
- `loyaltyEnabled`;
- `onlinePaymentEnabled`.

Отключенные функции не выводятся в основную навигацию, если у них есть флаг.

## Интеграция с ботом и СБИС

Сайт обращается к закрытому API бота только с backend по `BOT_API_BASE_URL`
и `BOT_API_KEY`. В production используется Docker DNS `http://bot:8383` в
общей внутренней сети `kiln_bot_api`. Бот отвечает за СБИС, доставку, историю,
сообщения, SMS и email-транспорт; ключ API не должен попадать в браузер.

- SMS используется только для первого подтверждения телефона в личном кабинете.
- Email PIN генерирует и проверяет сайт, а письмо отправляет бот через SMTP.
- Цена, наличие, зоны, тарифы, порог бесплатной доставки и время приходят из СБИС через бота.
- Привязки и заказы используют настоящий ID номенклатуры СБИС. Если бот
  исправляет ранее отданный ID строки прайса, синхронизация переносит активную
  связь по `position_id`, сохраняя закрытую старую запись в истории.
- Калорийность и БЖУ можно явно получить из карточки СБИС или заполнить вручную; автоматическая синхронизация не затирает редакционные значения.
- Аллергены заполняются вручную и показываются с пометкой «Может содержать».
- Карточки СБИС в админке открываются в модальном окне; список сохраняет поиск,
  выделение и прокрутку, а связи локальных карточек видны в редакторе. Связь
  позиции можно заменить или снять, а категорию СБИС выбрать из списка.
- В текущих и завершённых заказах показываются сохранённые позиции и сумма;
  технический `client_ref` не используется как видимый номер заказа.
- После подтверждения полной оплаты в СБИС polling автоматически скрывает
  кнопку и ссылку оплаты; дальнейшие приготовление и delivery dispatch
  принадлежат state machine бота, а не браузеру.
- Время API содержит явный UTC/offset и во всех клиентских и операторских
  карточках отображается в `Europe/Moscow`. Recovery оплаты не показывает
  клиенту технический `needs_operator`, устаревшее напоминание об оплате или
  ложное изменение состава.
- В админке настройка «Среда Достависты» мгновенно переключает только новые
  доставки между TEST и PROD. Она недоступна операторской роли. Браузер
  администратора получает только enum и признаки
  настроенности; endpoint и четыре credentials остаются в backend бота.
  Карточки уже созданных доставок показывают закреплённый badge TEST/PROD.
- Форма входа оператора не показывает рабочий email: request/verify endpoints
  сами используют фиксированный server-side адрес и принимают только код.
- В текущих заказах доступны серверно разрешённые действия: изменение до
  оплаты, безопасная отмена без удаления истории и переход к переписке с
  рестораном. Завершённые заказы отменять нельзя.
- В отложенной pending-заявке клиент и оператор видят отправленный состав и
  сумму позиций; до подтверждения клиент может вернуть состав в корзину и
  заменить заявку с тем же `client_ref`.
- Авторизованный клиент хранит адреса доставки на сервере, выбирает их в
  checkout и может менять, назначать основным или архивировать. Схему добавляет
  миграция `drizzle/0014_customer_addresses.sql`.
- Checkout инвалидирует расчёт при любом изменении адреса или subtotal и до
  нового успешного quote не показывает прежний тариф. Для доставки выводятся
  minimum, текущая стоимость, порог бесплатной доставки и shortfall; номер и
  название технической зоны клиенту не показываются. Кнопка оплаты требует
  актуальный quote и `subtotal >= minOrder`. Самовывоз не имеет delivery
  minimum и показывает централизованный адрес ресторана из contact settings.
- В открытой корзине и checkout наличие/количество проверяется через backend
  каждые 30 секунд. Ошибка СБИС сохраняет last-good; перед созданием бот всегда
  делает отдельную свежую проверку заказанных SKU.
- Маркетинговое согласие в checkout по умолчанию скрыто; администратор может
  включить его настройкой «Маркетинговое согласие» без изменения кода.

Для сайта SMTP-переменные не требуются. SMTP настраивается только в `.env`
бота; сайту нужны `BOT_API_BASE_URL` и совпадающий с ботом `BOT_API_KEY`.

Операторский кабинет сайта доступен только по непубличному адресу
`/operator/`: вход выполняется кодом из письма на `bar@kiln.spb.ru`, код
действует 3 минуты. `/ma` обслуживается сайтом и при запуске из привязанного
ресторанного чата автоматически создаёт операторскую сессию через подписанный
Mini App API бота. В чатах остаются только штатные кнопки WebView.

## Проверки

```bash
npm run lint
npm run typecheck
npm run build
npm run test:visual
```

`npm test` намеренно не входит в безопасный минимальный набор: он записывает
данные в PostgreSQL, использует generated-файлы `data/import` и HTTP-сервер.
Запускайте его только с отдельной тестовой БД и тестовым приложением. Полные
условия описаны в `AGENTS.md`.

## Миграции

`npm run db:migrate` идемпотентно применяет все файлы `drizzle/*.sql`.
Уже применённые файлы не изменяются: для новой схемы добавляется новая
миграция и одновременно обновляется `src/db/schema.ts`.
При production-развёртывании миграции выполняются внутри контейнера приложения
после создания резервной копии PostgreSQL.
