Импорт сотрудников из PayKeeper — wizard
Источник требований
BR 3.5 Бизнес-спека: PayKeeper · Импорт сотрудников из PayKeeper
Pull-импорт существующих кассиров из ЛК PayKeeper в наш ERP. Запускается по требованию владельца — кнопкой в разделе «Сотрудники». PK даёт минимум полей (id, login, email, fio, admin, refund, invoices_only), остальные обязательные у нас (first_name, last_name, password, phone, pin, roles, stores) дозаполняет владелец в wizard’е.
1. Кнопка «Выгрузить из PK» в /employees
Где расположена
В шапке списка сотрудников /employees рядом с кнопкой «Добавить сотрудника». Также рядом — кнопка «Журнал импортов» (см. §4).
Видимость и активность
| Условие | Поведение |
|---|---|
Нет integrations.read | Кнопка скрыта |
Есть integrations.read, но нет integrations.manage ИЛИ нет employees.edit | Кнопка видна, но disabled, tooltip «Недостаточно прав» |
Нет ни одного active PK-аккаунта во scope | Disabled, tooltip «Сначала подключите PayKeeper в карточке ЮЛ» |
Все PK-аккаунты suspended | Disabled, tooltip «Все интеграции PayKeeper приостановлены» |
| Permissions есть AND ≥1 active PK-аккаунт | Активна |
Поток при клике
- Загрузить
GET /api/v1/admin/paykeeper/accounts?status=active(отфильтровать по scope) - Если 1 ЛК → сразу переход на
/employees/import-from-paykeeper?account_id={id}(Шаг 1 wizard’а) - Если >1 ЛК → сначала модалка выбора (см. §2)
- Если 0 active (race condition с шагом загрузки кнопки) → toast «Нет активных интеграций PayKeeper»
2. Модалка выбора ЛК PK (только при >1)
┌─ Выберите ЛК PayKeeper для импорта ────────────────┐
│ │
│ ○ ООО «Ромашка» │
│ example.server.paykeeper.ru │
│ │
│ ○ ИП Петров │
│ petrov.server.paykeeper.ru │
│ │
│ Один импорт — для одного ЛК. │
│ Чтобы импортировать из другого ЛК — повторите │
│ процедуру. │
│ │
│ [Отмена] [Продолжить →] │
└────────────────────────────────────────────────────┘
- Имя ЮЛ + хост ЛК — для каждого аккаунта
- Per-account preview не делаем (не дёргаем PK заранее — это лишний трафик)
- Submit → переход на
/employees/import-from-paykeeper?account_id={selected}
3. Wizard — 3 шага
Реализован как отдельная страница /employees/import-from-paykeeper?account_id={id}. Преимущества по сравнению с модалкой: URL для возврата по back, удобнее работать с большим списком кандидатов.
В шапке страницы:
- Crumbs:
Сотрудники / Импорт из PayKeeper - Под crumbs — индикатор шагов:
① Кандидаты ──── ② Дозаполнение ──── ③ Результат - Имя ЮЛ + хост ЛК PK — справа в шапке для контекста
Шаг 1: Список кандидатов
Загрузка
- API:
POST /api/v1/admin/paykeeper/accounts/{id}/employees/preview - Состояние loading: skeleton-таблица + текст «Получаем список из PayKeeper…»
- Ошибки:
ACCOUNT_NOT_ACTIVE(422) → блокирующий блок «Интеграция PayKeeper приостановлена. Возобновите в карточке ЮЛ для запуска импорта.» + кнопка «Закрыть»PK_CONNECTION_FAILED(422) → блок «Не удалось получить список из PayKeeper: {msg}» + кнопки «Повторить» / «Закрыть»- Empty list (PK вернул
[]) → блок «В этом ЛК PayKeeper нет пользователей. Заведите кассиров в ЛК PayKeeper → Настройки → Доступ к панели администратора, затем повторите импорт.» + кнопка «Закрыть»
Таблица кандидатов
| ☐ | Логин PK | ФИО | Admin | Статус матча | Действие | |
|---|---|---|---|---|---|---|
| ☐ | admin система | spad20@yandex.ru | — | ✓ | 🆕 Новый | [Создать ▼] |
| ☐ | et | et@paykeeper.ru | ETA | ✓ | 🟡 Совпадает email | [Связать ▼] |
| ☐ | koala | — | — | — | 🆕 Новый | [Создать ▼] |
| ☐ | user система | — | — | — | 🔗 Уже импортирован | (disabled) |
Чекбокс — для bulk-actions (выбрать всё / снять выделение / применить одно действие к выбранным). На MVP — оставить, реализация bulk-actions опционально (можно сделать как отдельный кнопочный ряд «Применить ко всем выбранным: [действие ▼]»).
Бейдж «система» — для пользователей с pk_login равным admin / user (системные технические аккаунты PK). Tooltip: «Системный пользователь PayKeeper. Обычно это технический аккаунт ЛК — импортировать необязательно».
Колонка «Статус матча» — 3 значения:
| Статус | Иконка | Текст | Подсказка |
|---|---|---|---|
new | 🆕 | Новый | — |
matched_email | 🟡 | Совпадает email | Tooltip: «У вас уже есть сотрудник {matched.first_name} {matched.last_name} с этим email. Выберите как поступить.» |
already_linked | 🔗 | Уже импортирован | Под строкой серый текст: «Связан с {linked_employee.first_name} {linked_employee.last_name} ({linked_employee.email})» |
Колонка «Действие» — выпадающий список, варианты зависят от статуса:
| Статус | Доступные действия |
|---|---|
new | Создать (default) / Пропустить |
matched_email | Связать (default) / Создать с другим email / Обновить / Пропустить |
already_linked | (selector disabled) |
Кнопка «Далее →»
- Активна если хотя бы один кандидат имеет действие отличное от
ПропуститьиУже импортирован - Tooltip при disabled: «Выберите хотя бы одно действие отличное от «Пропустить»
- Действие: переход на Шаг 2
Шаг 2: Дозаполнение полей
Для каждой строки с действием Создать, Создать с другим email, Обновить — раскрытая форма accordion. Для Связать и Пропустить — компактная свёрнутая строка без формы (нечего дозаполнять).
Раскрытая форма
▼ admin (новый, действие: Создать)
Имя* [Иван ] ← split(fio)[1] или пусто
Фамилия* [Петров ] ← split(fio)[0] или пусто
Email* [spad20@yandex.ru ] ← из PK
Пароль*
◉ Сгенерировать (отправим reset-link на email)
○ Ввести вручную
[_____________] [показать]
Телефон [+7 __________ ]
PIN-код [____] ← 4 цифры
☐ Курьер
Роли и магазины:
☐ Бариста Магазины: ☐ ТТ-1 ☐ ТТ-2 ☐ ТТ-3
☐ Менеджер зала Магазины: …
☐ Курьер Магазины: …
[+ Добавить роль]
Поля
| Поле | Кейс create_new | Кейс create_with_alt_email | Кейс update_existing |
|---|---|---|---|
first_name | pre-fill из split(fio)[1], required | pre-fill из split(fio)[1], required | pre-fill из existing employee, disabled (но если в existing пусто — required) |
last_name | pre-fill из split(fio)[0], required | pre-fill из split(fio)[0], required | то же |
email | pre-fill из PK, required, validate email format | пусто (PK email уже у другого), required | disabled (от matched employee) |
password | radio Генерировать/Ввести (см. ниже) | то же | скрыто (не меняем существующий пароль) |
generate_password | true (default) | то же | true (не используется) |
phone | optional | optional | pre-fill, optional |
pin | optional, 4 digits | то же | pre-fill, optional |
is_courier | default false | то же | pre-fill |
roles[] | пусто, optional | то же | pre-fill из existing, можно изменять |
Split FIO
ФИО в PK — одна строка. Дефолт wizard’а: split по пробелу, первое слово →
last_name, второе →first_name. Третье и далее (отчество) игнорируются. Еслиfioпустое или одно слово — соответствующие поля пусто, владелец вводит. Это деформируется при редких форматах PK — поэтому владелец всегда может скорректировать вручную.
Блок «Пароль»
Radio:
- ◉ Сгенерировать (default) — backend генерирует случайный пароль, отправляет reset-link на email сотрудника. Hint под radio: «Сотруднику придёт письмо со ссылкой для установки пароля».
- ○ Ввести вручную — input password с кнопкой «глазик», валидация min 6 символов
Скрыт для update_existing — пароль при импорте через update не меняется.
Multi-select ролей и stores
По образцу карточки сотрудника (Сотрудники — Карточка вкладка «Роли и магазины»):
- Загрузка ролей:
GET /api/v1/admin/roles?status=active&hidden=false— только обычные (не скрытые) - Per-role чекбокс «Все ТТ» (default false) — иначе multi-select из ТТ scope’а
Скрытая роль владельца партнёра не доступна (фильтр hidden=false).
Валидация перед submit
- Все required заполнены
- Email формат
- PIN — ровно 4 цифры (если введён)
- Если выбраны роли — у каждой указан хотя бы один магазин ИЛИ галочка «Все ТТ»
- Email уникален в рамках franchise_id (проверится backend’ом, см. §5 ошибки)
Кнопки внизу страницы
- ← Назад (возврат на Шаг 1, decisions сохраняются в state)
- Импортировать (submit) — disabled пока есть незаполненные required
Шаг 3: Результат
Прогресс
После клика «Импортировать» — спиннер «Импортирую {N} сотрудников…» с timeout 60s.
API: POST /api/v1/admin/paykeeper/accounts/{id}/employees/import
Если timeout (60s) — toast «Импорт занимает дольше обычного. Откройте «Журнал импортов» для проверки результата».
Экран успеха (status: success)
┌──────────────────────────────────────┐
│ ✓ │
│ Импорт завершён │
│ │
│ Создано: 2 новых сотрудника │
│ Связано: 1 существующий │
│ Обновлено: 0 │
│ Пропущено: 1 │
│ │
│ [Перейти к сотрудникам] │
│ [Импортировать ещё из этого ЛК] │
│ [Импортировать из другого ЛК] │
└──────────────────────────────────────┘
«Импортировать ещё из этого ЛК» — вернёт на Шаг 1 с тем же account_id (свежий fetch — добавятся новые 🔗 Уже импортирован для только что созданных).
«Импортировать из другого ЛК» — вернёт на /employees, открывая модалку выбора ЛК (если их >1) или сразу wizard для оставшегося.
Экран частичного успеха (status: partial)
┌──────────────────────────────────────┐
│ ⚠ │
│ Импорт завершён частично │
│ │
│ Создано: 1 новый сотрудник │
│ Связано: 0 │
│ Обновлено: 0 │
│ Пропущено: 0 │
│ Ошибок: 2 │
│ │
│ Не удалось импортировать: │
│ • et — Email уже используется │
│ другим сотрудником │
│ • koala — Не удалось создать │
│ сотрудника (500) │
│ │
│ [Перейти к сотрудникам] │
│ [Подробнее об ошибках] │
└──────────────────────────────────────┘
«Подробнее об ошибках» — модалка с полным errors_json (тот же UI что и в Журнале импортов §4).
Экран полного провала (status: failed)
Аналогично partial, но без счётчика «Создано». Текст: «Не удалось импортировать ни одного сотрудника».
4. Модалка «Журнал импортов»
Где открывается
Кнопка «Журнал импортов» на странице /employees рядом с «Выгрузить из PK». Видна при integrations.read AND есть хотя бы один импорт в истории (для любого ЛК во scope).
Если у пользователя несколько ЛК — модалка содержит фильтр по ЛК (default «Все ЛК»).
Загрузка
API: GET /api/v1/admin/paykeeper/accounts/{id}/employees/imports?limit=20
Если фильтр «Все ЛК» — backend возвращает union по всем активным во scope (либо фронт делает N запросов и склеивает; финальное решение — на этапе декомпозиции BFF).
Таблица
| Время | ЛК | Запустил | Длительность | Статус | Создано | Связано | Обновлено | Пропущено | Ошибок |
|---|---|---|---|---|---|---|---|---|---|
| 27.04, 14:30 | example | Иван Петров | 8 сек | ⚠ Частично | +2 | +1 | 0 | 1 | 0 |
| 25.04, 10:15 | example | Иван Петров | 12 сек | ✓ Успешно | +5 | +0 | 0 | 0 | 0 |
| 20.04, 11:00 | petrov | Анна Сидорова | 4 сек | ✓ Успешно | +3 | +0 | 0 | 0 | 0 |
- Длительность =
finished_at - started_at. Дляrunning— «идёт N с». - Статус:
✓ Успешно(success),⚠ Частично(partial),✗ Не удалось(failed),⟳ Идёт(running с polling 5s).
Раскрытие строки → детали
Клик на строку → inline-блок (accordion):
- API
GET /api/v1/admin/paykeeper/accounts/{id}/employees/imports/{run_id} - Если
errors_jsonнепустой — список:• admin (skipped) — пропущен пользователем • et (create_new) — Email уже используется другим сотрудником • koala (create_new) — Не удалось создать сотрудника: 500 INTERNAL_ERROR - Если ошибок нет — серый текст «Без ошибок»
Пагинация
20 на страницу, кнопка «Загрузить ещё» внизу (использует since=lastSeenStartedAt).
5. Состояния и ошибки API
| Код бэка | HTTP | Где возникает | Что показать |
|---|---|---|---|
ACCOUNT_NOT_ACTIVE | 422 | Шаг 1 (preview), Шаг 3 (import) | Блок «Интеграция PayKeeper приостановлена. Возобновите для импорта.» + «Закрыть» |
PK_CONNECTION_FAILED | 422 | Шаг 1 (preview) | Блок «Не удалось получить список из PayKeeper: {msg}» + «Повторить» |
VALIDATION_ERROR | 400 | Шаг 3 (import) | Подсветить проблемные строки на Шаге 2 (вернуться назад с error highlight) |
CONFLICT (email уник.) | 409 | Шаг 3 (import) | Пометить строку красным «Сотрудник с таким email уже существует» — пользователь возвращается в Шаг 2 |
FORBIDDEN | 403 | Любой шаг | Toast «Недостаточно прав» (теоретически не должно случиться — кнопка disabled) |
ACCOUNT_NOT_FOUND | 404 | Любой шаг | Toast «PK-аккаунт не найден или удалён» + переход на /employees |
NOT_FOUND (run) | 404 | Журнал импортов → детали | Toast «Прогон не найден» |
Особый кейс: PK вернул пустой список
Шаг 1 (preview) вернул data: []:
В этом ЛК PayKeeper нет пользователей.
Заведите кассиров в ЛК PayKeeper:
Настройки → Доступ к панели администратора
Затем повторите импорт.
[Закрыть]
6. Permissions — сводка
| Действие | Permission |
|---|---|
| Кнопка «Выгрузить из PK» видна | integrations.read |
| Кнопка «Выгрузить из PK» активна | integrations.manage AND employees.edit AND ≥1 active PK-аккаунт |
| Шаг 1 (preview) | integrations.manage AND employees.edit |
| Шаги 2–3 (import) | integrations.manage AND employees.edit |
| Кнопка «Журнал импортов» видна | integrations.read |
| Просмотр прогона в журнале | integrations.read |
integrations.read / integrations.manage / employees.edit — существующие permissions, новые не вводим (см. спека и Роли).