Интеграция с 1С
Контракт обратной интеграции
Текущий документ описывает принятые соглашения по неймингу, формату и поведению взаимодействия при обратной интеграции с 1С Тетра. Под обратной интеграцией в данном случае подразумевается обмен данными от бэкенда tetramobile до 1С.
workspace.tetramobile -> 1c
Формат контрактов
Контракты представлены с помощью интерфейсов TypeScript. Такой формат выбран как наиболее простой в освоении и показательный относительно набора существующих полей, их типов и их обязательности.
Используемые термины и сокращения
| Термин | Пояснение |
|---|---|
| Контракт | Зафиксированное описание ожидаемого формата данных при взаимодействии нескольких участников обмена данными |
| Рейс | Транспортная операция в 1С Карго |
| TetraMobile | Название для текущей системы мониторинга |
| workspace.tetramobile | Обозначение frontend-сервиса (админки) для TetraMobile |
| domain.tetramobile | Обозначение основного backend-сервиса для бизнес-логики TetraMobile |
| Событие | Данные определённого формата с типизированным названием, обозначающие какое-либо изменение данных в domain.tetramobile |
Вводные
На стороне TetraMobile происходят изменения данных, которые необходимо фиксировать на стороне 1С. Такую фиксацию предполагается делать с помощью нескольких событий, порождаемых на стороне TetraMobile.
Почему не Kafka?
Для упрощения обмена не предполагается использовать существующие Message Queue. Вместо этого, предполагается создание HTTP эндпоинта на стороне 1С для приёма данных, содержащих события.
Ожидаемые события
1) Изменение фактического времени в Событиях у Рейса
2) Добавление/удаление фото для Рейса
HTTP-эндпоинт на стороне 1С
| Key | Value |
|---|---|
| URL | https://1c.tetramobile.tetratrans.ru/api/1/integrations/common |
| Method | POST |
| Authorization | HTTP Header X-Integrations-Token: <generated_jwt> |
Формат тела запроса:
interface Request {
event: Event // Единственный ключ, содержащий значение произошедшего события
}
interface Event {
name: String // Название события
payload: Record<string, any> // Структура данных, зависимая от названия события
}
Пример соответствующего HTTP-запроса:
curl --location 'https://1c.tetramobile.tetratrans.ru/api/1/integrations/common' \
--header 'Content-Type: application/json' \
--header 'X-Integrations-Token: ••••••' \
--data '{
"event": {
"name": "transportOperation.eventsActualDate.changed",
"payload": {
"eventIndex": 2,
"actualDateStart": "2024-09-09T02:00:30Z"
}
}
}'
Безопасность
Для безопасности в запросах передаётся HTTP-заголовок X-Integrations-Token.
Значение этого заголовка является чувствительной информацией, хранимой с обеих сторон обмена в безопасном хранилище.
Поведение эндпоинта
| HTTP-код | Условие |
|---|---|
204 No Content |
Запрос успешно обработан |
401 Unauthorized |
Заголовок X-Integrations-Token отсутствует или содержит некорректное значение |
400 Bad Request |
Запрос содержит некорректные данные |
408 Request Timeout |
Запрос не был обработан за ожидаемое время |
500 Internal Server Error |
Произошла ошибка обработки запроса с корректными данными |
Формат даты и времени
Любое значение, отображающее значение даты и времени передаётся строкой.
Строка имеет формат RFC3999 (пояснение RFC 3339 vs ISO 8601).
Время указано в UTC, то есть в часовом поясе Гринвича.
Пример: "2024-07-22T04:00:30Z"
Фактически, передаваемые даты соответствуют регулярному выражению \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z (пример на regex101)
Событие изменения фактического времени События Рейса
Контракт:
interface Event {
name: 'transportOperation.eventsActualDate.changed' // Название события
payload: EventPayload // Структура данных соответствующего события
}
interface EventPayload {
transportOperationUid: String // Идентификатор Рейса, используется Ref, пример: "a0842a7e-ac78-11ed-92b9-ac1f6be6228e"
eventIndex: Number // Индекс События у Транспортной операции, в котором произошло изменение (нумеруется с нуля)
actualDateStart?: String // Дата и время, Фактическое время начала события (по смыслу связано с `ТранспортнаяОперация.События[].ДатаНачала`)
actualDateFinish?: String // Дата и время, Фактическое время завершения события (по смыслу связано с `ТранспортнаяОперация.События[].ДатаОкончания`)
}
Пример:
curl --location 'https://1c.tetramobile.tetratrans.ru/api/1/integrations/common' \
--header 'Content-Type: application/json' \
--header 'X-Integrations-Token: ••••••' \
--data '{
"event": {
"name": "transportOperation.eventsActualDate.changed",
"payload": {
"transportOperationUid": "e84f78e2-4f0f-11ef-8b00-3050560100dc",
"eventIndex": 0,
"actualDateStart": "2024-09-09T02:40:30Z"
}
}
}'
Фактическое время начала и Фактическое время завершения
Используется понятие "Фактическое время", но в контракте можно встретить два времени — "Фактическое время начала" и "Фактическое время завершения".
Это объясняется тем, что тип соответствующего события может быть "протяжённым" (ВидыСобытийПеревозкиГруза.Протяженность = 1), то есть длительным по времени.
На текущий момент (сентябрь 2024):
- Известно только один такой тип события —
Таможенное оформление; - Только одна Транспортная операция имеет событие с таким типом — СЕ-ТРО-00052618;
- Ни у одной из Транспортных операций нет указания Планового времени завершения
- Ни у одной из Транспортных операций нет указания Фактического времени завершения
Из этого следует, что по всем известным ТрО на текущий момент могли бы быть события, содержащие только actualDateStart
Нумерация в поле eventIndex
Поле eventIndex указывает на индекс события, в котором произошло изменение.
Значение этого поля нумеруется с нуля.
То есть, если фактическое время изменилось в первом событии, то "eventIndex" = 0
Событие удаления фото Рейса
Контракт:
interface Event {
name: 'transportOperation.files.deleted' // Название события
payload: EventPayload // Структура данных соответствующего события
}
interface EventPayload {
transportOperationUid: String // Идентификатор Рейса, используется Ref, пример: "a0842a7e-ac78-11ed-92b9-ac1f6be6228e"
fileUid: String // Идентификатор файла формата UUID v4
}
Пример:
curl --location 'https://1c.tetramobile.tetratrans.ru/api/1/integrations/common' \
--header 'Content-Type: application/json' \
--header 'X-Integrations-Token: ••••••' \
--data '{
"event": {
"name": "transportOperation.files.deleted",
"payload": {
"transportOperationUid": "e84f78e2-4f0f-11ef-8b00-3050560100dc",
"fileUid": "d50d6c9c-5525-4ce9-93f2-e189edcde451"
}
}
}'
Событие добавления фото Рейса
Контракт:
interface Event {
name: 'transportOperation.files.created' // Название события
payload: EventPayload // Структура данных соответствующего события
}
interface EventPayload {
transportOperationUid: String // Идентификатор Рейса, используется Ref, пример: "a0842a7e-ac78-11ed-92b9-ac1f6be6228e"
eventIndex?: Number // Индекс события, для которого создан файл (отсутствует, если файл прикреплён непосредственно к ТрО)
fileUid: String // Идентификатор файла формата UUID v4
url: String // Абсолютная ссылка на файл
}
Пример:
curl --location 'https://1c.tetramobile.tetratrans.ru/api/1/integrations/common' \
--header 'Content-Type: application/json' \
--header 'X-Integrations-Token: ••••••' \
--data '{
"event": {
"name": "transportOperation.files.created",
"payload": {
"transportOperationUid": "e84f78e2-4f0f-11ef-8b00-3050560100dc",
"fileUid": "d50d6c9c-5525-4ce9-93f2-e189edcde451",
"url": "https://storage.tetramobile.tetratrans.ru/api/1/download/f50d6c9c-1135-4ce2-93b2-e189edcde45a"
}
}
}'