156 lines
19 KiB
Markdown
156 lines
19 KiB
Markdown
# Руководство по развёртыванию и чек-лист боевого тестирования Krona Staff
|
||
|
||
## 1. Назначение и архитектурные инварианты
|
||
|
||
Krona Staff — автономно развёртываемый блок Krona Business Platform, отвечающий за учёт рабочего времени, терминалы посещаемости и управленческий расчёт зарплаты.
|
||
|
||
### Ключевые инварианты (AGENTS.md)
|
||
|
||
1. **Разделение доменов**: Core владеет Tenant, User, Role, SSO, Platform Settings, Files, Notifications. Staff владеет Employee и HR-доменом (`User != Employee`).
|
||
2. **Изоляция арендаторов**: Все таблицы содержат `tenant_id` (ULID 26 символов). В PostgreSQL включен и принудительно активирован Row-Level Security (`FORCE ROW LEVEL SECURITY`). Запросы выполняются через tenant-scoped транзакции с выставлением `app.tenant_id`.
|
||
3. **Разделение ролей БД**:
|
||
- `krona_migrator` — владелец схемы и таблиц, выполняет миграции DDL, `NOSUPERUSER NOBYPASSRLS`.
|
||
- `krona_staff_app` — runtime-пользователь приложения с минимальными DML правами (SELECT, INSERT, UPDATE, DELETE на прикладные таблицы; нет прав на `schema_migrations`, нет прав DDL).
|
||
4. **Типы данных**:
|
||
- Идентификаторы — ULID (`VARCHAR(26)`).
|
||
- Денежные суммы — `BIGINT` в минимальных единицах (копейках/minor units) + ISO 4217 код валюты (например, `'RUB'`).
|
||
- Временные метки — UTC (`TIMESTAMPTZ`).
|
||
5. **Надёжность**: Транзакционный Outbox, идемпотентность по `Idempotency-Key`, CSRF-защита сессионных cookie (`Origin`/`Sec-Fetch-Site`), Rate-limiting ключей входа.
|
||
|
||
---
|
||
|
||
## 2. Развёртывание и запуск
|
||
|
||
### 2.1. Системные требования
|
||
|
||
- **ОС**: Linux (Ubuntu 22.04+/Debian 12+) или Windows Server 2022+ / Windows 11 (для сборки терминала).
|
||
- **Среда выполнения**: Node.js 22 LTS, npm 10+.
|
||
- **База данных**: PostgreSQL 17 (поддерживается 17.0+).
|
||
- **Сеть**: Порт 8080 (HTTP) или 8453 (HTTPS пилот).
|
||
|
||
### 2.2. Запуск через Docker Compose (рекомендуемый пилот)
|
||
|
||
```bash
|
||
# 1. Перейдите в каталог deploy/pilot
|
||
cd deploy/pilot
|
||
|
||
# 2. Задайте ULID организации из Krona Business Platform
|
||
export STANDALONE_TENANT_ID="01J0000000000000000000000A"
|
||
|
||
# 3. Выполните скрипт первичной инициализации:
|
||
# - Сгенерирует стойкие пароли БД, токены менеджера и терминала в secrets/
|
||
# - Сгенерирует ключи Web Push (VAPID)
|
||
# - Создаст самоподписанный TLS-сертификат для терминала
|
||
./setup.sh
|
||
|
||
# 4. Запустите стек (PostgreSQL + мигратор + app)
|
||
docker compose up -d --build
|
||
|
||
# 5. Проверьте запуск контейнеров и миграций
|
||
docker compose ps
|
||
docker compose logs -f app
|
||
```
|
||
|
||
### 2.3. Запуск без Docker (Bare-Metal / Dev стенд)
|
||
|
||
1. **Создайте базу данных и роли в PostgreSQL**:
|
||
|
||
```sql
|
||
-- Под пользователем postgres (DBA):
|
||
CREATE ROLE krona_migrator LOGIN PASSWORD 'migrator_secure_password' NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS;
|
||
CREATE ROLE krona_staff_app LOGIN PASSWORD 'app_secure_password' NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS;
|
||
|
||
CREATE DATABASE krona_staff OWNER krona_migrator ENCODING 'UTF8';
|
||
\c krona_staff
|
||
ALTER SCHEMA public OWNER TO krona_migrator;
|
||
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
|
||
REVOKE ALL ON DATABASE krona_staff FROM PUBLIC;
|
||
GRANT CONNECT ON DATABASE krona_staff TO krona_staff_app;
|
||
```
|
||
|
||
2. **Настройте переменные окружения (`.env`)**:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
Укажите в `.env`:
|
||
|
||
```ini
|
||
PORT=8080
|
||
NODE_ENV=production
|
||
STANDALONE_TENANT_ID=01J0000000000000000000000A
|
||
STANDALONE_ACTOR_ID=01J00000000000000000000001
|
||
STANDALONE_MANAGER_TOKEN=your_secure_manager_token_hex_or_ulid
|
||
STANDALONE_TERMINAL_TOKEN=your_secure_terminal_token_hex_or_ulid
|
||
DATABASE_URL=postgres://krona_staff_app:app_secure_password@127.0.0.1:5432/krona_staff
|
||
DATABASE_MIGRATION_URL=postgres://krona_migrator:migrator_secure_password@127.0.0.1:5432/krona_staff
|
||
```
|
||
|
||
3. **Сгенерируйте VAPID ключи**:
|
||
|
||
```bash
|
||
node scripts/generate-vapid-keys.mjs >> .env
|
||
```
|
||
|
||
4. **Примените миграции**:
|
||
|
||
```bash
|
||
npm run db:migrate
|
||
```
|
||
|
||
5. **Соберите и запустите приложение**:
|
||
```bash
|
||
npm run build
|
||
npm run start
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Чек-лист ручного боевого тестирования владельцем
|
||
|
||
Каждый шаг должен быть выполнен последовательно. Ожидаемый результат сверяется с фактическим.
|
||
|
||
| № | Шаг / Сценарий | Действие / HTTP-запрос | Ожидаемый результат | Статус |
|
||
| ------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | :----: |
|
||
| **1** | **Системные проверки (Health)** | `GET /api/v1/staff/live`<br>`GET /api/v1/staff/ready`<br>`GET /api/v1/staff/version` | HTTP 200 на всех эндпоинтах. `ready` подтверждает соединение с БД под ролью `krona_staff_app`. `version` возвращает `runtime: standalone` и semver. | [ ] |
|
||
| **2** | **Контракт OpenAPI** | `GET /api/v1/staff/openapi.json` | HTTP 200, возвращается валидная спецификация OpenAPI 3.1 со всеми роутами модуля. | [ ] |
|
||
| **3** | **Защита доступа (401)** | `GET /api/v1/staff/portal` без заголовка Authorization | HTTP 401 Unauthorized. Ошибки с деталями SQL отсутствуют. | [ ] |
|
||
| **4** | **Защита от CSRF** | `POST /api/v1/staff/portal/employees` с cookie сессии, но без заголовка `Origin` или с чужим Origin | HTTP 403 Forbidden (CSRF token missing/invalid). | [ ] |
|
||
| **5** | **Защита от перебора паролей (429)** | 35 последовательных запросов с неверным токеном/ключом | После исчерпания лимита возвращается HTTP 429 Too Many Requests с заголовком `Retry-After`. | [ ] |
|
||
| **6** | **Создание сотрудника** | `POST /api/v1/staff/employees`<br>Headers: `Authorization: Bearer <MANAGER_TOKEN>`<br>Body: `{"personnel_number": "EMP-001", "display_name": "Иванов Иван", "profession_code": "cook"}` | HTTP 201 Created. Создан сотрудник, присвоен ULID, запись зафиксирована в `staff_employees`. | [ ] |
|
||
| **7** | **Проверка справочника профессий** | `GET /api/v1/staff/professions` | HTTP 200, список активных профессий (`cook`, `cashier`, `barista`, `waiter` и др.). | [ ] |
|
||
| **8** | **Назначение тарифной ставки сотруднику** | `POST /api/v1/staff/payroll/assignments`<br>Body: привязка правила (почасовая ставка 350 руб/час = 35000 minor units). | HTTP 201 Created. Версионированное правило сохранено в `staff_pay_assignments`. | [ ] |
|
||
| **9** | **Отметка прихода через терминал** | `POST /api/v1/staff/attendance/events`<br>Headers: `Authorization: Bearer <TERMINAL_TOKEN>`<br>Body: `{"event_id": "<ULID>", "employee_id": "<EMP_ID>", "event_type": "check_in", "occurred_at": "<NOW_ISO>"}` | HTTP 201 Created. Событие записано в `staff_attendance_events`. Отправлено событие в outbox. | [ ] |
|
||
| **10** | **Идемпотентность отметки** | Повтор запроса шага 9 с тем же `event_id` и `source_event_id` | HTTP 200 OK (или 201), дубликат не создаётся, количество записей не удваивается. | [ ] |
|
||
| **11** | **Отметка ухода через терминал** | `POST /api/v1/staff/attendance/events` с `event_type: "check_out"` спустя 8 часов | HTTP 201 Created. Формируется завершённая рабочая смена. | [ ] |
|
||
| **12** | **Подтверждение смены менеджером** | `POST /api/v1/staff/work-sessions/:id/confirm` | HTTP 200 OK. Смена переходит в статус `confirmed`, часы готовы к начислению. | [ ] |
|
||
| **13** | **Авторасчёт ведомости зарплаты** | `POST /api/v1/staff/payroll/runs/auto-calc`<br>Body: период с 1 по 15 число месяца. | HTTP 201 Created. Начислено: 8 ч × 350 руб = 2800 руб (280000 minor units). Сохранена расшифровка формулы расчёта. | [ ] |
|
||
| **14** | **Согласование ведомости бухгалтером** | `POST /api/v1/staff/payroll/batches/:id/approve` | HTTP 200 OK. Статус пакета переходит в `approved`. | [ ] |
|
||
| **15** | **Подписание ведомости директором** | `POST /api/v1/staff/payroll/batches/:id/sign`<br>Body: `{"signature": "director_sign_key", "note": "Выплата за первую половину месяца"}` | HTTP 200 OK. Пакет подписан. Созданы строки начислений в `staff_payroll_ledger` с полем `note` (без ошибки 500!). | [ ] |
|
||
| **16** | **Проверка реестра выплат (Ledger)** | `GET /api/v1/staff/payroll/ledger` | HTTP 200 OK. Отображаются начисления и выплаты, сальдо корректно сходится. | [ ] |
|
||
| **17** | **Web Push уведомления сотруднику** | `POST /api/v1/staff/portal/push-subscriptions` с валидным FCM/W3C endpoint. | HTTP 201 Created. Подписка сохранена. При наступлении события формируется delivery без ложных блокировок хоста. | [ ] |
|
||
| **18** | **Уведомления директору без дублей** | `GET /api/v1/staff/notifications/unread` под учётной записью директора | HTTP 200 OK. Каждому директору отдаются ровно его уведомления без дублирования на каждого получателя. | [ ] |
|
||
| **19** | **Офлайн-синхронизация: регистрация терминала** | `POST /api/v1/staff/sync/devices/register` с токеном менеджера | HTTP 201 Created. Возвращается токен устройства `kstaff_dev_...`. Токен сохраняется в терминале. | [ ] |
|
||
| **20** | **Офлайн-синхронизация: начальный Pull (C0)** | `GET /api/v1/staff/sync/pull` с токеном устройства (без курсора) | HTTP 200 OK. Возвращаются справочники (сотрудники, оргструктура, тарифы) и курсор `cursor_ulid`. | [ ] |
|
||
| **21** | **Офлайн-синхронизация: пакет команд Push** | `POST /api/v1/staff/sync/push` с массивом офлайн-отметок посещаемости | HTTP 200 OK. Все корректные команды применены, битые команды получают понятную ошибку, статус транзакции не ломает весь пакет. | [ ] |
|
||
| **22** | **Отзыв прав уволенного сотрудника** | Перевод сотрудника в статус `fired` / деактивация доступа в `staff_access_grants` | Попытка входа уволенного сотрудника немедленно возвращает HTTP 401 Unauthorized. | [ ] |
|
||
| **23** | **Проверка утилиты миграции арендатора (Rehome)** | `node scripts/rehome-tenant.mjs --dry-run --source <OLD_ULID> --target <NEW_ULID>` | Утилита производит корректное сравнение количества строк по всем таблицам (`Number`) без сбоев типов и ошибок внешних ключей. | [ ] |
|
||
|
||
---
|
||
|
||
## 4. Диагностика и устранение неполадок
|
||
|
||
1. **Ошибка `401 Unauthorized` при запросе от терминала**:
|
||
- Проверьте, передан ли заголовок `Authorization: Bearer <STANDALONE_TERMINAL_TOKEN>` или токен зарегистрированного устройства.
|
||
2. **Ошибка `403 Forbidden` при запросах из веб-интерфейса**:
|
||
- Убедитесь, что запрос отправляется с доверенного Origin (`localhost`, `127.0.0.1` или IP пилота). Сессионные мутирующие запросы требуют валидный Origin.
|
||
3. **Проверка логов транзакций и RLS**:
|
||
```bash
|
||
# Просмотр логов контейнера
|
||
docker compose logs -f app
|
||
```
|
||
4. **Проверка состояния миграций БД**:
|
||
```bash
|
||
node scripts/check-migrations.mjs
|
||
```
|