@hubex/mcp 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +54 -0
- package/CONNECTING.md +260 -0
- package/README.md +266 -0
- package/dist/auth.js +226 -0
- package/dist/config.js +120 -0
- package/dist/generated/manifest.js +3643 -0
- package/dist/guides/field-notes.json +27 -0
- package/dist/guides/loader.js +32 -0
- package/dist/guides/service-map.js +6 -0
- package/dist/http.js +93 -0
- package/dist/index/store.js +134 -0
- package/dist/index/types.js +1 -0
- package/dist/index.js +20 -0
- package/dist/paths.js +20 -0
- package/dist/pii/fields.js +100 -0
- package/dist/pii/mask.js +50 -0
- package/dist/pii/strategies.js +88 -0
- package/dist/schema/build-index.js +67 -0
- package/dist/schema/deref.js +87 -0
- package/dist/schema/describe.js +65 -0
- package/dist/server.js +62 -0
- package/dist/token-prompt.js +41 -0
- package/dist/tools/curated.js +151 -0
- package/dist/tools/discovery.js +187 -0
- package/dist/tools/guides.js +126 -0
- package/dist/tools/registry.js +37 -0
- package/dist/tools/request.js +170 -0
- package/dist/tools/types.js +1 -0
- package/docs/guides/assets.md +334 -0
- package/docs/guides/attributes.md +125 -0
- package/docs/guides/checklisttemplates.md +154 -0
- package/docs/guides/companies.md +57 -0
- package/docs/guides/dictionaries.md +64 -0
- package/docs/guides/lifecycle.md +263 -0
- package/docs/guides/materials.md +135 -0
- package/docs/guides/notifications.md +184 -0
- package/docs/guides/roles.md +125 -0
- package/docs/guides/sla.md +130 -0
- package/docs/guides/start.md +67 -0
- package/docs/guides/taskchecklists.md +149 -0
- package/docs/guides/taskcreate.md +260 -0
- package/docs/guides/taskedit.md +277 -0
- package/docs/guides/tasktypes.md +156 -0
- package/docs/guides/users.md +71 -0
- package/generated/index.dev.json +18776 -0
- package/generated/index.prod.json +18858 -0
- package/package.json +48 -0
- package/swagger/dev/ADM.json +27777 -0
- package/swagger/dev/AUTH.json +1739 -0
- package/swagger/dev/AUTHN.json +1250 -0
- package/swagger/dev/AUTHZ.json +1404 -0
- package/swagger/dev/CM.json +309 -0
- package/swagger/dev/COMMON.json +6543 -0
- package/swagger/dev/ES.json +28029 -0
- package/swagger/dev/EXPORT.json +4575 -0
- package/swagger/dev/IMPORT.json +1479 -0
- package/swagger/dev/LIC.json +224 -0
- package/swagger/dev/MSG.json +7883 -0
- package/swagger/dev/NEWS.json +348 -0
- package/swagger/dev/PA.json +5981 -0
- package/swagger/dev/PMP.json +3196 -0
- package/swagger/dev/PROXY.json +416 -0
- package/swagger/dev/REPORT.json +3921 -0
- package/swagger/dev/SC.json +3771 -0
- package/swagger/dev/SLA.json +2837 -0
- package/swagger/dev/TSTG.json +4981 -0
- package/swagger/dev/UI.json +4720 -0
- package/swagger/dev/WH.json +16796 -0
- package/swagger/dev/WORK.json +36024 -0
- package/swagger/dev/WSP.json +1612 -0
- package/swagger/prod/ADM.json +27777 -0
- package/swagger/prod/AUTH.json +1308 -0
- package/swagger/prod/AUTHN.json +2710 -0
- package/swagger/prod/AUTHZ.json +896 -0
- package/swagger/prod/CM.json +162 -0
- package/swagger/prod/COMMON.json +4910 -0
- package/swagger/prod/ES.json +28029 -0
- package/swagger/prod/EXPORT.json +3091 -0
- package/swagger/prod/LIC.json +123 -0
- package/swagger/prod/MSG.json +6239 -0
- package/swagger/prod/NEWS.json +295 -0
- package/swagger/prod/PA.json +5123 -0
- package/swagger/prod/PMP.json +2978 -0
- package/swagger/prod/PROXY.json +250 -0
- package/swagger/prod/REPORT.json +3729 -0
- package/swagger/prod/SC.json +3771 -0
- package/swagger/prod/SLA.json +2201 -0
- package/swagger/prod/TSTG.json +4220 -0
- package/swagger/prod/UI.json +3879 -0
- package/swagger/prod/WH.json +16730 -0
- package/swagger/prod/WORK.json +35994 -0
- package/swagger/prod/WSP.json +1468 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: dictionaries
|
|
3
|
+
sources:
|
|
4
|
+
- page: admin/TicketType.md
|
|
5
|
+
content_hash: 4166476a7071aae8
|
|
6
|
+
- page: admin/WorkType.md
|
|
7
|
+
content_hash: d1fdeca14cd5b5e7
|
|
8
|
+
- page: admin/StatusType.md
|
|
9
|
+
content_hash: f9d4863b91cc64bf
|
|
10
|
+
- page: admin/ObjectsType.md
|
|
11
|
+
content_hash: 631a7aec138691e5
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Справочники
|
|
15
|
+
|
|
16
|
+
Почти каждое поле-идентификатор в теле запроса (`...ID`) — это ссылка на
|
|
17
|
+
запись из справочника. Значения справочников настраиваются в консоли
|
|
18
|
+
администратора и различаются между тенантами: не подставляй значения из
|
|
19
|
+
примеров вслепую, всегда сверяйся со списком через API.
|
|
20
|
+
|
|
21
|
+
## Где брать идентификаторы: порядок вызовов
|
|
22
|
+
|
|
23
|
+
- **Тип заявки** — `WORK:GET:/TaskTypes`. Определяет допустимые стадии
|
|
24
|
+
жизненного цикла, срок закрытия по умолчанию и разрешённые виды работ.
|
|
25
|
+
Обязателен при создании заявки.
|
|
26
|
+
- **Способ подачи заявки** — `WORK:GET:/RequestMethods`. Обязателен при
|
|
27
|
+
создании заявки; для интеграций используется значение 4.
|
|
28
|
+
- **Статус заявки** — `WORK:GET:/TaskStatuses`. Визуальное отображение
|
|
29
|
+
стадии для мобильного приложения заказчика, привязывается к стадии в
|
|
30
|
+
жизненном цикле — не назначается заявке напрямую произвольным значением.
|
|
31
|
+
- **Вид работ** — `WORK:GET:/WorkTypes`. Что за работа выполняется по
|
|
32
|
+
заявке/объекту/сотруднику.
|
|
33
|
+
- **Тип объекта** — `ES:GET:/AssetTypes`. Функциональное назначение
|
|
34
|
+
оборудования, определяет обязательность адреса.
|
|
35
|
+
- **Класс объекта** — `ES:GET:/AssetClasses`. Произвольная группировка
|
|
36
|
+
объектов (по стоимости, производителю и т.п.), не связана с типом.
|
|
37
|
+
- **Навыки исполнителей** — `PA:GET:/Skills`. Используются в правилах
|
|
38
|
+
автоназначения: система подбирает исполнителя по совпадению вида работ и
|
|
39
|
+
навыка в его карточке.
|
|
40
|
+
|
|
41
|
+
## Обязательные поля
|
|
42
|
+
|
|
43
|
+
У справочных `GET`-эндпоинтов тела запроса нет — все перечисленные вызовы
|
|
44
|
+
принимают только query-параметры (пагинация, фильтры удалённых записей и
|
|
45
|
+
т.п.), значения из ответа подставляются в `...ID`-поля других сущностей.
|
|
46
|
+
|
|
47
|
+
## Подводные камни
|
|
48
|
+
|
|
49
|
+
- Тип заявки (`WORK:GET:/TaskTypes`) и вид работ (`WORK:GET:/WorkTypes`) —
|
|
50
|
+
разные справочники с разной ролью: тип заявки задаёт жизненный цикл и
|
|
51
|
+
список **допустимых** видов работ, а конкретный вид работ выбирается из
|
|
52
|
+
этого списка отдельно на самой заявке.
|
|
53
|
+
- Тип объекта (`ES:GET:/AssetTypes`) и класс объекта (`ES:GET:/AssetClasses`)
|
|
54
|
+
— тоже разные вещи: тип влияет на бизнес-логику (обязательность адреса,
|
|
55
|
+
автоподстановка видов работ), класс — это ярлык для группировки и
|
|
56
|
+
фильтрации, бизнес-логики не несёт.
|
|
57
|
+
- Статус заявки (`WORK:GET:/TaskStatuses`) нельзя менять напрямую как
|
|
58
|
+
независимое поле: он выводится из перехода по стадии в жизненном цикле,
|
|
59
|
+
настроенном для типа заявки. Смена стадии, не разрешённая жизненным
|
|
60
|
+
циклом, отклоняется сервером.
|
|
61
|
+
- Значения справочников (какие типы, виды работ, статусы заведены)
|
|
62
|
+
специфичны для тенанта — перед подстановкой ID в тело запроса всегда
|
|
63
|
+
сверяйся с актуальным списком через соответствующий `GET`, а не полагайся
|
|
64
|
+
на значения из документации или прошлых сессий.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: lifecycle
|
|
3
|
+
sources:
|
|
4
|
+
- page: adminapp/src/modules/TaskLifecycle/saga.js
|
|
5
|
+
content_hash: 7fc865697a504027
|
|
6
|
+
- page: adminapp/src/modules/TaskStageModal/saga.js
|
|
7
|
+
content_hash: b24ac8fc352a0f0b
|
|
8
|
+
- page: adminapp/src/modules/TaskStages/saga.js
|
|
9
|
+
content_hash: 1d5ecfdf47139340
|
|
10
|
+
- page: adminapp/src/modules/TaskStatuses/saga.js
|
|
11
|
+
content_hash: cb2284b9dcdbbbc3
|
|
12
|
+
- page: adminapp/src/modules/TaskStageLinkModal/components/TaskStageLinkForm/TaskStageLinkForm.js
|
|
13
|
+
content_hash: bc5ed6671756f00d
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Жизненный цикл заявки
|
|
17
|
+
|
|
18
|
+
Жизненный цикл — это набор **стадий** и **переходов** между ними, настроенный
|
|
19
|
+
для конкретного **типа заявки**. Стадия (`TaskStage`) — состояние заявки,
|
|
20
|
+
требующее действий пользователя или системы. Переход (`TaskStageLink`) —
|
|
21
|
+
разрешённое движение из одной стадии в другую: именно он превращается в кнопку
|
|
22
|
+
в мобильном приложении.
|
|
23
|
+
|
|
24
|
+
Три сущности живут в разных сервисах, и их легко перепутать:
|
|
25
|
+
|
|
26
|
+
| Сущность | Где | Область | Комментарий |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| Стадия | `TSTG:GET:/TaskStages` | общая для тенанта | справочник, не привязан к типу заявки |
|
|
29
|
+
| Переход | `TSTG:GET:/TaskStageLinks` | **на тип заявки** | ключ — тройка `taskTypeID + fromTaskStageID + toTaskStageID` |
|
|
30
|
+
| Маршрут | `WORK:GET:/TaskTypes/{taskTypeID}/route` | **на тип заявки** | стартовая стадия, стартовый статус, финальная стадия |
|
|
31
|
+
| Статус | `WORK:GET:/TaskStatuses` | общая для тенанта | витрина стадии для приложения заказчика |
|
|
32
|
+
|
|
33
|
+
Порядок настройки в консоли администратора: сначала тип заявки
|
|
34
|
+
(`hubex_get_guide topic="tasktypes"`) и стадии, затем маршрут, затем переходы.
|
|
35
|
+
|
|
36
|
+
## 1. Стадии заявки
|
|
37
|
+
|
|
38
|
+
Список стадий тенанта: `TSTG:GET:/TaskStages`; одна стадия целиком (с
|
|
39
|
+
требованиями и правилом автоназначения) — `TSTG:GET:/TaskStages/{id}`.
|
|
40
|
+
|
|
41
|
+
Создание — `TSTG:POST:/TaskStages`, тело **массив**, ответ `201` — массив
|
|
42
|
+
идентификаторов:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
[
|
|
46
|
+
{
|
|
47
|
+
"name": "Назначена",
|
|
48
|
+
"description": null,
|
|
49
|
+
"color": "#464855",
|
|
50
|
+
"taskViewTemplateID": 1,
|
|
51
|
+
"actionID": 3,
|
|
52
|
+
"assigneeSelectionRuleID": null,
|
|
53
|
+
"assignToRoleID": null,
|
|
54
|
+
"assignToUserID": null,
|
|
55
|
+
"isShowTechnicianOnMap": false
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- `color` — до 7 символов (`#RRGGBB`), обязателен: цветом выводится название
|
|
61
|
+
стадии в списке заявок.
|
|
62
|
+
- `taskViewTemplateID` — веб-клиент всегда шлёт `1` (шаблон по умолчанию);
|
|
63
|
+
список шаблонов — `UI:GET:/TaskViewTemplate`.
|
|
64
|
+
- `actionID` — действие, которое система выполняет при входе на стадию
|
|
65
|
+
(закрыть заявку, назначить ближайшего исполнителя и т.п.). Справочник —
|
|
66
|
+
`TSTG:GET:/Action`. Необязателен.
|
|
67
|
+
- `assigneeSelectionRuleID` — правило автоподбора исполнителя
|
|
68
|
+
(`TSTG:GET:/AssigneeSelectionRules`). Указывается у той стадии, **на входе
|
|
69
|
+
в которую** исполнитель должен быть подобран; сам автоподбор запускается
|
|
70
|
+
автопереходом (см. раздел 4).
|
|
71
|
+
- `assignToRoleID` / `assignToUserID` — жёсткое назначение на роль или
|
|
72
|
+
пользователя вместо правила.
|
|
73
|
+
|
|
74
|
+
После создания стадии веб-клиент выполняет ещё два запроса — повтори их,
|
|
75
|
+
иначе стадия будет создана «полупустой»:
|
|
76
|
+
|
|
77
|
+
1. `TSTG:POST:/TaskStageRequirements` — требования (условия), без которых
|
|
78
|
+
переход на стадию невозможен: `[{ "taskStageID": 12, "data": [{ "id": 1 }] }]`.
|
|
79
|
+
Справочник условий — `TSTG:GET:/Requirements/requirements` («назначен
|
|
80
|
+
исполнитель», «обработаны чек-листы»). Тот же запрос с пустым `data`
|
|
81
|
+
снимает все требования.
|
|
82
|
+
2. `TSTG:POST:/TaskStageComponents/templates` — наполнение стадии полями формы
|
|
83
|
+
из шаблона: `[{ "taskStageID": 12, "taskViewTemplateID": 1, "taskTypeID": 5 }]`.
|
|
84
|
+
Без него на форме заявки на этой стадии не будет полей. AdminApp шлёт только
|
|
85
|
+
`TaskStageID` и `TaskViewTemplateID`, но в схеме `taskTypeID` обязателен —
|
|
86
|
+
передавай все три.
|
|
87
|
+
|
|
88
|
+
Изменение — `TSTG:PUT:/TaskStages` (массив объектов **с `id`**, полное тело),
|
|
89
|
+
удаление — `TSTG:DELETE:/TaskStages` (тело — список id) или
|
|
90
|
+
`TSTG:DELETE:/TaskStages/{id}`.
|
|
91
|
+
|
|
92
|
+
## 2. Статусы заявки
|
|
93
|
+
|
|
94
|
+
Статус — визуальное отображение стадии для мобильного приложения заказчика:
|
|
95
|
+
несколько внутренних стадий согласования можно показать заказчику одним
|
|
96
|
+
статусом «В работе». Статус не назначается заявке напрямую — он применяется
|
|
97
|
+
переходом (`applyTaskStatusID`).
|
|
98
|
+
|
|
99
|
+
- `WORK:GET:/TaskStatuses` — список.
|
|
100
|
+
- `WORK:POST:/TaskStatuses` — тело массив `[{ "name": "В работе", "color": "#2C7BE5", "sortOrder": 3 }]`.
|
|
101
|
+
- `WORK:PUT:/TaskStatuses` — то же тело с `id`; `sortOrder` меняется тем же PUT.
|
|
102
|
+
- `WORK:DELETE:/TaskStatuses/{id}` — удаление.
|
|
103
|
+
|
|
104
|
+
## 3. Маршрут типа заявки
|
|
105
|
+
|
|
106
|
+
Маршрут задаёт точку входа и точку выхода жизненного цикла. Без него заявки
|
|
107
|
+
этого типа не создаются.
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
GET WORK:GET:/TaskTypes/{taskTypeID}/route → { startTaskStage, startTaskStatus, finishTaskStage }
|
|
111
|
+
POST WORK:POST:/TaskTypeRoutes → создать, если маршрута ещё нет
|
|
112
|
+
PUT WORK:PUT:/TaskTypeRoutes → изменить существующий
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Тело POST/PUT — массив:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
[
|
|
119
|
+
{
|
|
120
|
+
"taskTypeID": 5,
|
|
121
|
+
"startTaskStageID": 10,
|
|
122
|
+
"startTaskStatusID": 1,
|
|
123
|
+
"finishTaskStageID": 18
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Выбор между POST и PUT делай по результату GET: если `startTaskStage`,
|
|
129
|
+
`startTaskStatus` и `finishTaskStage` пустые — POST, иначе PUT (так же
|
|
130
|
+
поступает веб-клиент). Удаление — `WORK:DELETE:/TaskTypeRoutes`, тело —
|
|
131
|
+
массив идентификаторов **типов заявок**, а не маршрутов.
|
|
132
|
+
|
|
133
|
+
## 4. Переходы между стадиями
|
|
134
|
+
|
|
135
|
+
Переход уникален тройкой `taskTypeID + fromTaskStageID + toTaskStageID`;
|
|
136
|
+
собственного `id` у него нет — все операции адресуют его этой тройкой.
|
|
137
|
+
|
|
138
|
+
Чтение: `TSTG:GET:/TaskStageLinks` с query `taskTypeID` и `taskStageFromID`
|
|
139
|
+
(исходящие переходы стадии) либо `taskStageToID` (входящие).
|
|
140
|
+
|
|
141
|
+
Создание — `TSTG:POST:/TaskStageLinks`, изменение — `TSTG:PUT:/TaskStageLinks`,
|
|
142
|
+
тело массив:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
[
|
|
146
|
+
{
|
|
147
|
+
"taskTypeID": 5,
|
|
148
|
+
"fromTaskStageID": 10,
|
|
149
|
+
"toTaskStageID": 12,
|
|
150
|
+
"name": "Назначить",
|
|
151
|
+
"description": null,
|
|
152
|
+
"branchID": 1,
|
|
153
|
+
"isPositiveResult": true,
|
|
154
|
+
"applyTaskStatusID": 2,
|
|
155
|
+
"timeoutSeconds": 120,
|
|
156
|
+
"timeoutToDeadlineSeconds": null,
|
|
157
|
+
"roles": [3, 7],
|
|
158
|
+
"sortOrder": 0
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Что означают поля:
|
|
164
|
+
|
|
165
|
+
- `name` — **название кнопки** перехода в мобильном приложении. Обязателен по
|
|
166
|
+
форме веб-клиента (в схеме помечен необязательным).
|
|
167
|
+
- `branchID` — ветка жизненного цикла, обязателен. Справочник —
|
|
168
|
+
`TSTG:GET:/Branches`: основная ветка (движение по основным стадиям), ветка
|
|
169
|
+
согласования (в мобильном приложении такие заявки попадают на отдельную
|
|
170
|
+
вкладку «Согласование»), ветка действий заказчика.
|
|
171
|
+
- `applyTaskStatusID` — статус, который заявка получит при переходе.
|
|
172
|
+
Передавай только если статус действительно нужно менять.
|
|
173
|
+
- `timeoutSeconds` — автоматический переход через указанное число **секунд**.
|
|
174
|
+
Это механизм автоподбора исполнителя: правило висит на целевой стадии, а
|
|
175
|
+
запускает его таймаут перехода.
|
|
176
|
+
- `timeoutToDeadlineSeconds` — автопереход за указанное время **до дедлайна**
|
|
177
|
+
заявки (например, вернуть заявку руководителю, если исполнитель так и не
|
|
178
|
+
назначен).
|
|
179
|
+
- `roles` — если задан, переход доступен только пользователям с этими ролями
|
|
180
|
+
(`ADM:GET:/Roles`). Пустой список = переход доступен всем.
|
|
181
|
+
- `sortOrder` — порядок кнопок; массовая перестановка — отдельным запросом
|
|
182
|
+
`TSTG:POST:/TaskStageLinks/reorder` с телом
|
|
183
|
+
`[{ taskTypeID, fromTaskStageID, toTaskStageID, sortOrder }]`.
|
|
184
|
+
|
|
185
|
+
Удаление — `TSTG:DELETE:/TaskStageLinks`, тело
|
|
186
|
+
`[{ "taskTypeID": 5, "fromTaskStageID": 10, "toTaskStageID": 12 }]`.
|
|
187
|
+
|
|
188
|
+
## 5. Переопределения перехода для ролей
|
|
189
|
+
|
|
190
|
+
Одному и тому же переходу можно задать разное название кнопки для разных
|
|
191
|
+
ролей: для всех — «Назначить», для сервисного специалиста — «Принять заявку».
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
GET TSTG:GET:/TaskStageLinks/overridings query: taskTypeID, taskStageFromID, taskStageToID
|
|
195
|
+
POST TSTG:POST:/TaskStageLinks/overridings создать для ролей, которых ещё нет
|
|
196
|
+
PUT TSTG:PUT:/TaskStageLinks/overridings изменить существующие
|
|
197
|
+
DELETE TSTG:DELETE:/TaskStageLinks/overridings снять переопределение с ролей
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Тело POST/PUT — массив, **по одному объекту на роль** (веб-клиент кладёт в
|
|
201
|
+
`roles` ровно один id):
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
[
|
|
205
|
+
{
|
|
206
|
+
"taskTypeID": 5,
|
|
207
|
+
"fromTaskStageID": 10,
|
|
208
|
+
"toTaskStageID": 12,
|
|
209
|
+
"roles": [7],
|
|
210
|
+
"name": "Принять заявку",
|
|
211
|
+
"description": null,
|
|
212
|
+
"isPositiveResult": true
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Тело DELETE — `[{ taskTypeID, fromTaskStageID, toTaskStageID, roles: [7, 9] }]`.
|
|
218
|
+
Порядок как в веб-клиенте: сначала сохранить сам переход, затем POST для новых
|
|
219
|
+
ролей, PUT для изменившихся, DELETE для убранных.
|
|
220
|
+
|
|
221
|
+
## 6. Копирование жизненного цикла
|
|
222
|
+
|
|
223
|
+
`TSTG:POST:/TaskStageLinks/copy` с телом
|
|
224
|
+
`[{ "sourceTaskTypeID": 5, "targetTaskTypeID": 9 }]` переносит все переходы из
|
|
225
|
+
одного типа заявки в другой. Именно так работает создание типа заявки «по
|
|
226
|
+
шаблону». Стадии при этом не дублируются — они общие для тенанта.
|
|
227
|
+
Отдельная копия стадии — `TSTG:POST:/TaskStages/copy` с
|
|
228
|
+
`[{ "sourceID": 12, "name": "Копия стадии" }]`.
|
|
229
|
+
|
|
230
|
+
## Последовательность MCP-вызовов
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
hubex_request_read(endpointId: `WORK:GET:/TaskTypes`) → taskTypeID
|
|
234
|
+
hubex_request_read(endpointId: `TSTG:GET:/TaskStages`) → стадии тенанта
|
|
235
|
+
hubex_request_write(endpointId: `TSTG:POST:/TaskStages`, body: [{...}]) → taskStageID
|
|
236
|
+
hubex_request_write(endpointId: `TSTG:POST:/TaskStageRequirements`, body: [{ taskStageID, data }])
|
|
237
|
+
hubex_request_write(endpointId: `TSTG:POST:/TaskStageComponents/templates`, body: [{ taskStageID, taskTypeID, taskViewTemplateID: 1 }])
|
|
238
|
+
hubex_request_read(endpointId: `WORK:GET:/TaskTypes/{taskTypeID}/route`, pathParams: { taskTypeID })
|
|
239
|
+
hubex_request_write(endpointId: `WORK:POST:/TaskTypeRoutes`, body: [{ taskTypeID, startTaskStageID, startTaskStatusID, finishTaskStageID }])
|
|
240
|
+
hubex_request_read(endpointId: `TSTG:GET:/Branches`) → branchID
|
|
241
|
+
hubex_request_write(endpointId: `TSTG:POST:/TaskStageLinks`, body: [{ taskTypeID, fromTaskStageID, toTaskStageID, name, branchID }])
|
|
242
|
+
hubex_request_read(endpointId: `TSTG:GET:/TaskStageLinks`, query: { taskTypeID, taskStageFromID })
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Подводные камни
|
|
246
|
+
|
|
247
|
+
- Стадии общие для тенанта, переходы — нет. Удаление стадии рвёт переходы во
|
|
248
|
+
**всех** типах заявок: перед удалением проверь
|
|
249
|
+
`TSTG:GET:/TaskStageLinks` с `taskStageFromID` и с `taskStageToID` (так
|
|
250
|
+
делает веб-клиент перед показом предупреждения).
|
|
251
|
+
- У перехода нет `id`. Повторный POST с той же тройкой — это не «второй
|
|
252
|
+
переход», а конфликт; для изменения используй PUT.
|
|
253
|
+
- Не путай `applyTaskStatusID` (статус, который ставит переход) с
|
|
254
|
+
`startTaskStatusID` (статус стартовой стадии в маршруте).
|
|
255
|
+
- Таймауты задаются в **секундах**, а срок закрытия типа заявки
|
|
256
|
+
(`closeMinutes`) — в минутах.
|
|
257
|
+
- Создание стадии без `TSTG:POST:/TaskStageComponents/templates` даёт стадию
|
|
258
|
+
без полей на форме заявки — визуально «пустую» карточку.
|
|
259
|
+
- Тело всех write-эндпоинтов раздела — массив, кроме `TSTG:POST:/TaskStages/{id}/assign`.
|
|
260
|
+
Обёртка `{ "data": [...] }` вместо массива приводит к ошибке «Параметр [data]
|
|
261
|
+
не может быть пустым».
|
|
262
|
+
- DELETE-эндпоинты жизненного цикла требуют тело; `hubex_request_delete`
|
|
263
|
+
принимает `body` — передавай его так же, как в write-запросах.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: materials
|
|
3
|
+
sources:
|
|
4
|
+
- page: swagger/WH.json
|
|
5
|
+
content_hash: cff1fe0ebc8a8740
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Материалы
|
|
9
|
+
|
|
10
|
+
Материал (Material) — номенклатурная позиция склада: то, что списывается в
|
|
11
|
+
заявке как израсходованное. Живёт в сервисе `WH`. Валюта у материала своя
|
|
12
|
+
(поля `costCurrencyID` и `purchaseCostCurrencyID`) и **не подставляется
|
|
13
|
+
сервером по умолчанию** — это главный подводный камень при создании через API.
|
|
14
|
+
|
|
15
|
+
## Создание материала: порядок вызовов
|
|
16
|
+
|
|
17
|
+
1. **Валюта тенанта** — `ADM:GET:/TenantSettings`, поле `defaultCurrency.id`.
|
|
18
|
+
Именно так делает веб-приложение: валюта у материала не выбирается в форме,
|
|
19
|
+
а берётся из настроек тенанта и подставляется молча. Полный список валют —
|
|
20
|
+
`COMMON:GET:/Currencies` (словарь по id: `1` — рубль, `2` — доллар,
|
|
21
|
+
`3` — евро и т.д.), но для материала бери значение из настроек тенанта, а не
|
|
22
|
+
угадывай.
|
|
23
|
+
2. **Единица измерения** — `COMMON:GET:/MeasurementUnits`. Берём `id` нужной
|
|
24
|
+
единицы, кладём в `measurementUnitID`. Значение по умолчанию в вебе —
|
|
25
|
+
`796` («Штука»).
|
|
26
|
+
3. **Создание** — `WH:POST:/Materials`. Тело — **массив** объектов, за один
|
|
27
|
+
вызов можно создать несколько материалов. `costCurrencyID` и
|
|
28
|
+
`purchaseCostCurrencyID` заполняем **одним и тем же** id валюты тенанта.
|
|
29
|
+
4. **Проверка** — `WH:GET:/Materials/{id}` или `WH:GET:/Materials/v2`.
|
|
30
|
+
В ответе должны быть не только `costCurrencyID`, но и раскрытые объекты
|
|
31
|
+
`costCurrency` / `purchaseCostCurrency` — если их нет, валюта не проставлена.
|
|
32
|
+
|
|
33
|
+
Изменение — `WH:PUT:/Materials`, тело того же вида, но с `id` у каждого
|
|
34
|
+
элемента. `PUT` заменяет запись целиком: перечисляй все поля, а не только
|
|
35
|
+
изменяемые. Веб при редактировании снова подставляет валюту из настроек
|
|
36
|
+
тенанта — то есть правка материала «чинит» пустую валюту сама собой.
|
|
37
|
+
|
|
38
|
+
## Обязательные поля
|
|
39
|
+
|
|
40
|
+
| Поле | Обязательность | Комментарий |
|
|
41
|
+
| ------------------------ | -------------- | -------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `name` | да | до 128 символов, единственное `required` в swagger; в вебе — обязательное поле |
|
|
43
|
+
| `measurementUnitID` | **фактически** | в вебе форма не сохраняется без единицы измерения |
|
|
44
|
+
| `cost` | нет | цена продажи; если заполнена — попадает в Акт выполненных работ. До 13 цифр, 2 знака после запятой |
|
|
45
|
+
| `costCurrencyID` | **фактически** | валюта цены продажи = `defaultCurrency.id` тенанта |
|
|
46
|
+
| `purchaseCost` | нет | закупочная цена; веб её не отправляет вообще |
|
|
47
|
+
| `purchaseCostCurrencyID` | **фактически** | валюта закупочной цены; веб пишет туда **ту же** валюту тенанта |
|
|
48
|
+
| `isMarkable` | нет | материал подлежит маркировке (Data Matrix, «Честный ЗНАК») |
|
|
49
|
+
| `vendorCode` | нет | артикул, до 32 символов |
|
|
50
|
+
| `erpID` | нет | идентификатор во внешней системе (например, в 1С), до 64 символов |
|
|
51
|
+
| `description` | нет | до 2048 символов |
|
|
52
|
+
|
|
53
|
+
Слово «фактически» означает: swagger помечает поле как `nullable` и запрос без
|
|
54
|
+
него пройдёт с кодом 200, но данные получатся неполными — сервер не валидирует
|
|
55
|
+
то, что валидирует форма в вебе.
|
|
56
|
+
|
|
57
|
+
## Про валюту: почему без неё материал работает некорректно
|
|
58
|
+
|
|
59
|
+
`costCurrencyID` и `purchaseCostCurrencyID` объявлены в swagger как
|
|
60
|
+
`nullable`, поэтому сервер принимает материал вообще без валюты и не
|
|
61
|
+
возвращает никакой ошибки. Дальше такой материал ведёт себя как сломанный:
|
|
62
|
+
|
|
63
|
+
- В ответах `WH:GET:/Materials/v2` и `WH:GET:/Materials/{id}` у него нет ни `costCurrencyID`, ни вложенных
|
|
64
|
+
объектов `costCurrency` / `purchaseCostCurrency` — только голая `cost`.
|
|
65
|
+
Клиенты (веб и мобильные приложения), которые читают символ валюты из
|
|
66
|
+
`costCurrency.asciiCode`, показывают цену без валюты или падают на
|
|
67
|
+
обращении к полю отсутствующего объекта.
|
|
68
|
+
- Стоимость израсходованных материалов в заявке считается по позициям с
|
|
69
|
+
разными (или отсутствующими) валютами и не сводится в корректную сумму.
|
|
70
|
+
- Дефолт «рубль» не подставляется ни на уровне тенанта, ни на уровне склада:
|
|
71
|
+
пустая валюта остаётся пустой, пока материал не отредактируют явно.
|
|
72
|
+
|
|
73
|
+
**Правило: всегда передавай `costCurrencyID` и `purchaseCostCurrencyID` при
|
|
74
|
+
создании материала**, даже если `cost` равен нулю и закупочной цены нет. Оба
|
|
75
|
+
поля заполняются одним значением — `defaultCurrency.id` из
|
|
76
|
+
`ADM:GET:/TenantSettings`; ровно это делают саги веб-приложения
|
|
77
|
+
`materialCreationForm` и `materialEditForm`. Для рублёвого тенанта это `1`, но
|
|
78
|
+
не хардкодь: у другого тенанта валюта по умолчанию другая, и материал с чужой
|
|
79
|
+
валютой сломает подсчёт стоимости так же, как материал без валюты.
|
|
80
|
+
|
|
81
|
+
Починка уже созданных материалов — тем же `WH:PUT:/Materials`: читаем текущие
|
|
82
|
+
поля через `WH:GET:/Materials/v2`, добавляем `costCurrencyID` /
|
|
83
|
+
`purchaseCostCurrencyID` и отправляем полный объект обратно.
|
|
84
|
+
|
|
85
|
+
## Пример тела
|
|
86
|
+
|
|
87
|
+
`WH:POST:/Materials` (создание) — массив, без `id`:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
[
|
|
91
|
+
{
|
|
92
|
+
"name": "Лампа светодиодная",
|
|
93
|
+
"vendorCode": "LED-9W",
|
|
94
|
+
"description": "",
|
|
95
|
+
"measurementUnitID": 796,
|
|
96
|
+
"cost": 50,
|
|
97
|
+
"costCurrencyID": 1,
|
|
98
|
+
"purchaseCost": 35,
|
|
99
|
+
"purchaseCostCurrencyID": 1,
|
|
100
|
+
"isMarkable": false
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`WH:PUT:/Materials` (изменение) — то же самое плюс `id` у каждого элемента.
|
|
106
|
+
|
|
107
|
+
## Подводные камни
|
|
108
|
+
|
|
109
|
+
- **Тело — массив, а не объект.** И `POST`, и `PUT` принимают массив
|
|
110
|
+
материалов. Если передать объект или обёртку `{ "data": [...] }`, HubEx
|
|
111
|
+
ответит `409 ParameterNull: Параметр [data] не может быть пустым` — сервер
|
|
112
|
+
не смог связать тело с параметром-массивом.
|
|
113
|
+
- **Успешный `PUT` возвращает пустое тело.** Ответ без содержимого — это
|
|
114
|
+
норма для изменения материалов; результат проверяй отдельным `GET`.
|
|
115
|
+
- `WH:GET:/Materials/v2` и остальные коллекции отдают **словарь по id**, а не
|
|
116
|
+
массив.
|
|
117
|
+
- Валюта цены продажи и валюта закупки — два независимых поля. Проставив
|
|
118
|
+
только `costCurrencyID`, получишь материал с наполовину заполненной
|
|
119
|
+
валютой: `purchaseCostCurrency` в ответе так и не появится.
|
|
120
|
+
- `measurementUnitID` берётся из общего справочника `COMMON`, а не из `WH`:
|
|
121
|
+
единицы измерения не заводятся на складе.
|
|
122
|
+
- `isMarkable: true` включает работу с кодами маркировки: при списании такого
|
|
123
|
+
материала в выполненных работах у инженера в мобильном приложении появляется
|
|
124
|
+
сканирование кода Data Matrix. Не ставь признак «на всякий случай» тестовым
|
|
125
|
+
материалам — сценарий списания станет другим.
|
|
126
|
+
- Материал — это только номенклатура. Остатки считаются отдельно: тот же
|
|
127
|
+
`WH:GET:/Materials` с фильтром `warehouseID` отдаёт материалы конкретного
|
|
128
|
+
склада, а попадают они туда приходными документами (`WH:GET:/Receipts`).
|
|
129
|
+
Созданный материал сам по себе ни на одном складе не лежит — чтобы списать
|
|
130
|
+
его в заявке, нужен приход.
|
|
131
|
+
- Удаление материала — мягкое: есть `WH:PUT:/Materials/restore` и
|
|
132
|
+
`WH:PUT:/Materials/{id}/restore` для восстановления, а список умеет отдавать
|
|
133
|
+
удалённые через `isDeleted` в `WH:GET:/Materials/v2`.
|
|
134
|
+
- `cost` в вебе ограничена 13 цифрами и двумя знаками после запятой; сервер
|
|
135
|
+
такой проверки не делает и примет что угодно в пределах `double`.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: notifications
|
|
3
|
+
sources:
|
|
4
|
+
- page: adminapp/src/modules/Triggers/saga.js
|
|
5
|
+
content_hash: c7b5460e1cfa0ec7
|
|
6
|
+
- page: adminapp/src/services/requests.js
|
|
7
|
+
content_hash: 54c3d95d85bb0ce0
|
|
8
|
+
- page: adminapp/src/locales/ru/app.json
|
|
9
|
+
content_hash: 22b22046c9336c1a
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Оповещения: триггеры, шаблоны, получатели
|
|
13
|
+
|
|
14
|
+
Оповещение в HubEx собирается из трёх независимых сущностей сервиса MSG:
|
|
15
|
+
|
|
16
|
+
- **Шаблон уведомления** (`MessageTemplate`) — текст и канал доставки.
|
|
17
|
+
- **Правило выбора получателя** (`RecipientSelectionRule`) — кому слать.
|
|
18
|
+
- **Триггер** (`Trigger`) — когда слать: событие и (или) стадии заявки; он
|
|
19
|
+
связывает шаблон с правилами.
|
|
20
|
+
|
|
21
|
+
Веб-клиент создаёт их именно в этом порядке — шаблон, затем триггер, затем
|
|
22
|
+
привязки:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
POST MSG:POST:/MessageTemplates → messageTemplateID
|
|
26
|
+
PUT MSG:PUT:/MessageTemplates/{id}/validate → проверка текста на сервере
|
|
27
|
+
POST MSG:POST:/RecipientSelectionRules → recipientSelectionRuleID
|
|
28
|
+
POST MSG:POST:/Triggers → triggerID
|
|
29
|
+
POST MSG:POST:/TriggerRecipientSelectionRules → правила триггера
|
|
30
|
+
POST MSG:POST:/CriticalityForTriggers → критичности триггера
|
|
31
|
+
POST TSTG:POST:/TaskStageMessageTriggers → стадии триггера
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 1. Шаблон уведомления
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
[
|
|
38
|
+
{
|
|
39
|
+
"description": "Заявка назначена на исполнителя",
|
|
40
|
+
"providerID": 1,
|
|
41
|
+
"contentTypeID": 2,
|
|
42
|
+
"subject": "Заявка №@номер заявки",
|
|
43
|
+
"content": "Вам назначена заявка @номер заявки",
|
|
44
|
+
"applicationID": null,
|
|
45
|
+
"navigateToID": null
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- `description` — название шаблона (то, что видно в списке).
|
|
51
|
+
- `providerID` — канал доставки: `MSG:GET:/Providers` (email, push и т.д.).
|
|
52
|
+
- `contentTypeID` — тип контента: `MSG:GET:/ContentTypes` (текст, HTML).
|
|
53
|
+
- `content` — текст с подстановками системных полей. Список доступных полей —
|
|
54
|
+
`MSG:GET:/Notifications/fields`; в интерфейсе они вводятся как `@имя поля`,
|
|
55
|
+
а перед отправкой заменяются на системные плейсхолдеры. Через MCP бери
|
|
56
|
+
корректные имена подстановок из `MSG:GET:/Notifications/fields`, не изобретай
|
|
57
|
+
их.
|
|
58
|
+
- `navigateToID` — куда открывать по нажатию: `MSG:GET:/NavigateTo`.
|
|
59
|
+
|
|
60
|
+
После создания или изменения веб-клиент всегда вызывает
|
|
61
|
+
`MSG:PUT:/MessageTemplates/{id}/validate` — серверную проверку текста. Ошибки
|
|
62
|
+
подстановок видны только там.
|
|
63
|
+
|
|
64
|
+
## 2. Правило выбора получателя
|
|
65
|
+
|
|
66
|
+
Правило — это набор булевых признаков «кому», а не список пользователей.
|
|
67
|
+
Основные флаги: `isCaller` (автор заявки), `isTaskRequestor`, `isTaskAssignee`
|
|
68
|
+
(текущий исполнитель), `isPreviousTaskAssignee`, `isTaskAssigneeManager`,
|
|
69
|
+
`isTaskContact`, `isTaskAssetResponsiblePerson`, `isTenantPowerUser`,
|
|
70
|
+
`isTaskWatchList` (наблюдатели), `isForRelevantUsers`,
|
|
71
|
+
`isForRelevantUsersByWorkType`. Все они обязательны в теле — передавай явные
|
|
72
|
+
`true`/`false`.
|
|
73
|
+
|
|
74
|
+
Точечные получатели: `customUserID`, `customRoleID`, `customEmailList`,
|
|
75
|
+
`customPhoneList` (телефоны в формате E.164, через запятую),
|
|
76
|
+
`useUnverifiedContacts`.
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
GET MSG:GET:/RecipientSelectionRules
|
|
80
|
+
POST MSG:POST:/RecipientSelectionRules
|
|
81
|
+
PUT MSG:PUT:/RecipientSelectionRules
|
|
82
|
+
DELETE MSG:DELETE:/RecipientSelectionRules
|
|
83
|
+
GET MSG:GET:/RecipientSelectionRules/recipients кто попадёт под правило
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 3. Триггер
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
[
|
|
90
|
+
{
|
|
91
|
+
"messageTemplateID": 41,
|
|
92
|
+
"eventID": 12,
|
|
93
|
+
"timeoutSeconds": null,
|
|
94
|
+
"description": "Уведомление исполнителю о назначении",
|
|
95
|
+
"isNotifyDuringWorkHours": true,
|
|
96
|
+
"isNotifyDuringDutyHours": true,
|
|
97
|
+
"isNotifyDuringOtherHours": false,
|
|
98
|
+
"isEnabled": true
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- `eventID` — событие системы, справочник `COMMON:GET:/Events`. События с
|
|
104
|
+
пометкой «отложенное» срабатывают с задержкой в пару минут — так гасится
|
|
105
|
+
поток уведомлений при ошибочных назначениях.
|
|
106
|
+
- Три флага `isNotifyDuring*` обязательны: рабочее время, дежурное время,
|
|
107
|
+
остальное время.
|
|
108
|
+
- `timeoutSeconds` — задержка отправки.
|
|
109
|
+
- Включение и выключение без удаления — `MSG:PUT:/Triggers/{triggerID}/activate`
|
|
110
|
+
и `MSG:PUT:/Triggers/{triggerID}/deactivate`.
|
|
111
|
+
|
|
112
|
+
Привязки триггера (каждая — отдельный запрос, тело
|
|
113
|
+
`[{ "triggerID": 7, "data": [id, id] }]`):
|
|
114
|
+
|
|
115
|
+
- критичности заявки — `MSG:POST:/CriticalityForTriggers`
|
|
116
|
+
(текущие — `MSG:GET:/Triggers/{id}/criticalities`);
|
|
117
|
+
- правила выбора получателей — `MSG:POST:/TriggerRecipientSelectionRules`;
|
|
118
|
+
- стадии заявки — `TSTG:POST:/TaskStageMessageTriggers`
|
|
119
|
+
(стадии триггера — `TSTG:GET:/TaskStages` с query `triggerID`, триггеры
|
|
120
|
+
стадии — `TSTG:GET:/TaskStages/{id}/messageTriggers`).
|
|
121
|
+
|
|
122
|
+
Каждый такой запрос **заменяет** список целиком: чтобы убрать одну стадию,
|
|
123
|
+
отправь оставшиеся.
|
|
124
|
+
|
|
125
|
+
## 4. Webhooks
|
|
126
|
+
|
|
127
|
+
Webhook отправляет запрос во внешнюю систему по событиям.
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
GET MSG:GET:/Webhooks
|
|
131
|
+
GET MSG:GET:/Webhooks/{id}
|
|
132
|
+
POST MSG:POST:/Webhooks создание и обновление (id в теле)
|
|
133
|
+
PUT MSG:PUT:/Webhooks/activate/{id} PUT MSG:PUT:/Webhooks/deactivate/{id}
|
|
134
|
+
DELETE MSG:DELETE:/Webhooks/{id}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Тело содержит `name`, `uri`, `description`, `isActive` и список `events` —
|
|
138
|
+
идентификаторы из `COMMON:GET:/Events`.
|
|
139
|
+
|
|
140
|
+
## 5. Приём заявок с почты
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
GET MSG:GET:/MailBoxes список ящиков
|
|
144
|
+
GET MSG:GET:/MailBoxes/{id} один ящик
|
|
145
|
+
GET MSG:GET:/MailBoxes/regexactions действия по regex над письмом
|
|
146
|
+
POST MSG:POST:/MailBoxes подключение ящика
|
|
147
|
+
PUT MSG:PUT:/MailBoxes/activate/{id} PUT MSG:PUT:/MailBoxes/deactivate/{id}
|
|
148
|
+
DELETE MSG:DELETE:/MailBoxes/{id}
|
|
149
|
+
GET MSG:GET:/MailBoxes/{id}/errors ошибки разбора писем
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Отправители, письма которых превращаются в заявки, — `MSG:GET:/MailBoxes/{mailBoxID}/senders`,
|
|
153
|
+
удаление отправителя — `MSG:DELETE:/MailBoxes/{mailBoxID}/senders/{id}`.
|
|
154
|
+
|
|
155
|
+
## Последовательность MCP-вызовов
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
hubex_request_read(endpointId: `MSG:GET:/Providers`)
|
|
159
|
+
hubex_request_read(endpointId: `MSG:GET:/ContentTypes`)
|
|
160
|
+
hubex_request_read(endpointId: `MSG:GET:/Notifications/fields`)
|
|
161
|
+
hubex_request_write(endpointId: `MSG:POST:/MessageTemplates`, body: [{ description, providerID, contentTypeID, content }])
|
|
162
|
+
→ response[0] = messageTemplateID
|
|
163
|
+
hubex_request_write(endpointId: `MSG:PUT:/MessageTemplates/{id}/validate`, pathParams: { id: messageTemplateID })
|
|
164
|
+
hubex_request_write(endpointId: `MSG:POST:/RecipientSelectionRules`, body: [{ description, isTaskAssignee: true, ... }])
|
|
165
|
+
hubex_request_write(endpointId: `MSG:POST:/Triggers`, body: [{ messageTemplateID, eventID, isNotifyDuringWorkHours: true, isNotifyDuringDutyHours: true, isNotifyDuringOtherHours: false }])
|
|
166
|
+
→ response[0] = triggerID
|
|
167
|
+
hubex_request_write(endpointId: `MSG:POST:/TriggerRecipientSelectionRules`, body: [{ triggerID, data: [ruleID] }])
|
|
168
|
+
hubex_request_write(endpointId: `TSTG:POST:/TaskStageMessageTriggers`, body: [{ triggerID, data: [taskStageID] }])
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Подводные камни
|
|
172
|
+
|
|
173
|
+
- Триггер без правил выбора получателя создаётся успешно и молчит — привязка
|
|
174
|
+
правил обязательна для работы оповещения.
|
|
175
|
+
- Если у триггера заданы и событие, и стадии заявки, пользователь получит
|
|
176
|
+
**два** уведомления. Выбирай одно основание срабатывания.
|
|
177
|
+
- Выбор конкретного пользователя и отдельно его роли в правилах приводит к
|
|
178
|
+
дублю письма тому же человеку.
|
|
179
|
+
- Текст шаблона проверяется только `MSG:PUT:/MessageTemplates/{id}/validate` —
|
|
180
|
+
без этого вызова ошибочная подстановка вылезет на реальном уведомлении.
|
|
181
|
+
- Привязки триггера (критичности, стадии, правила) перезаписывают список
|
|
182
|
+
целиком, а не добавляют элемент.
|
|
183
|
+
- Телефоны в `customPhoneList` веб-клиент нормализует в E.164 — передавай
|
|
184
|
+
номера в этом формате.
|