BR 3.5 — Импорт сотрудников из PayKeeper

Статус — драфт

Требования сформированы для обсуждения. Не начинать реализацию — сначала проходит /verify. Эталон структуры: BR 3.4.

Контекст

Кассиры в нашем ERP (employees в User Service) и в ЛК PayKeeper (user/operator) — это две разные сущности. Подробнее в трекере PK.

Реальный кейс: клиент уже использовал кассу 3-в-1 PayKeeper до подключения нашего ERP. У него в ЛК PK заведено 5–30 кассиров. При подключении ERP не хочется перебивать руками — нужна функция «Выгрузить из PK» по требованию владельца, чтобы достать существующих и связать/импортировать их в наш ERP.

POC API подтверждён (2026-04-27)

Endpoint GET /info/organization/users/ работает программно через Basic auth (те же pk_login/pk_password что используются для IMS API). Реальный пример ответа:

[{"id":"1","login":"admin","refund":"1000","admin":"true","invoices_only":false,"email":"spad20@yandex.ru","fio":""},
 {"id":"6","login":"et","refund":"3","admin":"true","invoices_only":false,"email":"et@paykeeper.ru","fio":"ETA"}]

Цель

Дать владельцу франшизы кнопку «Выгрузить из PK» в админке. По нажатию — pull списка кассиров из ЛК PK, показ диффа с существующими employees, wizard дозаполнения недостающих полей, импорт пакетом.

Принцип: при подключении ERP к PK ничего не происходит автоматически. Импорт — только по явному действию владельца.

§1. Скоуп

1.1. Что синхронизируем

Из ЛК PK тянем только пользователей (raw response endpoint’а /info/organization/users/):

Поле PKТипКуда идёт у нас
idstringpaykeeper_users.pk_user_id (mapping)
loginstringpaykeeper_users.pk_login (для матча с user_login в чеках от PK)
emailstring | null | ""employees.email (если null/пусто — wizard просит ввод)
fiostring | null | ""first_name + last_name (wizard разбирает по пробелу + ручная корректировка)
admin”true”/“false”информационно показываем владельцу, не маппим в наши permissions
refundstring числоне используем
invoices_onlyboolне используем

1.2. Что НЕ входит

  • Auto-push «новый сотрудник у нас → создать в PK» — отдельная BR (3.6 если будет)
  • Reverse-webhook от PK на изменение user в ЛК → к нам (PK не обещал такой webhook)
  • Удаление user в PK при удалении employee у нас — отдельная BR
  • Автоматический pull при подключении ERP к PK — только по нажатию кнопки
  • Периодический cron-pull — на старте не нужен; добавим в P1 если будет запрос
  • Двусторонний sync (PK правит → ERP обновляет и наоборот) — слишком сложно, конфликты, не нужно для MVP

§2. Сценарий использования

  1. Владелец франшизы открывает раздел «Сотрудники» в админке
  2. Видит кнопку «Выгрузить из PK» (рядом с «Добавить сотрудника»)
  3. Нажимает → spinner → backend вызывает PK API → возвращается список кандидатов с предварительным матчингом
  4. Wizard показывает таблицу:
    • Каждая строка — один пользователь PK
    • Колонки: login, email, fio, admin, статус матча, действие
    • Статус матча: 🟢 Match by email (есть наш employee с тем же email) / ⚪ New (нет совпадений)
  5. Владелец per-row выбирает действие:
    • Для Match — 4 опции (см. §3)
    • Для New — открывается форма дозаполнения (см. §4)
  6. Submit — пакетный импорт, отчёт о результатах:
    • N создано, M связано, K пропущено, L ошибок
  7. История прогонов доступна по кнопке «Журнал импортов»

§3. Дубли email — 4 опции владельца

Когда pk_user.email совпадает с существующим employees.email (в рамках franchise_id):

ОпцияЧто происходит
СвязатьСоздаём mapping paykeeper_users(pk_user_id, employee_id). Existing employee не меняется.
Создать новогоWizard просит ввести альтернативный email → POST /employees с новыми данными
ПропуститьНичего не делаем; в журнал импорта пишется skipped: duplicate email, owner choice
ОбновитьPATCH /employees/{id}: записываем login в mapping, fiofirst/last_name только если у нас эти поля пустые (не перезаписываем непустое)

§4. Дозаполнение полей (wizard для New)

PK даёт минимум — обязательные поля employees дозаполняет владелец:

Поле employeeИз PKWizard
first_name, last_namesplit fio по пробелуМожно скорректировать
emailпрямойЕсли пусто/null — обязательный ввод
passwordОпции: «Сгенерировать + reset-link на email» / «Ввести вручную»
franchise_idиз JWT, автоматически
phoneОпционально, ручной ввод
pin (для POS)Опционально
is_courierДефолт false
roles[] + role_store_mappingsMulti-select из активных permissions-ролей + per-role выбор ТТ

§5. Ролевая модель

Импорт сотрудников из PK — операция уровня владельца ЛК.

ДействиеВладелец франшизыВладелец партнёраМенеджер ТТКассир
Видеть кнопку «Выгрузить из PK»✅ (только по своим ЛК PK)
Запустить импорт
Видеть журнал импортов✅ (свои ЛК)

Permission: paykeeper.users.import (новый, только в наборах системной роли «Администратор» и владельца партнёра).

§6. Сущности (бизнес-уровень)

Технические таблицы — в 03-Services/Paykeeper Adapter/Data Model.md.

