Skip to content

В обе стороны: синхронизация заявок

Двусторонняя синхронизация — единственная интеграция, которая не сводится к трубопроводу. Эндпоинты простые; работать или не работать будет из-за ответа на вопрос чья запись главная. Ответьте на него сначала на бумаге — и код окажется коротким.

Сначала — владение

Выберите один из трёх вариантов и зафиксируйте его. Четвёртого, который позволяет не выбирать, не существует.

СхемаКто что меняетКогда подходит
Главная — ваша системаЗаявки заводятся и ведутся в вашей системе эксплуатации. У нас — зеркало, наш интерфейс для них преимущественно на чтение.Диспетчеры продолжают работать там, где привыкли; Tango Vision — карта и витрина данных.
Главные — мыЗаявки заводятся и ведутся здесь (с локацией, планом, оборудованием и SLA). Ваша система получает копию для отчётности.Служба эксплуатации живёт в Building OS, ваша система — архив.
Разделение по жизненному циклуВы владеете всем до triaged, мы — исполнением с in_progress, вы — закрытием.Две команды с реальной передачей работы: передача и есть граница владения.

Какой бы вариант вы ни выбрали, правило одно: по каждому полю ровно одна система имеет право породить изменение. Всё остальное — зеркало, а зеркало никогда не пишет обратно значение, которое только что получило.

Эндпоинты

Отдаются с origin оболочки Building OS, например https://building-os.k8s.tangovision.dev/api/service-desk/…

ДействиеВызов
СоздатьPOST /api/service-desk/requests
Список / опросGET /api/service-desk/requests?buildingId={uuid}
Прочитать однуGET /api/service-desk/requests/{id}
Изменить поляPATCH /api/service-desk/requests/{id}
Сменить статусPOST /api/service-desk/requests/{id}/transition
НазначитьPOST /api/service-desk/requests/{id}/assign
Добавить записьPOST /api/service-desk/requests/{id}/updates

Создание принимает buildingId (обязательно), subject, description (обязательно), priority (low medium high critical), category, локацию (spaceId, storeyId, elementId — всё внутренние UUID, см. Идентификаторы), поля заявителя, channel, а также произвольные formData / metadata.

Для заявок, которые создаёт ваша интеграция, ставьте "channel": "system". Это настоящее значение канала, оно видно в интерфейсе и это самый дешёвый способ для оператора отличить зеркальную заявку от заведённой человеком.

Как связать две записи

Поля externalId у заявки нет. Ваш идентификатор кладите в metadata:

json
{
  "buildingId": "…", "description": "Течь в трубе под потолком",
  "channel": "system", "spaceId": "…",
  "metadata": { "sourceSystem": "acme-fm", "externalId": "WO-2026-4471" }
}

Отсюда два следствия:

  1. Уникальность metadata ничем не обеспечивается. Отправив тот же payload дважды, вы получите две заявки. Держите соответствие у себя — ваш id → наш id — и проверяйте его перед созданием.
  2. Сохраняйте оба возвращённых значения. id (UUID, нужен всем последующим вызовам) и number (человекочитаемый номер, который будут называть пользователи).

Наша машина состояний ограничивает ваше отображение

Переход проверяется, а не принимается на веру:

new         → triaged | cancelled
triaged     → in_progress | waiting | resolved | cancelled
in_progress → waiting | resolved | cancelled
waiting     → in_progress | resolved | cancelled
resolved    → closed | reopened
reopened    → in_progress | resolved
closed, cancelled → терминальные

То есть зеркало не может прыгнуть из new сразу в in_progress, и ничто не переоткрывает closed: вернувшаяся проблема — это resolved → reopened или новая заявка. Наложите свои статусы на этот граф до реализации, а если у вас статусов меньше — проводите нашу заявку через промежуточный шаг, а не пытайтесь его пропустить.

Рабочий процесс конкретного типа заявки может сузить эти переходы (добавить он не может), поэтому переход, допустимый для одного типа, для другого может быть отклонён. 400 от /transition — это «здесь так нельзя», а не ошибка.

Две вещи, которые удивляют

Создание заявки у нас может её же и изменить

При создании отрабатывают правила маршрутизации. Заявка, отправленная как new, может вернуться уже triaged, с назначенной командой и поднятым приоритетом, плюс системные записи в ленте. Это платформа работает как настроена, — но если ваша синхронизация трактует «запись у них изменилась» как «вернуть изменение назад», то первая же зеркальная заявка запустит цикл. Игнорируйте изменения, порождённые вашей же записью: сравнивайте с тем, что отправили, либо помечайте запись в metadata и пропускайте эхо.

Часы SLA стартуют в момент создания у нас

slaFirstResponseDueAt и slaResolutionDueAt вычисляются по политике SLA в момент создания в нашей системе, а первое назначение засчитывается как первый ответ. Заявка трёхдневной давности, залитая зеркалом, получит свежие часы, а не исходные, — и отчёт по SLA на зеркале разойдётся с исходной системой. Если заказчику важен SLA, держите его на стороне владельца, а часы другой стороны считайте декоративными.

Наряды

Если используется CAFM, наряды связывает с заявками сама платформа: cafm.work-order.created записывает workOrderId в заявку и добавляет системную запись, .updated добавляет записи о ходе работ, .completed переводит заявку из triaged, in_progress или waiting в resolved (заявитель по-прежнему может её переоткрыть). Обработчики идемпотентны, поэтому повторная доставка не задваивает записи и переходы.

Для внешней интеграции это важно в одном: заявка может сменить статус без участия вашей интеграции. Цикл опроса обязан спокойно переживать статус, который он не вызывал.

Рабочая схема для «главная — ваша система»

  1. Новая заявка у вас → POST /requests с channel: "system", metadata.externalId и разрешённым spaceId. Сохраните возвращённый id.
  2. Изменение полей у вас → PATCH /requests/{id}; смена статуса → POST /requests/{id}/transition по допустимым рёбрам.
  3. Раз в несколько минут → опрашивайте открытые статусы, сравнивайте updatedAt с тем, что видели, и подтягивайте поля, которыми вы не владеете (назначение, записи, связь с нарядом).
  4. Никогда не пишите обратно то, что пришло на шаге 3.

Шаг 3 — это опрос, потому что события заявок сегодня не доходят до внешних подписчиков, а у списка нет фильтра updatedSince, см. Данные из двойника. И то и другое — известные пробелы, а не замысел; если они вам мешают, скажите: названная заблокированная интеграция — это то, что ставит доработку в план.

Создано на платформе Tango Vision. Вопросы? developers@tango.vision