@besales/mcp 0.1.0 → 0.11.1
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/README.md +272 -17
- package/dist/auth/connection-store.d.ts +58 -0
- package/dist/auth/connection-store.js +208 -0
- package/dist/auth/connection-store.js.map +1 -0
- package/dist/auth/oauth-client.d.ts +27 -2
- package/dist/auth/oauth-client.js +62 -11
- package/dist/auth/oauth-client.js.map +1 -1
- package/dist/auth/session-workspace.d.ts +2 -0
- package/dist/auth/session-workspace.js +20 -0
- package/dist/auth/session-workspace.js.map +1 -0
- package/dist/auth/token-storage.d.ts +19 -5
- package/dist/auth/token-storage.js +11 -6
- package/dist/auth/token-storage.js.map +1 -1
- package/dist/cli.d.ts +2 -7
- package/dist/cli.js +111 -33
- package/dist/cli.js.map +1 -1
- package/dist/http/api-client.d.ts +4 -13
- package/dist/http/api-client.js +18 -18
- package/dist/http/api-client.js.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/instructions/server-instructions.d.ts +15 -0
- package/dist/instructions/server-instructions.js +243 -0
- package/dist/instructions/server-instructions.js.map +1 -0
- package/dist/package-metadata.js +7 -1
- package/dist/package-metadata.js.map +1 -1
- package/dist/resources/concepts/feedback-sheets.md +77 -0
- package/dist/resources/concepts/sandbox.md +13 -0
- package/dist/resources/concepts/workbook-classification.md +241 -0
- package/dist/resources/docs/agent-behavior.md +393 -0
- package/dist/resources/docs/crm-integration.md +535 -0
- package/dist/resources/docs/files-and-uploads.md +295 -0
- package/dist/resources/docs/knowledge-base.md +521 -0
- package/dist/resources/docs/pipeline-builder.md +221 -0
- package/dist/resources/docs/pipeline-settings-deep.md +221 -0
- package/dist/resources/docs/platforms.md +513 -0
- package/dist/resources/docs/prompt-anatomy.md +298 -0
- package/dist/resources/docs/prompt-principles.md +289 -0
- package/dist/resources/registry.js +34 -12
- package/dist/resources/registry.js.map +1 -1
- package/dist/resources/workflows/compare-models.md +46 -0
- package/dist/resources/workflows/connect-crm-from-scratch.md +89 -0
- package/dist/resources/workflows/connect-datasource-from-scratch.md +92 -0
- package/dist/resources/workflows/extract-from-document.md +36 -0
- package/dist/resources/workflows/iterate-with-sandbox.md +31 -0
- package/dist/resources/workflows/platform-setup-from-scratch.md +113 -0
- package/dist/resources/workflows/production-readiness-check.md +41 -0
- package/dist/schemas/mcp-tools.json +2636 -182
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/definitions/agent-design.d.ts +215 -0
- package/dist/tools/definitions/agent-design.js +643 -0
- package/dist/tools/definitions/agent-design.js.map +1 -0
- package/dist/tools/definitions/crm-platform.d.ts +211 -0
- package/dist/tools/definitions/crm-platform.js +1070 -0
- package/dist/tools/definitions/crm-platform.js.map +1 -0
- package/dist/tools/definitions/datasource.d.ts +40 -0
- package/dist/tools/definitions/datasource.js +196 -0
- package/dist/tools/definitions/datasource.js.map +1 -0
- package/dist/tools/definitions/knowledge.d.ts +215 -0
- package/dist/tools/definitions/knowledge.js +782 -0
- package/dist/tools/definitions/knowledge.js.map +1 -0
- package/dist/tools/definitions/model-comparison.d.ts +25 -0
- package/dist/tools/definitions/model-comparison.js +101 -0
- package/dist/tools/definitions/model-comparison.js.map +1 -0
- package/dist/tools/definitions/platform-setup.d.ts +412 -0
- package/dist/tools/definitions/platform-setup.js +738 -0
- package/dist/tools/definitions/platform-setup.js.map +1 -0
- package/dist/tools/definitions/session.d.ts +11 -0
- package/dist/tools/definitions/session.js +86 -0
- package/dist/tools/definitions/session.js.map +1 -0
- package/dist/tools/definitions/shared.d.ts +742 -0
- package/dist/tools/definitions/shared.js +773 -0
- package/dist/tools/definitions/shared.js.map +1 -0
- package/dist/tools/definitions.d.ts +873 -88
- package/dist/tools/definitions.js +14 -856
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/registry.d.ts +3 -1
- package/dist/tools/registry.js +90 -11
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/result.d.ts +1 -1
- package/dist/tools/result.js +12 -4
- package/dist/tools/result.js.map +1 -1
- package/dist/utils/logger.js +2 -1
- package/dist/utils/logger.js.map +1 -1
- package/docs/host-setup.md +34 -15
- package/package.json +2 -2
- package/scripts/install-claude-desktop.js +89 -11
- package/scripts/mock-api-server.js +1 -1
- package/scripts/mock-credentials.js +49 -6
- package/dist/types/api-contract.gen.d.ts +0 -6975
- package/dist/types/api-contract.gen.js +0 -6
- package/dist/types/api-contract.gen.js.map +0 -1
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
# 12. CRM-интеграция
|
|
2
|
+
|
|
3
|
+
> **Источник правды:** `ai-aniomaly/docs/ai-prompt-builder/12-crm-integration.md` — копия для MCP doc-bridge (v1.5 Increment 6b). Синкать при изменении оригинала.
|
|
4
|
+
>
|
|
5
|
+
> ⚠️ **MCP-контекст:** это доменная справка, написанная для людей/админ-UI. В MCP-сессии ты выполняешь те же операции **через besales_* tools**, а не через UI (drag-n-drop, клики в админке). Где текст ниже говорит «клиент/админ делает X через UI» — это «что должно получиться»; механику бери из tool (напр. загрузка файла → `besales_file_upload_request`; документ в knowledge → `besales_knowledge_document_upload`; сайт → `besales_knowledge_website_add`) + из workflow-ресурсов. Внутренние ссылки `[NN-name.md]` — относительные пути исходной доки, через MCP НЕ резолвятся: ищи `besales://docs/<slug>` или concept-resource.
|
|
6
|
+
|
|
7
|
+
## 1. Назначение
|
|
8
|
+
|
|
9
|
+
Animaly интегрируется с 4 типами CRM. Цель интеграции:
|
|
10
|
+
- Сохранять контакты и сделки в CRM при первом контакте.
|
|
11
|
+
- Перемещать сделки по воронке через триггеры.
|
|
12
|
+
- Передавать оператору-человеку с правильным контекстом.
|
|
13
|
+
- Маппить поля платформы (имя, телефон, UTM) в кастомные поля CRM.
|
|
14
|
+
- Получать статус лида для маршрутизации между агентами.
|
|
15
|
+
|
|
16
|
+
## 2. Поддерживаемые CRM
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
enum CrmType {
|
|
20
|
+
AMOCRM, // AmoCRM (OAuth + REST API + Chat API)
|
|
21
|
+
BITRIX24, // Bitrix24 (REST API + Open Lines + лиды-сделки)
|
|
22
|
+
TELEGRAM, // Telegram Forum CRM (топик-based группа как «CRM»)
|
|
23
|
+
GETCOURSE, // GetCourse как CRM (заказы/доски/этапы/теги/группы/менеджеры)
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 3. Сущность `Crm`
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"id": "uuid",
|
|
32
|
+
"type": "AMOCRM",
|
|
33
|
+
"status": "active",
|
|
34
|
+
"config": { /* зависит от type — см. §7 для AMOCRM, §8 для BITRIX24, §10 для GETCOURSE */ },
|
|
35
|
+
"workspaceId": "<workspaceId>",
|
|
36
|
+
"chatApiScope": "WORKSPACE"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Поле | Описание |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `type` | enum `CrmType`. |
|
|
43
|
+
| `status` | Текстовая строка: `active`, `inactive`, `error`. |
|
|
44
|
+
| `config` | JSON, специфичный для CRM-провайдера (см. §7-§10). |
|
|
45
|
+
| `chatApiScope` | `WORKSPACE` (один канал AmoCRM на весь workspace) или `PLATFORM` (отдельный канал для каждой платформы через `PlatformAmoChatConfig`). Применяется только для AmoCRM. |
|
|
46
|
+
|
|
47
|
+
Уникален в `(workspaceId, type)` — нельзя подключить две AmoCRM в один workspace.
|
|
48
|
+
|
|
49
|
+
**Важно:** Поле `Crm.config.managedTagPrefix` используется только в **Bitrix24** (для тегов сделок) и **GetCourse** (`Crm.config.managedTagPrefix` для тегов; см. §10). В AmoCRM такого префикса нет.
|
|
50
|
+
|
|
51
|
+
## 4. Воронки и статусы
|
|
52
|
+
|
|
53
|
+
### `CrmPipeline`
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"crmId": "<Crm.id>",
|
|
58
|
+
"id": 1234567,
|
|
59
|
+
"name": "Основная воронка",
|
|
60
|
+
"sort": 0,
|
|
61
|
+
"isMain": true,
|
|
62
|
+
"isUnsortedOn": true,
|
|
63
|
+
"isArchive": false,
|
|
64
|
+
"accountId": 87654321,
|
|
65
|
+
"isActive": true,
|
|
66
|
+
"isCustom": false,
|
|
67
|
+
"pipelineType": "deal",
|
|
68
|
+
"pipelineMetadata": null
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`pipelineType`: `deal` (сделки, default) или `lead` (только Bitrix24 — отдельные лиды).
|
|
73
|
+
|
|
74
|
+
### `CrmPipelineStatus`
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"pipelineCrmId": "<Crm.id>",
|
|
79
|
+
"pipelineId": 1234567,
|
|
80
|
+
"id": 142,
|
|
81
|
+
"name": "Переговоры",
|
|
82
|
+
"sort": 10,
|
|
83
|
+
"color": "#FFFF00",
|
|
84
|
+
"type": 0,
|
|
85
|
+
"isEditable": true,
|
|
86
|
+
|
|
87
|
+
"isOptional": false,
|
|
88
|
+
"isSystemManaged": true,
|
|
89
|
+
"statusTypes": ["INCOMING", "OUTGOING"],
|
|
90
|
+
|
|
91
|
+
"isBotActive": true,
|
|
92
|
+
"isOperatorAvailable": true,
|
|
93
|
+
"onMessageReceivedMoveToStatus": null,
|
|
94
|
+
|
|
95
|
+
"statusMetadata": null
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Поле | Описание |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `statusTypes[]` | enum `StatusType[]`: `INCOMING`, `OUTGOING`, `MESSAGE_SENT`, `AI_TOOK_OVER`, `OPERATOR_TOOK_OVER`, `USER_BLOCKED`. Используется в Pipeline Builder. |
|
|
102
|
+
| `isBotActive` | Если `false` — бот не отвечает в этом статусе. |
|
|
103
|
+
| `isOperatorAvailable` | Доступен ли оператор для перевода. |
|
|
104
|
+
| `onMessageReceivedMoveToStatus` | ID статуса, в который перевести при получении сообщения от пользователя (автоперевод). |
|
|
105
|
+
| `isSystemManaged` | Управляется системой (для Bitrix24 live-проверки). |
|
|
106
|
+
|
|
107
|
+
## 5. Маппинг полей (`CrmFieldMapping`)
|
|
108
|
+
|
|
109
|
+
Маппит поля платформы (имя, телефон, email, UTM) в кастомные поля CRM:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"id": "uuid",
|
|
114
|
+
"workspaceId": "<workspaceId>",
|
|
115
|
+
"crmType": "AMOCRM",
|
|
116
|
+
"fieldMappings": {
|
|
117
|
+
"enabled": true,
|
|
118
|
+
"fields": {
|
|
119
|
+
"phone": {
|
|
120
|
+
"crmFieldId": "PHONE",
|
|
121
|
+
"saveAs": "phone",
|
|
122
|
+
"enabled": true,
|
|
123
|
+
"specialPurpose": "phone"
|
|
124
|
+
},
|
|
125
|
+
"first_name": {
|
|
126
|
+
"crmFieldId": "FIRST_NAME",
|
|
127
|
+
"saveAs": "first_name",
|
|
128
|
+
"enabled": true,
|
|
129
|
+
"specialPurpose": null
|
|
130
|
+
},
|
|
131
|
+
"telegram_id": {
|
|
132
|
+
"crmFieldId": "12345",
|
|
133
|
+
"saveAs": "telegram_id",
|
|
134
|
+
"enabled": true,
|
|
135
|
+
"specialPurpose": "telegramId"
|
|
136
|
+
},
|
|
137
|
+
"company_name": {
|
|
138
|
+
"crmFieldId": "67890",
|
|
139
|
+
"saveAs": "company_name",
|
|
140
|
+
"enabled": true,
|
|
141
|
+
"specialPurpose": null
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"isActive": true
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `specialPurpose` — зачем оно
|
|
150
|
+
|
|
151
|
+
`specialPurpose` подсказывает системе, что в этом CRM-поле «лежит» и позволяет:
|
|
152
|
+
- **Task-trigger**: при создании задачи в CRM искать пользователя по этому полю.
|
|
153
|
+
- **Pipeline-фильтр**: проверять существующие сделки с этим контактом.
|
|
154
|
+
- **Кросс-платформенный мердж**: объединять `ChatUser` в `UnifiedCustomer` по совпадению `phone`/`telegramId`/`avitoId`.
|
|
155
|
+
|
|
156
|
+
> ⚠️ **Полный список допустимых значений `specialPurpose` — ровно 6** (`src/core/application/crms/services/crm-field-mapping.service.ts:38-45`, тип `FieldSpecialPurpose`):
|
|
157
|
+
>
|
|
158
|
+
> - `phone` — телефон в формате для отправки сообщений (E.164 без `+` для Wazzup).
|
|
159
|
+
> - `telegramId` — Telegram user ID (числовой).
|
|
160
|
+
> - `username` — Telegram username (без `@`).
|
|
161
|
+
> - `avitoId` — Avito user ID.
|
|
162
|
+
> - `instagramId` — Instagram user ID / username (Direct API).
|
|
163
|
+
> - `vkonId` — VK user ID.
|
|
164
|
+
>
|
|
165
|
+
> Никаких `email`, `firstName`, `lastName`, `lastMessage` — этих значений в коде НЕТ. Если AI-сервис подставит любое из них, runtime отклонит запись (через DTO `IsIn([...])`-валидацию).
|
|
166
|
+
>
|
|
167
|
+
> Для `email`/`firstName`/`lastName` и подобных бизнес-полей используется обычный маппинг **без** `specialPurpose` (поле остаётся `null`). Имя клиента сохраняется в `ChatUser.firstName` / `ChatUser.lastName` через builtin-инструмент `save_user_info` (см. [03-tools-catalog.md](03-tools-catalog.md), §2.3) или через `ActionCrmUpdateField`. Email сохраняется в `ChatUser.email`. Эти поля не нужно мапить через `specialPurpose`.
|
|
168
|
+
|
|
169
|
+
## 6. Операторы (`CrmOperator`)
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"id": "uuid",
|
|
174
|
+
"crmType": "AMOCRM",
|
|
175
|
+
"crmAccountId": "12345",
|
|
176
|
+
"crmId": "<Crm.id>",
|
|
177
|
+
"name": "Сергей Иванов",
|
|
178
|
+
"email": "sergey@example.com",
|
|
179
|
+
"lang": "ru",
|
|
180
|
+
"isAdmin": false,
|
|
181
|
+
"isSDR": true,
|
|
182
|
+
"isOperator": true
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Используются для:
|
|
187
|
+
- Стратегии назначения ответственного при создании сделки (`Platform.responsibleAssigneeStrategy`, см. §12).
|
|
188
|
+
- Выбора оператора при `CALL_OPERATOR` action (`OperatorStrategy`):
|
|
189
|
+
- `OperatorStrategy.RANDOM` — случайный оператор с `isOperator=true`.
|
|
190
|
+
- `OperatorStrategy.FROM_DEAL` — ответственный текущей сделки (если есть).
|
|
191
|
+
- Отправки уведомлений и постановки задач.
|
|
192
|
+
|
|
193
|
+
> Не путать: `OperatorStrategy.FROM_DEAL` существует и используется в `CALL_OPERATOR` action. `Platform.responsibleAssigneeStrategy` — отдельный enum, и значения `FROM_DEAL` там **нет** (см. §12).
|
|
194
|
+
|
|
195
|
+
## 7. AmoCRM — особенности
|
|
196
|
+
|
|
197
|
+
### Подключение — OAuth 2.0 (через UI)
|
|
198
|
+
|
|
199
|
+
Поток: workspace получает `accessToken` (TTL ~24 часа) + `refreshToken` (TTL ~3 месяца). Refresh автоматический. Endpoint авторизации — `https://www.amocrm.ru/oauth/`. После авторизации сохраняются `subdomain`, `accountId`, `amojoAccountId`.
|
|
200
|
+
|
|
201
|
+
### Подключение — MCP-провизионинг (долгосрочный токен)
|
|
202
|
+
|
|
203
|
+
> Через MCP (`besales_crm_create_init`, `crm_type=AMOCRM`) AmoCRM подключается **НЕ** OAuth-redirect'ом, а **долгосрочным токеном** (long-lived token). Пользователь создаёт в AmoCRM приватную интеграцию и вводит на setup-странице 4 значения: `sub_domain` + `client_id` (несекретные), `client_secret` + `long_access_token` (секреты). `Crm.config` хранит `subDomain` + `longAccessToken` (Bearer для REST-вызовов), redirect_uri не нужен. На `READY` токен онлайн-проверяется живым запросом к аккаунту. Операционные шаги (где взять значения, какие scopes — в т.ч. доступ к **событиям/звонкам** для выгрузки аудио-диалогов) — `besales://workflows/connect-crm-from-scratch`.
|
|
204
|
+
|
|
205
|
+
### Chat API (Amojo) — отдельный канал для сообщений в карточке сделки
|
|
206
|
+
|
|
207
|
+
Чтобы сообщения от бота отображались в карточке сделки AmoCRM, нужен **отдельный Amojo-канал** (`chats.amojo.amocrm.ru`). AmoCRM требует обращения в техподдержку для получения `channelId` + `channelSecretKey`. Без этого канала бот может управлять сделкой, но переписка не появится в карточке.
|
|
208
|
+
|
|
209
|
+
Endpoint в Animaly для привязки канала: `POST /api/amo-crm/channel-connect`.
|
|
210
|
+
|
|
211
|
+
Чтобы сообщения от бота отображались в карточке сделки AmoCRM:
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"Crm": { "chatApiScope": "WORKSPACE" }
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Если `chatApiScope=WORKSPACE` — один канал на весь workspace. Если `PLATFORM` — каждая платформа имеет свой канал через `PlatformAmoChatConfig`:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"platformId": "<platformId>",
|
|
224
|
+
"channelId": "<AmoCRM channel ID>",
|
|
225
|
+
"channelSecretKey": "<AmoCRM channel secret>",
|
|
226
|
+
"scopeId": "<scope id>",
|
|
227
|
+
"amoAccountId": "<amojo account>",
|
|
228
|
+
"amojoId": "<amojo user>"
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Поля контакта vs сделки
|
|
233
|
+
|
|
234
|
+
В AmoCRM два уровня:
|
|
235
|
+
- **Контакт** (`CONTACT`): телефон, email, имя.
|
|
236
|
+
- **Сделка** (`LEAD`): сумма, статус, кастомные поля воронки.
|
|
237
|
+
|
|
238
|
+
В `ActionCrmUpdateField.fieldMappings[].entityType` указывается `LEAD` или `CONTACT`.
|
|
239
|
+
|
|
240
|
+
### Жизненный цикл интеграции AmoCRM
|
|
241
|
+
|
|
242
|
+
Интеграция переключается между `isActive=true/false` не только вручную: система сама охраняет состояние.
|
|
243
|
+
|
|
244
|
+
**1. Валидация перед активацией.** Перед тем как `Crm.isActive` поднимется в `true`, бэкенд проверяет валидность OAuth-токенов и доступность AmoCRM API (`/api/v4/account` или эквивалент). Если токены просрочены, scope недостаточен или API возвращает ошибку — активация отклоняется с понятным сообщением. AI-сервис должен **не предлагать** клиенту включать CRM, не пройдя OAuth-flow заново.
|
|
245
|
+
|
|
246
|
+
**2. Авто-отключение при ошибке авторизации.** Если в процессе работы AmoCRM начинает отвечать `401`/`403` на refresh token (отозвали доступ, удалили интеграцию в админке AmoCRM, истёк `refreshToken`), Animaly автоматически выставляет `Crm.isActive=false`. После этого:
|
|
247
|
+
- Триггеры с `CRM_*`-actions перестают писать в CRM (Action завершается с error → дальнейшие actions в триггере прерываются, если не стоит `continueOnError=true`).
|
|
248
|
+
- Запись новых ChatUser в CRM приостанавливается.
|
|
249
|
+
- В UI клиенту нужно повторно пройти OAuth — после успешного re-connect система пройдёт валидацию и снова активирует интеграцию.
|
|
250
|
+
|
|
251
|
+
**3. Guard платформы — `Platform.sendToCrm`.** Регистрация нового `ChatUser` в CRM выполняется только если **обе** проверки пройдены: `Platform.sendToCrm=true` И связанная `Crm.isActive=true`. Иначе ChatUser создаётся локально, но без CRM-записи (это допустимо — можно дослать позже action'ом `CRM_UPDATE_FIELD` или вручную).
|
|
252
|
+
|
|
253
|
+
**4. Флаг `sendToCrm` на платформе.** По умолчанию `true`. Используется для платформ, где новые лиды **не нужны** в CRM:
|
|
254
|
+
- Технические каналы (Web Widget для поддержки уже зарегистрированных клиентов).
|
|
255
|
+
- Тестовые и стейджинговые платформы.
|
|
256
|
+
- Каналы, где лиды квалифицируются на отдельной платформе и переносятся вручную.
|
|
257
|
+
|
|
258
|
+
> ⚠️ Если AI-сервис проектирует платформу, где CRM нужна только для **части** диалогов — лучше держать `sendToCrm=true` и фильтровать неподходящие случаи через триггер с `CALL_OPERATOR` или `SILENT_AI`, чем выключать регистрацию глобально.
|
|
259
|
+
|
|
260
|
+
**5. Stale lead cleanup при ошибках AmoCRM.** Если AmoCRM-операция (`CRM_MOVE_STAGE`, `CRM_UPDATE_FIELD` и т.п.) падает с ошибкой «entity not found» (типично — лид был вручную удалён в админке AmoCRM), Animaly **проверяет** через `/api/v4/leads/{id}` (или `/contacts/{id}`), реально ли entity отсутствует. Если так:
|
|
261
|
+
- В `ChatUser` обнуляются поля `leadId`, `leadStatus`, `leadStatusId`.
|
|
262
|
+
- Если **и** контакт тоже missing — обнуляются `amoContactId`, `amoUserId`, `conversationId`.
|
|
263
|
+
- В `UnifiedCustomer` — те же поля + `amoTagNames = []`.
|
|
264
|
+
|
|
265
|
+
Cleanup — best-effort: если он сам упал (например, AmoCRM API недоступен) — ошибка логируется и проглатывается, основной триггер продолжает работу. Лог формата `[AmoCRM stale] {opName}: lead {id} отсутствует в AmoCRM. chatUser={id}. Очищены локальные ссылки: chatUsers={count}, unifiedCustomers={count}, clearContact={bool}` появляется в WARN-уровне. AI-сервис не должен учитывать эту механику при генерации триггеров — она работает прозрачно.
|
|
266
|
+
|
|
267
|
+
## 8. Bitrix24 — особенности
|
|
268
|
+
|
|
269
|
+
### Подключение — OAuth 2.0 через установку Bitrix24 App
|
|
270
|
+
|
|
271
|
+
> ⚠️ В прошлой версии документации было сказано «без OAuth, ручная установка приложения через webhook URL». **Это неверно.** Bitrix24 в Animaly использует полноценный OAuth 2.0, аналогично AmoCRM. Ручной inbound webhook не поддерживается.
|
|
272
|
+
|
|
273
|
+
**Поток подключения:**
|
|
274
|
+
|
|
275
|
+
1. Пользователь устанавливает Bitrix24 App из marketplace портала клиента.
|
|
276
|
+
2. Bitrix24 выполняет `POST /api/bitrix24/:workspaceId/install` с кредами в теле:
|
|
277
|
+
- `member_id` — идентификатор портала.
|
|
278
|
+
- `domain` — `<example>.bitrix24.ru`.
|
|
279
|
+
- `access_token`, `refresh_token`, `application_token`.
|
|
280
|
+
- `expires_in` — TTL access-токена (обычно 3600 сек).
|
|
281
|
+
- `client_endpoint`, `server_endpoint` — REST endpoints портала.
|
|
282
|
+
3. Animaly создаёт `Crm{ type: BITRIX24 }` с заполненным `config`.
|
|
283
|
+
4. Все REST-вызовы идут через `client_endpoint` с `access_token`.
|
|
284
|
+
5. `Bitrix24TokenService` следит за TTL: за **5 минут до истечения** автоматически обновляет токены (с backoff на `invalid_client`).
|
|
285
|
+
|
|
286
|
+
**Структура `Crm.config` для Bitrix24:**
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"type": "BITRIX24",
|
|
291
|
+
"config": {
|
|
292
|
+
"domain": "example.bitrix24.ru",
|
|
293
|
+
"memberId": "<portal member id>",
|
|
294
|
+
"clientId": "<oauth client id app>",
|
|
295
|
+
"clientSecret": "<oauth client secret>",
|
|
296
|
+
"accessToken": "<encrypted>",
|
|
297
|
+
"refreshToken": "<encrypted>",
|
|
298
|
+
"applicationToken": "<used for event signature validation>",
|
|
299
|
+
"expiresAt": "2026-04-30T13:00:00Z",
|
|
300
|
+
"clientEndpoint": "https://example.bitrix24.ru/rest/",
|
|
301
|
+
"serverEndpoint": "https://oauth.bitrix.info/rest/",
|
|
302
|
+
"managedColumnPrefix": "[BOT] "
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Endpoints Bitrix24 в Animaly
|
|
308
|
+
|
|
309
|
+
| Endpoint | Назначение |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `POST /api/bitrix24/:workspaceId/install` | Установка приложения, сохранение токенов |
|
|
312
|
+
| `POST /api/bitrix24/:workspaceId/handler` | Обработчик событий портала (события сделок/контактов) |
|
|
313
|
+
| `POST /api/bitrix24/:workspaceId/deal-update` | Обновление полей сделки из Animaly |
|
|
314
|
+
| `POST /api/bitrix24-bot/:workspaceId/:platformId/webhook` | Open Lines bot — входящие сообщения от клиентов |
|
|
315
|
+
|
|
316
|
+
Подпись событий проверяется через `application_token` (Bitrix24 включает его в payload).
|
|
317
|
+
|
|
318
|
+
### Лиды и сделки
|
|
319
|
+
|
|
320
|
+
В Bitrix24 — отдельные сущности «Лид» и «Сделка». Можно конвертировать лид в сделку через `ActionConvertLeadToDeal`:
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"actionType": "CONVERT_LEAD_TO_DEAL",
|
|
325
|
+
"params": {
|
|
326
|
+
"crmId": "<Crm.id>",
|
|
327
|
+
"crmType": "BITRIX24",
|
|
328
|
+
"targetPipelineId": 1,
|
|
329
|
+
"targetStatusId": 5,
|
|
330
|
+
"note": "Конвертирован из лида в сделку"
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Open Lines
|
|
336
|
+
|
|
337
|
+
Bitrix24 Open Lines — отдельный канал для общения с клиентом из Telegram/WhatsApp/etc внутри Bitrix24. Подключается через `Platform.type=Bitrix24Bot`.
|
|
338
|
+
|
|
339
|
+
### Live-проверка колонки
|
|
340
|
+
|
|
341
|
+
Для follow-up и Inactive User: перед отправкой проверяется текущая колонка сделки. Если она не `system managed` или `OPERATOR_TOOK_OVER` — отправка отменяется. См. [`08-follow-up.md`](08-follow-up.md), раздел 4.1.
|
|
342
|
+
|
|
343
|
+
### Каскадная деактивация `Bitrix24Bot`-платформ при удалении Bitrix24 CRM
|
|
344
|
+
|
|
345
|
+
При удалении Bitrix24-CRM (`DELETE /api/crms/{crmId}`) Animaly **автоматически деактивирует** все привязанные `Platform.type=Bitrix24Bot` записи этого workspace:
|
|
346
|
+
1. Backend находит все `Platform { workspaceId, type=Bitrix24Bot, isActive=true }`.
|
|
347
|
+
2. Для каждой — best-effort вызов `imbot.unregister` в Bitrix24 (через `BotId`-маппинг). При неудаче — warning лог, deactivation продолжается.
|
|
348
|
+
3. Single `UPDATE` ставит `isActive=false, isAiEnabled=false` всем найденным платформам в одной транзакции.
|
|
349
|
+
|
|
350
|
+
Платформы **не удаляются физически** — сохраняется история чатов/сообщений. Webhook-handler фильтрует по `isActive=true`, так что даже если Bitrix24 продолжит слать события — они будут проигнорированы.
|
|
351
|
+
|
|
352
|
+
Возвращается `DeactivatedBitrix24BotPlatformInfo[]` для UI-баннера, чтобы клиенту было понятно, какие платформы выключились. AI-сервис должен предупредить клиента: «Если удалить Bitrix24 CRM — все Bitrix24Bot платформы воркспейса будут автоматически отключены».
|
|
353
|
+
|
|
354
|
+
## 9. Telegram Forum CRM
|
|
355
|
+
|
|
356
|
+
Топик-based «CRM»: в Telegram-группе с включёнными темами каждый клиент = одна тема. Удобно для маленьких команд.
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"ChatUser": {
|
|
361
|
+
"forumTopicId": 42,
|
|
362
|
+
"forumTopicStatus": "OPERATOR_SET"
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
`forumTopicStatus`: `NEW_MESSAGE` / `AI_HANDLE` / `TAKE_INTO_WORK` / `OPERATOR_SET` / `DEAL_DONE`.
|
|
368
|
+
|
|
369
|
+
Не имеет воронок и кастомных полей в обычном смысле — статус хранится в названии топика (через эмодзи или префикс).
|
|
370
|
+
|
|
371
|
+
## 10. GetCourse — особенности
|
|
372
|
+
|
|
373
|
+
### Подключение
|
|
374
|
+
|
|
375
|
+
API Key школы + Webhook secret. Действует только для одной школы.
|
|
376
|
+
|
|
377
|
+
### Особенности
|
|
378
|
+
|
|
379
|
+
- Нет «воронки» в обычном понимании — есть **доски** (boards) с **этапами** (stages).
|
|
380
|
+
- Сделки хранят `status` (строка: `paid`, `cancelled`, `in_work`, …) — не `statusId`.
|
|
381
|
+
- Менеджеры назначаются через `manager_user_id` (числовой).
|
|
382
|
+
- **Managed-prefix теги**: триггер `GETCOURSE_SET_DEAL_TAGS` управляет только тегами с префиксом школы (`Crm.config.managedTagPrefix`). Бизнес-теги защищены.
|
|
383
|
+
- **Departments**: список отделов хранится в `Platform.getcourseDepartments` (вручную, GetCourse не имеет API списка отделов).
|
|
384
|
+
|
|
385
|
+
### GetCourse-специфичные actions триггеров
|
|
386
|
+
|
|
387
|
+
См. [`04-triggers-and-actions.md`](04-triggers-and-actions.md), раздел 5.15–5.23. Все 9 действий:
|
|
388
|
+
- `GETCOURSE_SET_DEAL_TAGS`
|
|
389
|
+
- `GETCOURSE_CLOSE_DIALOG`
|
|
390
|
+
- `GETCOURSE_UPDATE_USER_CUSTOM_FIELDS`
|
|
391
|
+
- `GETCOURSE_UPDATE_DEAL_STATUS`
|
|
392
|
+
- `GETCOURSE_SET_DEAL_MANAGER`
|
|
393
|
+
- `GETCOURSE_SET_PERSONAL_MANAGER`
|
|
394
|
+
- `GETCOURSE_ADD_USER_GROUPS`
|
|
395
|
+
- `GETCOURSE_REMOVE_USER_GROUPS`
|
|
396
|
+
- `GETCOURSE_CHANGE_DEPARTMENT`
|
|
397
|
+
|
|
398
|
+
## 11. Маркеры сделок (`Platform.dealMarkerType`)
|
|
399
|
+
|
|
400
|
+
Чтобы отделить «свои» сделки от пришедших из других источников:
|
|
401
|
+
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"Platform": {
|
|
405
|
+
"dealMarkerType": "TAG",
|
|
406
|
+
"dealMarkerValue": "telegram-bot",
|
|
407
|
+
"dealMarkerFieldId": null
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
| `dealMarkerType` | Поведение |
|
|
413
|
+
|---|---|
|
|
414
|
+
| `NONE` | Без маркировки. |
|
|
415
|
+
| `TAG` | Добавлять тег `dealMarkerValue` к сделке при создании. |
|
|
416
|
+
| `CUSTOM_FIELD` | Заполнить кастомное поле `dealMarkerFieldId` значением `dealMarkerValue`. |
|
|
417
|
+
|
|
418
|
+
## 12. Стратегия ответственного (`Platform.responsibleAssigneeStrategy`)
|
|
419
|
+
|
|
420
|
+
```json
|
|
421
|
+
{
|
|
422
|
+
"responsibleAssigneeStrategy": "RANDOM_OPERATOR",
|
|
423
|
+
"fixedResponsibleId": null
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
| Значение | Поведение |
|
|
428
|
+
|---|---|
|
|
429
|
+
| `KEEP` | Сохранять текущего ответственного (если есть). |
|
|
430
|
+
| `ALWAYS_ONE` | Всегда `fixedResponsibleId` (`CrmOperator.id`). |
|
|
431
|
+
| `RANDOM_OPERATOR` | Случайный из `CrmOperator` с `isOperator=true`. |
|
|
432
|
+
|
|
433
|
+
## 13. Inline-пример: AmoCRM с маппингом UTM
|
|
434
|
+
|
|
435
|
+
```json
|
|
436
|
+
{
|
|
437
|
+
"crm": {
|
|
438
|
+
"type": "AMOCRM",
|
|
439
|
+
"config": { "subdomain": "example", "...": "..." },
|
|
440
|
+
"chatApiScope": "PLATFORM"
|
|
441
|
+
},
|
|
442
|
+
"fieldMapping": {
|
|
443
|
+
"crmType": "AMOCRM",
|
|
444
|
+
"fieldMappings": {
|
|
445
|
+
"enabled": true,
|
|
446
|
+
"fields": {
|
|
447
|
+
"phone": { "crmFieldId": "PHONE", "saveAs": "phone", "enabled": true, "specialPurpose": "phone" },
|
|
448
|
+
"first_name": { "crmFieldId": "FIRST_NAME", "saveAs": "first_name", "enabled": true, "specialPurpose": null },
|
|
449
|
+
"telegram_id":{ "crmFieldId": "987654", "saveAs": "telegram_id","enabled": true, "specialPurpose": "telegramId" },
|
|
450
|
+
"utm_source": { "crmFieldId": "111", "saveAs": "utm_source", "enabled": true, "specialPurpose": null },
|
|
451
|
+
"utm_campaign":{"crmFieldId": "112", "saveAs": "utm_campaign","enabled": true, "specialPurpose": null }
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
"platformAmoChatConfig": {
|
|
456
|
+
"platformId": "<platformId>",
|
|
457
|
+
"channelId": "<from AmoCRM support>",
|
|
458
|
+
"channelSecretKey": "<from AmoCRM support>",
|
|
459
|
+
"scopeId": "<scope id>",
|
|
460
|
+
"amoAccountId": "<amojo>",
|
|
461
|
+
"amojoId": "<amojo user>"
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## 14. Inline-пример: Bitrix24 с конвертацией лида
|
|
467
|
+
|
|
468
|
+
```json
|
|
469
|
+
{
|
|
470
|
+
"crm": {
|
|
471
|
+
"type": "BITRIX24",
|
|
472
|
+
"config": {
|
|
473
|
+
"domain": "example.bitrix24.ru",
|
|
474
|
+
"memberId": "<portal member id>",
|
|
475
|
+
"clientId": "<oauth client id>",
|
|
476
|
+
"clientSecret": "<oauth client secret>",
|
|
477
|
+
"accessToken": "<encrypted>",
|
|
478
|
+
"refreshToken": "<encrypted>",
|
|
479
|
+
"applicationToken": "<encrypted>",
|
|
480
|
+
"expiresAt": "2026-04-30T13:00:00Z",
|
|
481
|
+
"clientEndpoint": "https://example.bitrix24.ru/rest/",
|
|
482
|
+
"managedColumnPrefix": "[BOT] "
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
"platform": {
|
|
486
|
+
"type": "Bitrix24Bot",
|
|
487
|
+
"bitrix24BotId": 42,
|
|
488
|
+
"bitrix24BotName": "BeSales AI",
|
|
489
|
+
"bitrix24OpenLineId": "5"
|
|
490
|
+
},
|
|
491
|
+
"trigger": {
|
|
492
|
+
"name": "convert_lead_when_qualified",
|
|
493
|
+
"condition": "user explicitly confirmed product interest and budget",
|
|
494
|
+
"actions": [
|
|
495
|
+
{
|
|
496
|
+
"actionType": "CONVERT_LEAD_TO_DEAL",
|
|
497
|
+
"priority": 10,
|
|
498
|
+
"params": {
|
|
499
|
+
"crmId": "<Crm.id>",
|
|
500
|
+
"crmType": "BITRIX24",
|
|
501
|
+
"targetPipelineId": 1,
|
|
502
|
+
"targetStatusId": 5,
|
|
503
|
+
"note": "Лид квалифицирован"
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## 15. Граничные случаи и валидация
|
|
512
|
+
|
|
513
|
+
| Правило | Что AI-сервису помнить |
|
|
514
|
+
|---|---|
|
|
515
|
+
| Один `Crm` на `(workspaceId, type)` | Нельзя подключить две AmoCRM в один workspace. |
|
|
516
|
+
| `chatApiScope=PLATFORM` требует `PlatformAmoChatConfig` | Иначе сообщения не уйдут в карточку. |
|
|
517
|
+
| Bitrix24 — лид≠сделка | Если у `ChatUser` `bitrixLeadId` — это лид, нужно конвертировать. |
|
|
518
|
+
| Telegram Forum CRM — без воронок | Не использовать `LEAD_STATUS` маршрутизацию для этого типа. |
|
|
519
|
+
| GetCourse `dealsTagPrefix` обязателен | Без него action `GETCOURSE_SET_DEAL_TAGS` не будет работать. |
|
|
520
|
+
| Live-проверка Bitrix24 | Follow-up не отправится, если колонка не `system managed`. |
|
|
521
|
+
| Маркеры — для аналитики | Не критично, но полезно для отделения «своих» сделок. |
|
|
522
|
+
| Composite ключ статусов | `(pipelineCrmId, pipelineId, id)` — не использовать только `id`. |
|
|
523
|
+
| `onMessageReceivedMoveToStatus` | Может конфликтовать с `CRM_MOVE_STAGE` action — приоритет последнего. |
|
|
524
|
+
| `isBotActive=false` у статуса | Бот молчит в этом статусе. Часто — для «закрытых» этапов. |
|
|
525
|
+
|
|
526
|
+
## 16. Связь с другими подсистемами
|
|
527
|
+
|
|
528
|
+
| Подсистема | Связь |
|
|
529
|
+
|---|---|
|
|
530
|
+
| **Триггеры** ([`04-triggers-and-actions.md`](04-triggers-and-actions.md)) | `CRM_MOVE_STAGE`, `CRM_UPDATE_FIELD`, `CRM_ADD_NOTE`, `CONVERT_LEAD_TO_DEAL`, `GETCOURSE_*`. |
|
|
531
|
+
| **Маршрутизация** ([`06-routing.md`](06-routing.md)) | `RoutingStrategy=LEAD_STATUS` использует `CrmPipelineStatus` через `AgentRoutingRuleLeadStatus`. |
|
|
532
|
+
| **Платформы** ([`11-platforms.md`](11-platforms.md)) | `sendToCrm`, `dealMarkerType`, `responsibleAssigneeStrategy`, `PlatformAmoChatConfig`. |
|
|
533
|
+
| **Follow-up** ([`08-follow-up.md`](08-follow-up.md)) | `crmNote` пишется в CRM. Bitrix24 — live-проверка колонки. |
|
|
534
|
+
| **Переменные** ([`05-variables.md`](05-variables.md)) | `crmFields`, `outgoingCrmFields`, `{leadStatus}`, `{crmContactLink}`, `{crmDealLink}`. |
|
|
535
|
+
| **Поведение** ([`10-agent-behavior.md`](10-agent-behavior.md)) | `botWaitingReturn`, `aiErrorTargetStageId`, `skipTaskCreationOnCallOperator`. |
|