paykeeper_users — связь нашего employee с пользователем PK:

  • pk_user_id (id из PK)
  • pk_login (для матча с user_login в чеках от PK)
  • employee_id (FK)
  • account_id (какой ЛК PK)
  • UNIQUE (account_id, pk_user_id) — один pk_user в одном ЛК = одна запись
  • UNIQUE (account_id, employee_id) — один employee связан только с одним pk_user в данном ЛК

paykeeper_user_imports — история прогонов:

  • Когда запущено, кем, статус, сколько создано / связано / пропущено / ошибок

§7. Метрики и алерты

  • paykeeper_user_import_runs_total{result} — счётчик прогонов
  • paykeeper_user_import_users_total{action} — кол-во импортированных по типу действия (created / linked / skipped / errored)
  • Алерт: PK API недоступен >5 мин при попытке импорта → admin notification

§8. Открытые вопросы

  1. admin: true в PK — игнорировать или предложить владельцу галочкой «Выдать роль Администратор у нас»? Дефолт: игнорировать, наша permissions-модель глубже плоского флага PK. Решить на kick-off.
  2. Системные пользователи PK (login: admin, login: user) — импортировать или пропускать? Дефолт: показываем с пометкой «системный пользователь PK», владелец сам решает.
  3. Phone — PK не отдаёт. Дозаполнение через wizard — ОК, либо запросить у PK (низкий приоритет).
  4. Пагинация — на практике ≤30 пользователей в ЛК. Если у крупной франшизы >100 → нужна постраничность wizard’а. Решим позже на основе реальных данных.
  5. Match by login + email — что если у нас уже есть employee с тем же email и другим login в PK (могло сложиться при прошлом ручном дублировании)? Дефолт: по email, login остаётся как metadata в mapping.
  6. Reverse — мы создаём employee → нужно ли тут же предлагать создать в PK? В §1.2 это вынесено в отдельную BR; но возможно стоит здесь добавить чек-бокс «также создать в ЛК PK» при создании employee — простая фича, экономит шаг владельцу.

§9. Фазинг

ФазаЧтоСрок
A. Backendpaykeeper_users миграция + клиент PK для users + endpoints preview / import / importsСпринт 1
B. FrontendКнопка + wizard (3 шага: preview → дозаполнение per-user → результат)Спринт 1 (параллельно)
C. Журнал импортовТаблица истории прогонов с детализациейСпринт 2
D. Связка чековПри получении чека от PK с user_login находим связанный employee для отчётов «выручка по кассиру»Спринт 2

§10. План работ — файлы под создание/правку

CREATE (новые файлы)

  • 08-Specs/Интеграции/PayKeeper.md — добавить раздел «Импорт сотрудников из PK» (бизнес-правила, 4 опции дублей, дозаполнение)
  • 09-Frontend Specs/Админка Франшизы/Импорт сотрудников из PayKeeper.md — фронт-спека wizard’а (3 шага, состояния, ошибки)
  • 07-Tasks/Decomposition/3.5 Импорт сотрудников/Overview.md
  • 07-Tasks/Decomposition/3.5 Импорт сотрудников/Paykeeper Adapter.md — клиент PK + сервис импорта + контроллер
  • 07-Tasks/Decomposition/3.5 Импорт сотрудников/Admin BFF.md — proxy-routes
  • 07-Tasks/Decomposition/3.5 Импорт сотрудников/Admin Franchise.md — кнопка + wizard

EDIT (существующие)

  • 03-Services/Paykeeper Adapter/API.md — 4 endpoint’а:
    • POST /api/v1/paykeeper/employees/preview — pull списка из PK + diff
    • POST /api/v1/paykeeper/employees/import — пакетный импорт по решениям wizard’а
    • GET /api/v1/paykeeper/employees/imports — история прогонов
    • GET /api/v1/paykeeper/employees/imports/{id} — детали прогона
  • 03-Services/Paykeeper Adapter/Data Model.md — 2 новые таблицы (paykeeper_users, paykeeper_user_imports)
  • 03-Services/Paykeeper Adapter/Events.md — опционально топик paykeeper.users.imported для аудита
  • 03-Services/Paykeeper Adapter/Overview.md — обновить зону ответственности (добавить «импорт пользователей из PK»)
  • 07-Tasks/PayKeeper Integration/Требования к PayKeeper.md — зафиксировать GET /info/organization/users/ как подтверждённый рабочий (POC сделан 2026-04-27); добавить в открытые вопросы — отдаёт ли PK поле phone где-то ещё

CODE (репозитории — после verify)

  • erp-paykeeper-adapterPayKeeperUsersClient, UserImportService, UserImportController, миграции, тесты
  • erp-admin/bff — proxy-routes на adapter
  • erp-admin/web — кнопка «Выгрузить из PK» + WizardImportEmployees компонент

NO CHANGES в erp-user-service — переиспользуем существующий POST /api/v1/employees.

§11. Verification

После реализации (на erp-test.nirbi.ru + koala-test ЛК PK):

  1. Войти владельцем → раздел Сотрудники → «Выгрузить из PK»
  2. Должен показаться список 4 пользователей из koala-test
  3. Wizard:
    • et (et@paykeeper.ru, ETA) — если у нас нет такого email → форма дозаполнения
    • admin — если у нас уже есть admin@франшиза → 4 опции выбора
    • user (email: null, fio: null) — wizard требует ввести email + ФИО
  4. Submit → проверить в БД: paykeeper_users (4 записи mapping), employees (новые с franchise_id)
  5. Повторный pull → не дублирует (по mapping видим уже импортированных)
  6. PK недоступен → 503 с понятным сообщением, импорт не запускается
  7. Чек от PK с user_login=et → находим связанного employee → отчёт «выручка по кассиру ETA» работает

§12. Ссылки