13 KiB
Ядро мобильного приложения 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-код на экране. Все три ведут на один одноразовый токен приглашения.
Первый вход:
- переход по приглашению (или сканирование QR в приложении);
- сотрудник задаёт пароль — уходит в Core, на устройстве не остаётся;
- сотрудник задаёт PIN из 6 цифр — хранится только как хеш в secure-store, сервер о нём не знает;
- если на телефоне есть биометрия, предлагается включить её вместо 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. Порядок работ
- Ядро: навигация, дизайн-токены, http-клиент, обработка ошибок, реестр модулей.
- Аутентификация: приглашение, пароль, PIN, биометрия, refresh, Device ID.
- Модуль Staff: «Сегодня», «Мои смены», «Моя зарплата», «Детализация», «Профиль».
- Push-уведомления: отметка о приходе, подтверждение смены, утверждение ведомости.
- Сборка: EAS profiles (
development,preview,production), публикация в RuStore.
Серверная часть (расчёт, ведомость, подтверждение смен, API) делается в репозитории krona-staff и к моменту пункта 3 отдаёт готовый контракт в api/openapi.yaml.