krona-staff/docs/APP-CORE.md
Ochenstarik 3464998168
Some checks are pending
ci / verify (push) Waiting to run
feat: add secure attendance portal and payroll foundation
2026-09-24 15:59:38 +07:00

13 KiB
Raw Permalink Blame History

Ядро мобильного приложения Krona Business Platform

Документ для команды, которая делает приложение (в том числе для Codex). Описывает каркас, в который модули платформы добавляют свои экраны. Staff — первый модуль; касса, финансы и остальное подключаются тем же способом.

Правило номер один: приложение не считает деньги. Все суммы, смены и остатки приходят с сервера уже рассчитанными, вместе с объяснением, из чего они сложились. Клиент их только показывает. Это условие проверяемости расчёта и требование ADR-002.

1. Стек

Что Решение Почему
Каркас React Native + Expo (managed), TypeScript strict тот же язык, что и модули платформы; типы API переиспользуются, сборка Android и iOS без Mac через EAS
Навигация Expo Router файловая маршрутизация, удобна для подключения экранов модулями
Серверное состояние TanStack Query + persist кэш, повторы, офлайн-чтение
Локальное состояние Zustand как во фронтенде платформы
Формы и валидация React Hook Form + Zod Zod уже используется в модулях
Секреты на устройстве expo-secure-store refresh-токен и PIN-хеш только здесь
Биометрия expo-local-authentication разблокировка, не аутентификация на сервере
Уведомления expo-notifications push; пока Core не отдаёт push, канал Telegram остаётся запасным
Тесты Vitest + React Native Testing Library, Maestro для сценариев —

Совместимость: Android 9+, iOS 15+. Публикация: RuStore и Google Play, TestFlight/App Store — по решению владельца.

2. Структура

app/                     экраны Expo Router: (auth), (tabs), modules/<module>/…
src/core/
  api/                   http-клиент, конверт ответа, ошибки, Idempotency-Key
  auth/                  сессия, refresh, PIN, биометрия, приглашения
  modules/               реестр модулей: вкладки, экраны, права
  ui/                    дизайн-система: токены, компоненты, типографика
  i18n/                  ru по умолчанию
  telemetry/             логи без персональных данных
src/modules/staff/       экраны и запросы модуля Staff

Модуль регистрируется в реестре декларативно и не правит ядро:

registerModule({
  id: 'ru.krona.staff',
  title: 'Работа',
  requiredPermissions: ['staff.payslip.read_own'],
  tabs: [{ name: 'today', icon: 'clock', screen: TodayScreen }],
  routes: [{ path: 'staff/payroll/:runId', screen: PayrollRunScreen }],
});

Ядро показывает вкладку, только если у пользователя есть право и модуль включён у арендатора. Это зеркалит слоты платформы (34-MODULE-SYSTEM), чтобы приложение не расходилось с вебом.

3. Аутентификация

Учётными записями владеет ядро платформы, приложение не заводит своих пользователей и не хранит пароли.

Приглашение (решение владельца). Администратор создаёт учётку сотрудника и отправляет приглашение одним из трёх способов: письмо на почту, ссылка в мессенджер, QR-код на экране. Все три ведут на один одноразовый токен приглашения.

Первый вход:

  1. переход по приглашению (или сканирование QR в приложении);
  2. сотрудник задаёт пароль — уходит в Core, на устройстве не остаётся;
  3. сотрудник задаёт PIN из 6 цифр — хранится только как хеш в secure-store, сервер о нём не знает;
  4. если на телефоне есть биометрия, предлагается включить её вместо PIN.

Дальнейшие входы: PIN или биометрия разблокируют refresh-токен в secure-store, ядро меняет его на access-токен. Пароль нужен только при смене устройства и после отзыва сессии.

Токены: access-JWT живёт 15 минут и хранится только в памяти; refresh — непрозрачный, в secure-store, с привязкой к Device ID. Три неверных PIN подряд — пауза, десять — сброс локальной сессии и вход по паролю.

