К содержимому

Импорт статусов существующих сделок

POST /v1/external/gck/cabinets/{cabinetId}/deals/status-import-jobs

Нужен JWT со scope cabinet:deals:write, доступ к кабинету и право управлять сделками. Поиск кабинета и чтение его каталога дополнительно требуют cabinet:read. Доступ проверяется при постановке, выполнении и чтении операции.

Изоляция и область изменения

Поле mode обязательно: localData или remote. Режим сохраняется с операцией и не меняется при повторе. Импорт не включает изоляцию.

localData требует опубликованные локальные данные изолированного кабинета, в том числе при временно нарушенной синхронизации. Источник истины — локальные сделки Alitos; статусы не отправляются во внешний кабинет ГЦК. По умолчанию latestDealOnly: false: обрабатываются все найденные сделки телефона. При true выбирается только последняя историческая сделка: дата создания, время создания, затем идентификатор по убыванию, включая историю за пределами текущего периода аналитики. Не найденные телефоны учитываются в notFoundPhones, новых сделок не создаётся. Запись статусов, аналитики и квитанции атомарна.

remote отправляет статусы в обычный внешний кабинет ГЦК по телефону. latestDealOnly: true здесь отклоняется. Внешний сервис определяет совпавшие сделки; успешная доставка не подтверждает наличие телефона или изменение конкретной сделки. Проверки текущих статусов и выгрузки на каждый телефон нет.

Существующие REST GET /v1/external/cabinets/{cabinetId}/context и /settings показывают доступ и безопасные настройки, но не публикуют режим изоляции. Уточните режим в настройках кабинета Alitos у оператора; не выводите его из scopeMode (это область доступа). Движок проверяет режим перед выбором маршрута. Несовпадение даёт status_import_mode_changed, без переключения на другой режим; после предыдущей попытки это не доказывает отсутствие уже выполненной записи.

Создание новых сделок выполняет отдельный импорт телефонов.

Данные и приоритеты

От 1 до 100 непустых групп, всего не более 1000 входных телефонов, включая дубликаты. Каждый телефон — строка до 64 символов: десять цифр либо одиннадцать с первой 7 или 8; форматирующие символы игнорируются. Ошибка хотя бы одного номера отклоняет весь запрос. Общий HTTP-лимит тела по умолчанию 1 MiB, фактический лимит доступен в capabilities; он применяется и к chunked-запросам (413). Эквивалентные форматы одного номера объединяются.

dealStatusId — идентификатор статуса каталога ГЦК (1–14), а не текстовая метка. Каталог аналитики кабинета показывает статусы 4–14 и их актуальные метки; системные статусы 1, 2, 3 — Новый, Просмотрен, Проработан. Переименование статуса не меняет его идентификатор.

statusPriorities необязателен: до 100 записей, каждый статус встречается один раз, ранг — целое число от 0 до 2147483647. Меньшее число означает более высокий приоритет. Для телефона в нескольких группах выигрывает статус с меньшим рангом; неуказанный ранг считается 2147483647. При равенстве выигрывает меньший dealStatusId, независимо от порядка групп. Время события не принимается.

Только в localData текущий статус найденной сделки защищён от замены, только если оба статуса указаны в statusPriorities и ранг текущего строго меньше нового. При равных рангах изменение разрешено. Неуказанный текущий или новый ранг защиту не даёт. Включайте в список и существующие статусы, которые хотите защитить, даже если для них нет входной группы. Приоритеты относятся к одному импорту и не сохраняются как настройки кабинета. В remote приоритет выбирает только входной статус на телефон; существующий внешний статус не защищается.

Запуск и получение результата

Сохраните один Idempotency-Key для логической операции. Пример Bash/curl:

curl --include --request POST \
  "https://api.alitos.io/v1/external/gck/cabinets/${CABINET_ID}/deals/status-import-jobs" \
  --header "Authorization: Bearer ${ALITOS_EXTERNAL_JWT}" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
  --data '{
    "reason": "Синхронизация итогов обработки",
    "mode": "localData",
    "latestDealOnly": true,
    "groups": [{"dealStatusId": 6, "phones": ["+7 900 123-45-67"]}],
    "statusPriorities": [
      {"dealStatusId": 9, "priority": 0},
      {"dealStatusId": 6, "priority": 1},
      {"dealStatusId": 5, "priority": 2}
    ]
  }'

Ответ 202 Accepted содержит operationId, status, Location и Retry-After. Читайте GET /v1/external/operations/{operationId}, соблюдая интервал Retry-After. После succeeded поле result содержит:

{
  "cabinetId": "123456789", "mode": "localData", "latestDealOnly": true,
  "inputPhones": 1, "acceptedPhones": 1,
  "updatedPhones": 1, "alreadyTargetPhones": 0,
  "priorityProtectedPhones": 0, "notFoundPhones": 0
}

inputPhones — исходное число строк, acceptedPhones — уникальные номера. В localData четыре итоговых счётчика относятся к телефонам, взаимно исключаются и в сумме равны acceptedPhones. succeeded допускает ненулевой notFoundPhones. В режиме всех сделок это счётчики телефонов, а не количества изменённых сделок: updatedPhones означает хотя бы одну разрешённую замену; alreadyTargetPhones — все найденные сделки уже имеют целевой статус; priorityProtectedPhones — есть отличающийся статус, но все такие сделки защищены; notFoundPhones — нет ни одной сделки. Для remote результат содержит общие поля и только deliveredPhones, rejectedPhones: доставленные и явно отклонённые внешним сервисом телефоны. Их сумма равна acceptedPhones; succeeded может иметь отклонённые телефоны. Поля updatedPhones, alreadyTargetPhones, priorityProtectedPhones, notFoundPhones отсутствуют: эти факты неизвестны. Пример:

{"cabinetId":"123456789","mode":"remote","latestDealOnly":false,
 "inputPhones":2,"acceptedPhones":2,"deliveredPhones":1,"rejectedPhones":1}

Данные задания хранятся в зашифрованном виде. Ответ не содержит телефонных строк; постраничные результаты пусты.

При потере ответа повторяйте тот же POST с тем же ключом, телом и причиной: вернётся та же операция, а изменение значимых параметров даст 409 idempotency_key_conflict. Рабочий процесс безопасно восстанавливает атомарную запись по её сохранённому результату. Не создавайте новый ключ для неопределённого результата: сначала прочитайте операцию и фактические статусы. В remote журнал хранит подтверждённые chunks: они не отправляются повторно. Неотправленный остаток можно продолжить с исходной идентичностью запроса; неопределённая отправка завершает задание как status_import_outcome_unconfirmed и требует сверки. Часть статусов могла быть доставлена. Таймаут сам по себе не означает отклонение; наличие ещё работающего исполнителя означает ожидание. Нельзя автоматически заменить ключ или режим после неизвестного результата. Исчерпание попыток не доказывает, что записи не было. При изменении режима кабинета после записи требуется сверка; переключение режима не разрешает повтор во внешнюю систему. Хранение операции ограничено её expiresAt; после истечения ключ не даёт защиту от повторной записи.

Валидация: status_import_mode_unsupported, status_import_groups_invalid, status_import_phone_invalid, status_import_priorities_invalid, status_import_latest_unsupported, phones_limit_exceeded (400). Общие 401/403, 413, 429, 503 и восстановление описаны в операциях и ошибках.