feat(pwa): вход для сотрудника, просмотр от лица водителя и вкладка «Отладка»

Приложение обкатывается на боевом контуре, поэтому интерфейс надо проверять на
живых данных — а живые данные лежат у настоящих водителей.

* «Вход для сотрудника» (/dev) — логин учётки CRM, поддержан второй шаг 2FA;
  дальше поиск водителя по ФИО/телефону/id и просмотр его экранов.
* Плашка «Смотрю как …» над всеми экранами: интерфейс в этом режиме неотличим
  от водительского, и чужой баланс легко принять за свой.
* Вкладка «Отладка» (/debug, водителю недоступна): смена водителя, driver_id,
  режим, срок токена, адрес API, коммит и время сборки, журнал последних
  40 ответивших запросов, сброс кэша сервис-воркера.
* Пополнение под просмотром идёт в учебный контур: экран /mock-pay/:orderId
  выбирает, что «ответит» банк, а настоящий экран оплаты разбирает эти статусы
  своим боевым кодом. Ни рубля, ни чека 54-ФЗ.

Заодно доезжает незакоммиченная работа прошлой сессии: дев-прокси в
vite.config.ts, его тест и разобранный .env.example.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 13:15:02 +10:00
co-authored by Claude Opus 5
parent aff1a45006
commit ceec3db364
27 changed files with 1308 additions and 17 deletions
+147
View File
@@ -0,0 +1,147 @@
# Премиум Водитель — PWA водителя
React + Vite + TypeScript. Прод: **https://app.pptaxi.ru**.
Бэкенд — модуль `payments` в Premium CRM (`https://crm.pptaxi.ru/api`,
репозиторий `crm2`, `backend/app/modules/payments/`).
## Как поднять локально
```bash
npm install
npm run dev # http://localhost:5174
```
Отдельно поднимается бэкенд `crm2` на **127.0.0.1:8000** (тот же порт, что в
`backend/Dockerfile`). Больше настраивать нечего: dev-сервер сам подставляет
`VITE_API_BASE=/api` и проксирует `/api` на бэкенд — см. `vite.config.ts`.
**Почему через прокси, а не «разрешить localhost в CORS бэкенда».** Прокси
делает запрос ОДНОГО происхождения со страницей (`http://localhost:5174/api/…`),
поэтому CORS в разработке не участвует вообще. Боевой список origin'ов на
бэкенде остаётся закрытым, а `CORS_ALLOW_LOCALHOST=true` в `backend/.env` для
этого фронта включать не надо. (Раньше рецепт с `CORS_ALLOW_LOCALHOST` тут и не
работал: прокси не было, и фронт по умолчанию уходил на боевой домен, а не на
локальный бэкенд.)
Сервис-воркер в режиме разработки не регистрируется (`devOptions` у
`vite-plugin-pwa` по умолчанию выключены) — старый кэш прод-версии в дев не
лезет.
### Бэкенд не на `127.0.0.1:8000`
```bash
VITE_API_TARGET=http://127.0.0.1:8090 npm run dev
```
В PowerShell инлайн-префикса `VAR=... команда` нет — переменную ставят отдельно:
```powershell
$env:VITE_API_TARGET="http://127.0.0.1:8090"; npm run dev
```
Имя переменной то же, что в CRM (`crm2/frontend/vite.config.ts`), — рецепт один
на оба фронта.
### Ходить в API напрямую, мимо прокси
`.env.local` (в git не попадает):
```
VITE_API_BASE=http://127.0.0.1:8000/api
```
Своя настройка приоритетнее дев-дефолта — специально, чтобы `npm run dev` её не
перебивал. Для похода в чужой origin бэкенду понадобится
`CORS_ALLOW_LOCALHOST=true` в `backend/.env` и рестарт.
> ⚠️ `crm.pptaxi.ru` — боевой терминал Т-Банка и боевая касса АТОЛ. Пополнение,
> сделанное «просто посмотреть», — это реальные деньги водителя, реальный чек
> 54-ФЗ и реальная проводка в леджере. Поэтому прокси по умолчанию смотрит в
> локальный бэкенд, а не в прод.
## Проверки
```bash
npx tsc -b # типы
npm test # vitest run
```
`npx tsc --noEmit` **без `-b`** здесь всегда зелёный и ничего не значит:
корневой `tsconfig.json` — solution-файл (`files: []` + `references`), под него
не попадает ни один файл (`tsc --noEmit --listFiles` печатает пустоту). Типы
живут в `tsconfig.app.json` (`src`) и `tsconfig.node.json` (конфиги +
`dev-proxy.test.ts`), и до обоих добирается только `tsc -b`.
## Сборка
```bash
npm run build # tsc -b && vite build → dist/
```
Дев-дефолт `VITE_API_BASE=/api` подставляется только при запущенном dev-сервере
и в бандл не попадает: в собранном `assets/index-*.js` лежит
`https://crm.pptaxi.ru/api` (проверено сборкой). Поэтому `VITE_API_BASE` боевой
сборке задавать не обязательно — без неё `src/api/client.ts` подставляет ровно
этот адрес.
> ⚠️ `npm run preview` поднимает **уже собранный** бандл, а база в нём
> абсолютная: запросы уходят на боевой `crm.pptaxi.ru` мимо прокси dev-сервера.
> Смотреть локальные правки — только `npm run dev`.
Куда выкладывается `dist/` — в плане проекта:
`../../docs/superpowers/plans/2026-06-18-premium-driver-pwa.md`, раздел
«Deployment notes»: статика на `app.pptaxi.ru`, Caddy с фолбэком на `index.html`.
## Обкатка: кто может войти и как смотреть чужой экран
Приложение выложено на боевой домен, а обкатывается интерфейс. Поэтому на время
обкатки включены две вещи.
### 1. Вход только по списку номеров
Переменная бэкенда (`crm2/backend/.env`, читается в `app/config.py`):
```
DRIVER_APP_ALLOWED_PHONES=+7 (914) 123-45-67, 79990001122
```
Разделители — запятая, точка с запятой, перенос строки (пробел разделителем не
считается: номер вписывают в привычном виде). Сверяются последние 10 цифр.
**Пусто — гейт выключен, вход открыт всем водителям.**
Проверка стоит в единственной воронке выдачи токена
(`modules/payments/driver_auth.create_driver_token_for`), поэтому закрыты сразу
все пять путей входа: код в бот, кнопка «Войти через Telegram», колбэки своего
бота и два аварийных отката на n8n. Бот отвечает человеку текстом, остальные
пути — 403. Логика гейта: `modules/payments/login_gate.py`.
### 2. Просмотр от лица водителя (сотрудник)
На экране входа внизу — «Вход для сотрудника» (`/dev`): логин и пароль **учётки
CRM**, при включённой двухфакторке спросит код. Дальше поиск по ФИО, телефону
или id — и приложение открывается от лица выбранного водителя.
* Токен помечен claim'ом `imp`, живёт 8 часов (обычный водительский — 30 дней).
* Гейт — `is_admin` в CRM. Каждая выдача пишется в аудит (`driver_app.impersonate`).
* Кнопка «Отозвать доступ» в CRM гасит и просмотр сотрудника — поколение общее.
* Гейт обкатки на этот путь не распространяется: смотреть надо как раз на
водителей, которых в allowlist нет.
Пока просмотр действует, сверху на всех экранах висит плашка «Смотрю как …» со
ссылкой на вкладку **«Отладка»** (`/debug`, водителю недоступна):
* от чьего лица открыт экран и кнопка сменить водителя;
* `driver_id`, режим, сотрудник, когда истекает токен, адрес API, коммит и время
сборки, состояние сети;
* журнал последних 40 ответивших запросов (метод, путь, статус, миллисекунды);
* сброс кэша сервис-воркера с перезагрузкой — лечит «правка не приехала»;
* выходы: из просмотра, из учётки сотрудника.
### Деньги под просмотром: учебный платёж
Пополнение под `imp`-токеном **не идёт в банк**. Проверки суммы, категории и
расчёт комиссии настоящие, а вместо Init на терминале заводится учебный заказ в
Redis (`modules/payments/mock_pay.py`, префикс `mock-`, час жизни). Экран
`/mock-pay/:orderId` даёт выбрать, что «ответит» банк — оплачено, отказ,
истёкший счёт, возврат, — и настоящий экран оплаты разбирает эти статусы своим
боевым кодом. В `driver_payments` не попадает ни строки, чек 54-ФЗ не пробивается.