Чего сегодня не хватает в ядре платформы (проверено по коду ochenstarik/business-platform): поток приглашения реализован как каркас, восстановление пароля отсутствует, права модулей нельзя выдать роли. До их готовности модуль Staff в режиме standalone выдаёт собственные токены для пилота — это временный режим, который выключается переключением на platform. Собственную модель пользователей в приложении заводить нельзя ни при каких условиях.

4. Работа с API

  • База: https://<host>/api/v1/<module>. Для Staff — /api/v1/staff.
  • Заголовки каждого запроса: Authorization: Bearer <access>, X-Tenant-ID, X-Request-Id (ULID, генерирует клиент).
  • Успешный ответ: {"data": …, "meta": …}. Ошибка: {"errors": [{"code": "…", "message": "…", "field": "…"}], "meta": {"request_id": "…"}}. Показывать пользователю message, в лог писать code и request_id.
  • Любая мутация несёт Idempotency-Key (UUID v4, генерирует клиент и сохраняет до подтверждения). Повтор с тем же ключом и другим телом получает 409.
  • 401 → однократный refresh и повтор запроса; второй 401 → выход на экран входа.
  • Чужой ресурс возвращает 404, а не 403 — не показывать это как «нет прав».
  • Деньги приходят как {"amount_minor": "623100", "currency_code": "RUB"}, строкой. Клиент не приводит к числу с плавающей точкой: форматирование только через хелпер formatMoney, который работает со строкой и делит на 100.
  • Типы генерируются из api/openapi.yaml модуля через openapi-typescript; рукописные интерфейсы ответов запрещены.

5. Экраны Staff, версия 1

Экран Содержание
Сегодня текущая смена: точка, время прихода, идёт ли смена, предварительное начисление за день с пометкой «до подтверждения управляющим»
Мои смены список за период: дата, точка, часы, статус (ожидает подтверждения, подтверждена, отклонена)
Моя зарплата неделя: начислено, выдано, остаток. Внутри — по дням: выручка точки не показывается, показываются часы, доля фонда, почасовая, надбавка, итог
Детализация дня объяснение суммы: «фонд точки ÷ 3 участника + 12 ч × 100 ₽ + надбавка 500 ₽»
Профиль имя, табельный номер, точка, выход, смена PIN, включение биометрии

Сотрудник видит только свои данные. Ни один экран не должен запрашивать данные других сотрудников; при попытке сервер ответит 404.

6. Офлайн

Приложение работает в кафе с плохой связью, поэтому:

  • чтение — из кэша TanStack Query с сохранением на диск, с явной пометкой «данные на такое-то время»;
  • мутации (их мало: подтверждение прочтения, смена PIN) — очередь с Idempotency-Key и повтором;
  • денежные суммы никогда не считаются на клиенте, даже офлайн: если данных нет, показывается прочерк, а не оценка.

7. Что делает сервер, а не приложение

  • расчёт начислений, фонда, долей, гарантий, округлений;
  • определение, какие смены попали в расчёт;
  • проверка прав: приложение прячет кнопки, но решает всегда сервер;
  • агрегаты за неделю и остаток.

8. Порядок работ

  1. Ядро: навигация, дизайн-токены, http-клиент, обработка ошибок, реестр модулей.
  2. Аутентификация: приглашение, пароль, PIN, биометрия, refresh, Device ID.
  3. Модуль Staff: «Сегодня», «Мои смены», «Моя зарплата», «Детализация», «Профиль».
  4. Push-уведомления: отметка о приходе, подтверждение смены, утверждение ведомости.
  5. Сборка: EAS profiles (development, preview, production), публикация в RuStore.

Серверная часть (расчёт, ведомость, подтверждение смен, API) делается в репозитории krona-staff и к моменту пункта 3 отдаёт готовый контракт в api/openapi.yaml.