Skip to main content
Вход — JSON выгрузки из HR-системы (структура клиента, как есть):
Каждый item обрабатывается так: PUT /saas/v2/user/ext/{extId}/update (обновление карточки по внешнему идентификатору) — если сотрудника нет (NotFound), POST /saas/v2/user/create (создаёт пользователя и нанимает его — массив extra.staff.employments). Пользователь, подразделение и должность адресуются по extId напрямую — внутренние id нашей системы не нужны вообще. extra — контейнер сопутствующих сущностей: сейчас staff.employments, позже — значения кастомных полей и т.п.

Что реально уходит в API на Иванова: маппинг 1-в-1

Входной item из выгрузки:
Что уходит в API (новый сотрудник — создание + наём одним вызовом):
Ответ: { "user": { ... } } — пользователь создан и нанят в «ПОДР-001» на должность «Менеджер» одним вызовом. Существующий сотрудник — обновление карточки по extId, внутренний id не нужен (если пользователя нет — вернётся NotFound, тогда идём в create):
Без extra.staff — намеренно: повторная передача назначения безвредна (идемпотентный no-op), но если сотрудника в 1С перевели в другое подразделение, extra создаст ему второе назначение (совместительство), а не перевод. Кадровые изменения — через /staff/employment/*. extra в update тоже поддерживается — тот же формат, что и в create. Разберём, что произойдёт с каждым элементом employments:
Правило простое: extra.employments умеет только «убедиться, что назначение есть» (нет — нанять, есть — пропустить). Оно НЕ умеет закрывать/менять существующие назначения:
  • сотрудника перевелиPOST /staff/employment/ext/{extId}/transfer;
  • повысили (сменилась должность) → POST /staff/employment/ext/{extId}/promote;
  • уволилиPOST /staff/employment/ext/{extId}/terminate.
И зеркальное следствие: если существующее назначение НЕ передать в employments — с ним НИЧЕГО не произойдёт, оно останется активным. Массив — это не «полный список для синхронизации»: отсутствие назначения в нём ≠ увольнение с него. Убрать назначение можно только явным вызовом terminate. Поэтому слать можно хоть одно назначение, хоть все — перечисленные донаймутся/пропустятся, неперечисленные не тронутся. Если при переводе прислать в extra новую пару — получите сотрудника с ДВУМЯ активными назначениями (старым и новым), а не перевод. Совместительство — несколько назначений в одном массиве (до 10):
Вариант без email — вход по domain + password (domain строится из login заменой недопустимых символов на _):

Что делать, когда в 1С что-то изменилось

Цикл выше — идемпотентный: его можно гонять хоть каждый час, повторные прогоны безопасны. Карточные изменения он подхватывает сам, а вот кадровые — нет, для них есть отдельные вызовы. Все они адресуются по extId назначения (тот самый extId, который вы передали в элементе employments при найме — поэтому его важно задавать). Примеры кадровых вызовов (extId назначения из примера выше — i.ivanov.01011990:ПОДР-001):
Нюанс с extId при transfer/promote: назначение закрывается и открывается новое, но extId переезжает на новую запись — после перевода тот же i.ivanov.01011990:ПОДР-001 продолжает указывать на действующее назначение сотрудника. Если в extId зашит код подразделения (как в примере), он перестанет совпадать с фактическим департаментом — это не ошибка, просто ключ. Хотите точности — используйте в качестве extId идентификатор записи о назначении из 1С.

Предусловия

  • Подразделения (ПОДР-001 и т.д.) должны быть созданы заранее: POST /saas/v2/staff/department/create { "name": "Отдел продаж", "extId": "ПОДР-001", "parentExtId": "ПОДР-000" }. Если departmentExtId не найден — create вернёт StaffDepartmentNotFound и пользователь создан не будет.
  • Кадровые изменения существующего сотрудника идут через employment-endpoint’ы по extId назначения: перевод — POST /staff/employment/ext/{extId}/transfer { "toDepartmentExtId": "ПОДР-002" }, смена должности — .../promote { "toPositionExtId": "<GUID>" }, увольнение — .../terminate. Новое назначение (в т.ч. совместительство) — POST /staff/employment/hire, либо extra.staff.employments в user/update/user/upsert — донанимает идемпотентно (повтор той же активной пары департамент + должность — no-op); переводы и увольнения через extra не выражаются.
  • Увольнение с последнего активного назначения автоматически закрывает аккаунт: user.status становится Terminated, все сессии завершаются, вход блокируется. Повторный наём (hire) автоматически возвращает Active. Уволить владельца школы или самого себя нельзя (StaffCannotTerminateSchoolOwner / StaffCannotTerminateSelf).
  • Статус Blocked (блокировка админом) ставится через user/update { "status": "Blocked" }: вход закрыт, сессии завершаются, новые назначения курсов запрещены; данные сохраняются и видны в отчётах. { "status": "Active" } снимает блокировку, доступы восстанавливаются. Владельца школы заблокировать нельзя.
  • Отсутствия управляют статусом OnLeave: когда у сотрудника начинается действующее absence (отпуск/больничный и т.д.), user.status автоматически становится OnLeave, по завершении/удалении — возвращается в Active. OnLeave — информационный статус: вход в систему остаётся доступен.
  • leader_external_ids мапится не на сотрудника, а на руководителя подразделения — отдельный вызов POST /saas/v2/staff/department-manager/set { "departmentExtId": "ПОДР-001", "employmentExtId": "<extId назначения>", "isPrimary": true }.
  • status из выгрузки мапится на user.status: «Работает» → "Active". «Уволен» напрямую статусом не передаётся — увольнение оформляется через employment/terminate, и Terminated выставится автоматически. OnLeave тоже вручную не ставится — им управляют absences.
  • role/tag_list/city/email_for_notifications — соответствия в API нет, поля игнорируются.
  • Сотрудники без email: передавайте domain (латиница/цифры/_/точки — точка не по краям и не подряд; сервер приведёт к lowercase, уникален в рамках школы — занят → DomainIsBusy) и обязательно password — без email и телефона domain+password становится единственным способом входа.
  • Должности — по GUID (positionExtId), не по имени: имя изменяемо и не уникально на стороне 1С («добавят мягкий знак — интеграция сломается»). GUID необязателен: без него работает fallback по имени. Ограничение: имя должности уникально в рамках школы — две должности с одинаковым именем, но разными GUID (разные организации в 1С) создать нельзя (StaffPositionNameIsNotUniq) — сведите GUID’ы к одному или не передавайте GUID у второй организации.

Обновлено: 2026-07-09 07:40 UTC