@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.
Files changed (92) hide show
  1. package/.env.example +54 -0
  2. package/CONNECTING.md +260 -0
  3. package/README.md +266 -0
  4. package/dist/auth.js +226 -0
  5. package/dist/config.js +120 -0
  6. package/dist/generated/manifest.js +3643 -0
  7. package/dist/guides/field-notes.json +27 -0
  8. package/dist/guides/loader.js +32 -0
  9. package/dist/guides/service-map.js +6 -0
  10. package/dist/http.js +93 -0
  11. package/dist/index/store.js +134 -0
  12. package/dist/index/types.js +1 -0
  13. package/dist/index.js +20 -0
  14. package/dist/paths.js +20 -0
  15. package/dist/pii/fields.js +100 -0
  16. package/dist/pii/mask.js +50 -0
  17. package/dist/pii/strategies.js +88 -0
  18. package/dist/schema/build-index.js +67 -0
  19. package/dist/schema/deref.js +87 -0
  20. package/dist/schema/describe.js +65 -0
  21. package/dist/server.js +62 -0
  22. package/dist/token-prompt.js +41 -0
  23. package/dist/tools/curated.js +151 -0
  24. package/dist/tools/discovery.js +187 -0
  25. package/dist/tools/guides.js +126 -0
  26. package/dist/tools/registry.js +37 -0
  27. package/dist/tools/request.js +170 -0
  28. package/dist/tools/types.js +1 -0
  29. package/docs/guides/assets.md +334 -0
  30. package/docs/guides/attributes.md +125 -0
  31. package/docs/guides/checklisttemplates.md +154 -0
  32. package/docs/guides/companies.md +57 -0
  33. package/docs/guides/dictionaries.md +64 -0
  34. package/docs/guides/lifecycle.md +263 -0
  35. package/docs/guides/materials.md +135 -0
  36. package/docs/guides/notifications.md +184 -0
  37. package/docs/guides/roles.md +125 -0
  38. package/docs/guides/sla.md +130 -0
  39. package/docs/guides/start.md +67 -0
  40. package/docs/guides/taskchecklists.md +149 -0
  41. package/docs/guides/taskcreate.md +260 -0
  42. package/docs/guides/taskedit.md +277 -0
  43. package/docs/guides/tasktypes.md +156 -0
  44. package/docs/guides/users.md +71 -0
  45. package/generated/index.dev.json +18776 -0
  46. package/generated/index.prod.json +18858 -0
  47. package/package.json +48 -0
  48. package/swagger/dev/ADM.json +27777 -0
  49. package/swagger/dev/AUTH.json +1739 -0
  50. package/swagger/dev/AUTHN.json +1250 -0
  51. package/swagger/dev/AUTHZ.json +1404 -0
  52. package/swagger/dev/CM.json +309 -0
  53. package/swagger/dev/COMMON.json +6543 -0
  54. package/swagger/dev/ES.json +28029 -0
  55. package/swagger/dev/EXPORT.json +4575 -0
  56. package/swagger/dev/IMPORT.json +1479 -0
  57. package/swagger/dev/LIC.json +224 -0
  58. package/swagger/dev/MSG.json +7883 -0
  59. package/swagger/dev/NEWS.json +348 -0
  60. package/swagger/dev/PA.json +5981 -0
  61. package/swagger/dev/PMP.json +3196 -0
  62. package/swagger/dev/PROXY.json +416 -0
  63. package/swagger/dev/REPORT.json +3921 -0
  64. package/swagger/dev/SC.json +3771 -0
  65. package/swagger/dev/SLA.json +2837 -0
  66. package/swagger/dev/TSTG.json +4981 -0
  67. package/swagger/dev/UI.json +4720 -0
  68. package/swagger/dev/WH.json +16796 -0
  69. package/swagger/dev/WORK.json +36024 -0
  70. package/swagger/dev/WSP.json +1612 -0
  71. package/swagger/prod/ADM.json +27777 -0
  72. package/swagger/prod/AUTH.json +1308 -0
  73. package/swagger/prod/AUTHN.json +2710 -0
  74. package/swagger/prod/AUTHZ.json +896 -0
  75. package/swagger/prod/CM.json +162 -0
  76. package/swagger/prod/COMMON.json +4910 -0
  77. package/swagger/prod/ES.json +28029 -0
  78. package/swagger/prod/EXPORT.json +3091 -0
  79. package/swagger/prod/LIC.json +123 -0
  80. package/swagger/prod/MSG.json +6239 -0
  81. package/swagger/prod/NEWS.json +295 -0
  82. package/swagger/prod/PA.json +5123 -0
  83. package/swagger/prod/PMP.json +2978 -0
  84. package/swagger/prod/PROXY.json +250 -0
  85. package/swagger/prod/REPORT.json +3729 -0
  86. package/swagger/prod/SC.json +3771 -0
  87. package/swagger/prod/SLA.json +2201 -0
  88. package/swagger/prod/TSTG.json +4220 -0
  89. package/swagger/prod/UI.json +3879 -0
  90. package/swagger/prod/WH.json +16730 -0
  91. package/swagger/prod/WORK.json +35994 -0
  92. 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
+ номера в этом формате.