v1.9.0: Phase 0 — инфраструктура CRM/лидов, миграции БД, расширение моделей

This commit is contained in:
2026-06-04 14:38:43 +03:00
parent e29e2984da
commit f06ecaabe3
15 changed files with 3315 additions and 10 deletions
+430
View File
@@ -0,0 +1,430 @@
# Интеграция с 1С:УНФ 3.0 (1С-Фреш)
> Дата исследования: 04.06.2026
> Статус: ИССЛЕДОВАНИЕ (код не написан)
---
## 1. Краткое описание
Документ описывает доступные API для интеграции сервисного портала AegisOne с 1С:УНФ 3.0, работающей в облаке 1С-Фреш.
**Цель:** Синхронизация данных между 1С:УНФ и порталом — клиенты, заказы, контактные лица.
---
## 2. Доступные интерфейсы 1С-Фреш
| Интерфейс | Назначение | Чтение | Запись | Сложность |
|-----------|-----------|--------|--------|-----------|
| **OData** | Стандартный интерфейс платформы 1С | ✅ | ✅ | Средняя |
| **REST API** | Портал 1С:ITS Fresh-Integration | ✅ | ✅ | Средняя |
| **HTTP-сервис** | УниверсальнаяИнтеграция (UniversalIntegration) | ✅ | ✅ | Высокая |
| **Система взаимодействия** | Вебхуки (внешние → 1С) | ❌ | ✅ | Низкая |
### Главный вывод
**OData** — основной и самый доступный способ интеграции. Через OData доступны практи­чески все объекты 1С:УНФ 3.0: справочники, документы, регистры.
---
## 3. OData интерфейс
### 3.1 Формат URL
```
https://<server>/a/unf/<tenant>/odata/standard.odata/<Entity>
```
| Параметр | Описание | Пример |
|----------|----------|--------|
| `server` | Адрес сервера 1С-Фреш | `https://xxx.1cfresh.com` |
| `app` | Код приложения | `unf` (для УНФ) |
| `tenant` | Номер области данных | `34` |
| `Entity` | Имя объекта метаданных | `Catalog_Контрагенты` |
### 3.2 Аутентификация
```
Authorization: Basic <base64(login:password)>
```
Используется сервисный пользователь 1С-Фреш с правами доступа к OData.
### 3.3 Формат ответа
```
?$format=json — JSON
?$format=atom — Atom/XML (по умолчанию)
?$format=json;odata=nometadata — JSON без метаданных
```
---
## 4. Доступные сущности 1С:УНФ 3.0
### 4.1 Справочники (Catalogs)
| OData URL | Описание | Приоритет для нас |
|-----------|----------|-------------------|
| `Catalog_Контрагенты` | Покупатели, поставщики, прочие контрагенты | 🔴 Высокий → **Клиенты** |
| `Catalog_КонтактныеЛица` | Контактные лица контрагентов | 🔴 Высокий → **Контакты** |
| `Catalog_Номенклатура` | Услуги, товары, работы | 🟡 Средний → Каталог услуг |
| `Catalog_Договоры` | Договоры с контрагентами | 🟡 Средний → Привязка к клиенту |
| `Catalog_Проекты` | Проекты | 🟡 Средний → Связь с заказами |
| `Catalog_Организации` | Наши организации | 🟢 Низкий → Контекст |
| `Catalog_БанковскиеСчета` | Расчётные счета | 🟢 Низкий |
### 4.2 Документы (Documents)
| OData URL | Описание | Приоритет для нас |
|-----------|----------|-------------------|
| `Document_ЗаказКлиента` | Заказ покупателя | 🔴 Высокий → **Заказ/Лид** |
| `Document_РеализацияТоваровУслуг` | Реализация товаров и услуг | 🟡 Средний → История работ |
| `Document_АктВыполненныхРабот` | Акт выполненных работ | 🟡 Средний → Подтверждение |
| `Document_СчетНаОплату` | Счёт на оплату | 🟡 Средний → Финансы |
| `Document_ПоступлениеТоваров` | Поступление товаров | 🟢 Низкий → Закупки |
| `Document_СчетНаОплатуПоставщика` | Счёт от поставщика | 🟢 Низкий |
### 4.3 Регистры сведений
| OData URL | Описание |
|-----------|----------|
| `InformationRegister_ЦеныНоменклатурыДокументов` | Цены номенклатуры |
| `InformationRegister_КурсыВалют` | Курсы валют |
| `InformationRegister_НастройкиСистемыНалогообложения` | Учётная политика |
### 4.4 Табличные части
Доступны через суффикс имени:
```
Document_ЗаказКлиента_Товары — табличная часть «Товары»
Document_ЗаказКлиента_Услуги — табличная часть «Услуги»
Catalog_Контрагенты_КонтактнаяИнформация — контактная информация
```
---
## 5. Примеры запросов
### 5.1 Все контрагенты (покупатели)
```http
GET /odata/standard.odata/Catalog_Контрагенты?$format=json
&$select=Ref_Key,Description,ИНН,КПП,РегистрационныйНомер
&$filter=not (IsFolder)
&$orderby=Description
&$top=100
Authorization: Basic <auth>
```
### 5.2 Поиск контрагента по ИНН
```http
GET /odata/standard.odata/Catalog_Контрагенты?$format=json
&$select=Ref_Key,Description,ИНН,КПП
&$filter=(ИНН eq '2310031540')
Authorization: Basic <auth>
```
### 5.3 Поиск контрагента по наименованию
```http
GET /odata/standard.odata/Catalog_Контрагенты?$format=json
&$select=Ref_Key,Description,ИНН
&$filter=like(Description, 'Аегис%')
Authorization: Basic <auth>
```
### 5.4 Заказы конкретного клиента
```http
GET /odata/standard.odata/Document_ЗаказКлиента?$format=json
&$expand=Контрагент
&$select=Ref_Key,Number,Date,СуммаДокумента,Статус,Контрагент/Description
&$filter=Контрагент_Key eq guid'...'
&$orderby=Date desc
Authorization: Basic <auth>
```
### 5.5 Контактные лица контрагента
```http
GET /odata/standard.odata/Catalog_КонтактныеЛица?$format=json
&$select=Ref_Key,Description,Должность,Владелец_Key
&$filter=Владелец_Key eq guid'...'
Authorization: Basic <auth>
```
### 5.6 Номенклатура (услуги)
```http
GET /odata/standard.odata/Catalog_Номенклатура?$format=json
&$expand=ЕдиницаИзмерения
&$select=Ref_Key,Description,Артикул,ВидНоменклатуры
&$filter=not (IsFolder)
&$orderby=Description
Authorization: Basic <auth>
```
### 5.7 Создание нового контрагента (POST)
```http
POST /odata/standard.odata/Catalog_Контрагенты
Content-Type: application/json
Authorization: Basic <auth>
{
"Description": "ООО Ромашка",
"ИНН": "2310031540",
"КПП": "231001001",
"ЮридическоеФизическоеЛицо": "ЮридическоеЛицо"
}
```
### 5.8 Обновление контрагента (PATCH)
```http
PATCH /odata/standard.odata/Catalog_Контрагенты(guid'...')
Content-Type: application/json
Authorization: Basic <auth>
{
"Description": "ООО Ромашка (обновлено)"
}
```
---
## 6. Маппинг сущностей 1С:УНФ → Наш портал
### 6.1 Контрагенты → Клиенты
| Поле 1С:УНФ | Поле портала | Тип маппинга |
|-------------|-------------|--------------|
| `Ref_Key` | `onec_id` (UUID) | Прямой |
| `Description` | `company_name` | Прямой |
| `ИНН` | `inn` | Прямой (ключ дедупликации) |
| `КПП` | `kpp` | Прямой |
| `Телефон` | `phone` | Нормализация |
| `Email` | `email` | Прямой |
| `Адрес` | `address` | Прямой |
### 6.2 Заказ клиента → Лид/Заказ
| Поле 1С:УНФ | Поле портала | Тип маппинга |
|-------------|-------------|--------------|
| `Ref_Key` | `onec_order_id` (UUID) | Прямой |
| `Number` | `number` | Прямой |
| `Date` | `created_at` | Конвертация |
| `СуммаДокумента` | `amount` | Прямой |
| `Статус` | `status` | Маппинг статусов |
| `Контрагент_Key` | `customer_id` | FK (по onec_id) |
### 6.3 Статусы заказов
| Статус 1С:УНФ | Статус портала |
|---------------|---------------|
| В работе | `in_progress` |
| Закрыт | `completed` |
| Отменён | `cancelled` |
---
## 7. Стратегия интеграции
### Фаза 0: Настройка доступа (отдельная задача)
1. **Получить от пользователя:**
- URL базы в 1С-Фреш (типа `https://xxx.1cfresh.com/a/unf/`)
- Логин/пароль сервисного пользователя
- Номер области данных (tenant)
2. **Проверить доступность:**
- Выполнить тестовый запрос к `$metadata`
- Убедиться в наличии нужных сущностей
- Проверить права доступа
3. **Создать таблицу настроек:**
```sql
CREATE TABLE onec_settings (
id SERIAL PRIMARY KEY,
base_url VARCHAR(500) NOT NULL,
tenant VARCHAR(50) NOT NULL,
login VARCHAR(100) NOT NULL,
password_encrypted TEXT NOT NULL, -- fernet
last_sync_at TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW()
);
```
### Фаза 1: Синхронизация контрагентов → клиенты
1. **Миграция:** Таблица `onec_sync_log` для логирования
```sql
CREATE TABLE onec_sync_log (
id SERIAL PRIMARY KEY,
entity_type VARCHAR(50) NOT NULL, -- 'contragent', 'order', etc.
onec_id UUID NOT NULL,
portal_id INTEGER,
action VARCHAR(20) NOT NULL, -- 'create', 'update', 'skip'
details JSONB,
synced_at TIMESTAMP DEFAULT NOW()
);
```
2. **Задача синхронизации:**
- Читаем `Catalog_Контрагенты` из 1С
- Дедупликация по ИНН (точное совпадение)
- Если ИНН нет — по наименованию + ИНН КПП
- Создаём/обновляем в таблице `customers`
- Логируем каждое действие
3. **Периодичность:** Раз в день (ночью) или по требованию
### Фаза 2: Синхронизация заказов
1. **Миграция:** Поле `onec_order_id` в `leads` или новая таблица
2. **Задача синхронизации:**
- Читаем `Document_ЗаказКлиента` из 1С
- Привязка к клиенту по `Контрагент_Key`
- Маппинг статусов
- Логирование
### Фаза 3 (опционально): Обратная запись
- Создание заказа в портале → создание в 1С
- Требует аккуратной обработки ошибок
- **Рекомендация:** Только после стабильности фаз 1-2
---
## 8. Технические детали
### 8.1 Python-клиент для OData
Рекомендуемая библиотека: `pyodata` или `requests` (raw OData).
```python
import base64
import httpx
class OneCFreshClient:
"""Клиент для работы с 1С-Фреш через OData."""
def __init__(self, base_url: str, tenant: str, login: str, password: str):
self.base_url = base_url.rstrip('/')
self.tenant = tenant
self.auth = base64.b64encode(f"{login}:{password}".encode()).decode()
async def get_contragents(self, top: int = 100, skip: int = 0) -> list:
"""Получить список контрагентов."""
url = f"{self.base_url}/a/unf/{self.tenant}/odata/standard.odata/Catalog_Контрагенты"
params = {
"$format": "json;odata=nometadata",
"$select": "Ref_Key,Description,ИНН,КПП",
"$filter": "not (IsFolder)",
"$top": str(top),
"$skip": str(skip),
}
headers = {"Authorization": f"Basic {self.auth}"}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params, headers=headers)
resp.raise_for_status()
return resp.json().get("value", [])
async def get_contragent_by_inn(self, inn: str) -> dict | None:
"""Найти контрагента по ИНН."""
url = f"{self.base_url}/a/unf/{self.tenant}/odata/standard.odata/Catalog_Контрагенты"
params = {
"$format": "json;odata=nometadata",
"$select": "Ref_Key,Description,ИНН,КПП",
"$filter": f"(ИНН eq '{inn}')",
}
headers = {"Authorization": f"Basic {self.auth}"}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params, headers=headers)
resp.raise_for_status()
items = resp.json().get("value", [])
return items[0] if items else None
```
### 8.2 Ограничения
| Ограничение | Описание | Решение |
|-------------|----------|---------|
| **Multi-tenancy** | Каждая база в своей области данных | Указать tenant в URL |
| **Публикация OData** | Должна быть включена в конфигурации | Проверить через `$metadata` |
| **Права доступа** | Сервисный пользователь должен иметь права на чтение | Настроить роли в 1С |
| **Пагинация** | Большие выборки — через `$top`/`$skip` | Построчная загрузка |
| **Rate limits** | Возможны ограничения на частоту запросов | Очередь задач, retry |
### 8.3 Проверка доступности
```bash
# Тестовый запрос метаданных
curl -u "login:password" \
"https://xxx.1cfresh.com/a/unf/34/odata/standard.odata/$metadata"
# Тестовый запрос контрагентов
curl -u "login:password" \
"https://xxx.1cfresh.com/a/unf/34/odata/standard.odata/Catalog_Контрагенты?\$format=json&\$top=5"
```
---
## 9. Сравнение с webhook (Система взаимодействия)
| Критерий | OData | Webhook |
|----------|-------|---------|
| **Чтение данных** | ✅ Да | ❌ Нет (только запись) |
| **Запись данных** | ✅ Да | ✅ Да (только в 1С) |
| **Направление** | Bidirectional | One-way (внешнее → 1С) |
| **Формат** | JSON (стандартный OData) | JSON (свой формат) |
| **Аутентификация** | Basic Auth | URL + логин/пароль |
| **Сложность** | Средняя | Низкая |
| **Использование** | Синхронизация данных | Уведомления, сообщения |
**Вывод:** OData — для синхронизации данных. Webhook — для отправки уведомлений в 1С.
---
## 10. Рекомендации
### Приоритет implementation
1. **OData синхронизация контрагентов** — самый важный функционал
2. **OData синхронизация заказов** — расширение функционала
3. **Webhook уведомления** — опционально, для двусторонней связи
### Вопросы к пользователю (для фазы 0)
1. URL базы в 1С-Фреш?
2. Логин/пароль сервисного пользователя?
3. Номер области данных (tenant)?
4. Какие данные в приоритете: контрагенты, заказы, или всё сразу?
5. Направление синхронизации: только из 1С → портал, или bidirectional?
### Рекомендация
**Начать с read-only синхронизации контрагентов** — самый безопасный и быстрый способ:
- Не требует записи обратно
- Минимальные риски
- Быстро даёт результат (клиенты из 1С появляются в портале)
- Потом можно расширять до заказов и bidirectional
---
## 11. Ссылки
- [Документация 1С-Фреш: OData](https://its.1c.ru/db/fresh/content/19956692/hdoc)
- [Платформа 1С: REST интерфейс](https://v8.1c.ru/platforma/rest-interfeys/)
- [OData: правила формирования имени ресурса](https://42clouds.com/ru-ru/manuals/interfeys-odata-pravila-formirovaniya-imeni-resursa/)
- [Работа с 1С через OData (Infostart)](https://infostart.ru/1c/articles/1570140/)
- [odata1c-client (GitHub)](https://github.com/Dakword/odata1c-client) — PHP-клиент с примерами
- [Контрагенты и контактные лица в 1С:УНФ](https://unf4you.ru/publ/kontragenty_i_kontaktnye_lica/1-1-0-407)
- [Заказ покупателя в 1С:УНФ](https://estart1c.ru/unf-30/189-kak-v-1sroznice-i-1sunf-sozdat-i-ispolzovat-zakaz-pokupatelja.html)
+2 -2
View File
@@ -85,12 +85,12 @@ py_service/
| Параметр | Значение |
|----------|----------|
| Тип | Python FastAPI (Max Bot API) |
| FastAPI | порт **8001** |
| FastAPI | порт **8002** |
| PostgreSQL | общая БД `aegisone` (порт 5432), таблицы с префиксом `bot_` |
| Домен | `max.aegisone.ru` |
| Директория | `/opt/projects/aegisone-py/max_bot/` |
| Контейнер | `aegisone-max-bot` |
| Webhook | `https://max.aegisone.ru/webhook` → localhost:8001 |
| Webhook | `https://max.aegisone.ru/webhook` → localhost:8002 |
| Токен бота | `id2311381465_bot` (хранится в bot_settings) |
**Структура:**
File diff suppressed because it is too large Load Diff
+541
View File
@@ -0,0 +1,541 @@
# Yeastar S20 — Интеграция с AegisOne
> Дата последнего обновления: 03.06.2026
---
## 1. Подключение к АТС
### Сетевая схема
```
┌─────────────────────┐ ┌──────────────────────────┐
│ Сервер AegisOne │ │ Локальная сеть (офис) │
│ 81.177.141.34 │ │ 192.168.1.0/24 │
│ │ │ │
│ ┌───────────────┐ │ VPN/ │ ┌──────────────────┐ │
│ │ py_service │──┼──port───┼──│ Yeastar S20 │ │
│ │ max_bot │ │ forward │ │ 192.168.1.150 │ │
│ └───────────────┘ │ │ │ │ │
│ │ │ │ WAN: │ │
│ │ │ │ 185.105.171.187 │ │
└─────────────────────┘ │ └──────────────────┘ │
└──────────────────────────┘
```
### IP-адреса и порты
| Ресурс | Локальный IP | Белый IP | Порт (локал) | Порт (проброс) | Протокол |
|--------|-------------|----------|-------------|----------------|----------|
| **АТС (Web UI)** | 192.168.1.150 | 185.105.171.187 | 8088 | 45088 | HTTPS |
| **AMI** | 192.168.1.150 | 185.105.171.187 | 5038 | 45038 | TCP |
| **MySQL (CDR)** | 192.168.1.150 | 185.105.171.187 | 3306 | 43306 | TCP |
| **FTP** | 192.168.1.150 | 185.105.171.187 | 21 | 45021 | TCP |
| **SSH** | 192.168.1.150 | 185.105.171.187 | 8022 | — | TCP |
### Проброс портов (на роутере/ файрволе офиса)
| Направление | Протокол | Внешний порт → Внутренний IP:порт |
|-------------|----------|----------------------------------|
| Internet → АТС | TCP | 45038 → 192.168.1.150:5038 (AMI) |
| Internet → АТС | TCP | 45021 → 192.168.1.150:21 (FTP) |
| Internet → АТС | TCP | 43306 → 192.168.1.150:3306 (MySQL) |
| Internet → АТС | TCP | 45088 → 192.168.1.150:8088 (Web UI) |
---
## 2. Учётные данные и параметры устройства
### Параметры устройства (актуально на 03.06.2026)
| Параметр | Значение | Источник |
|----------|----------|----------|
| Модель | Yeastar S20 | FTP `/support/tmp/deviceinfo.txt` |
| Серийный номер | `3691C1453100` | FTP `/support/tmp/deviceinfo.txt` |
| Прошивка (software) | `30.15.0.206` | FTP `/support/tmp/deviceinfo.txt` |
| Железо (hardware) | V1.30 0005-0000 | FTP `/support/tmp/deviceinfo.txt` |
| OEM | Yeastar S20 | FTP `/support/tmp/deviceinfo.txt` |
| Asterisk | 13.7.0 | AMI `CoreSettings` |
| AMI версия | 2.8.0 | AMI `CoreSettings` |
| MySQL | 5.1.61 | MySQL greeting |
| PBX Center | 1.24.2 | Web UI App Center |
| SSH-сервер | dropbear 2019.78 | SSH banner (порт 8022) |
| FTP-сервер | vsFTPd 3.0.2 | FTP banner (порт 21) |
| CDR | Включён | AMI `CoreSettings` |
| Max звонков | 10 | AMI `CoreSettings` |
| REST API | Не поддерживается на S20 | Документация Yeastar |
### Web UI (админ-панель АТС)
| Параметр | Значение |
|----------|----------|
| URL | `https://192.168.1.150:8088` (локально) или `https://185.105.171.187:45088` (интернет) |
| Логин | `admin` |
| Пароль | `.-SHGWa_12` |
### AMI (Asterisk Manager Interface)
| Параметр | Значение |
|----------|----------|
| Хост | `192.168.1.150` (локально) или `81.177.141.34` (из сервера, через проброс) |
| Порт | `5038` (проброс: `45038`) |
| Логин | `1cuser` |
| Пароль | `1csecret` |
| Привилегии | Ограниченные (нет `Command`, `Originate`) |
| Разрешённые IP | `81.177.141.34/255.255.255.255` + `192.168.1.0/255.255.255.0` |
### MySQL (доступ к CDR)
| Параметр | Значение |
|----------|----------|
| Хост | `192.168.1.150` (локально) или через проброс `43306` |
| Порт | `3306` (проброс: `43306`) |
| Логин | `1cuser` |
| Пароль | `1csecret` |
| **ВАЖНО** | `charset='utf8'` (MySQL 5.1.61 не поддерживает `utf8mb4`!) |
| БД | `cdr` |
### FTP (записи звонков)
| Параметр | Значение |
|----------|----------|
| Порт | `21` (проброс: `45021`) |
| Логин | `support` |
| Пароль | `T4oQSU_?` |
| Статус | ✅ **Работает!** |
| Доступ | Корневая ФС (чroot), `/tmp` недоступен |
Доступные каталоги через FTP:
- `/sounds/record/` — звуки (пусто)
- `/storage_share/mmc1` — симлинк → `/tmp/media/mmc1` (нерезолвящийся через FTP)
- `/support/tmp/deviceinfo.txt`**информация об устройстве**
- `/support/` — системные файлы, bin
- `/gui_backups/` — бэкапы
- `/fax/`, `/www/`, `/var/`, `/etc/`, `/boot/`, `/cache/` — системные каталоги
**Ограничение**: записи звонков в `/tmp/media/mmc1/autorecords/` **недоступны**`/tmp` за пределами FTP-чroot. Для доступа к записям нужен SSH.
### SSH (файловая система)
| Параметр | Значение |
|----------|----------|
| Порт | `8022` |
| SSH-сервер | dropbear 2019.78 |
| Пароль | `T4oQSU_?` |
| Логин | **НЕ ИЗВЕСТЕН** |
| Статус | ✅ Порт открыт, но логин не найден |
| **Рекомендация** | Не использовать SSH — только MySQL + AMI |
### Хранилище данных (в Web UI)
| Параметр | Значение |
|----------|----------|
| Имя файла | `share` |
| Логин | `share` |
| Пароль | `9tktYfM2` |
| Назначение | Сетевая папка (симлинк `mmc1 -> /tmp/media/mmc1` в `/storage_share/`) |
---
## 3. Где найти информацию об АТС
### Серийный номер, модель, прошивка
| Источник | Способ |
|----------|--------|
| **FTP** `/support/tmp/deviceinfo.txt` | Содержит: hardware, software, sn, product, oem |
| **Web UI** `Dashboard` или `Settings > System > General > Preferences` | Отображается на главной странице |
| **Web UI** `Settings > System > General > About` | Версия прошивки и модель |
Содержимое `deviceinfo.txt`:
```
hardware: V1.30 0005-0000
software: 30.15.0.206
sn: 3691C1453100
product: Yeastar S20
oem: Yeastar S20
```
### Версия Asterisk и компонентов
| Источник | Способ |
|----------|--------|
| **AMI** команда `CoreSettings` | AsteriskVersion, AMIversion, CoreCDRenabled |
| **AMI** команда `ListCommands` | Список доступных AMI-команд с привилегиями |
| **Web UI** `Settings > System > General > About` | Версия PBX Center, прошивки |
### Конфигурация (абоненты, очереди, транки)
| Источник | Что даёт |
|----------|----------|
| **AMI** `QueueStatus` | Очереди, участники, стратегии |
| **AMI** `VoicemailUsersList` | Абоненты, голосовая почта |
| **AMI** `PJSIPQualify` | Статус SIP-абонента |
| **MySQL** БД `cdr` | История звонков, абоненты (из CDR) |
| **Web UI** `Extensions` | Список расширений |
| **Web UI** `Trunks` | SIP-транки |
| **Web UI** `Call Queue` | Очереди |
### Записи звонков
| Источник | Способ |
|----------|--------|
| **MySQL** колонки `recordpath`, `monitorfile` | Путь и имя WAV-файла на PBX |
| **Web UI** `CDR and Recordings` | Ручное скачивание через браузер |
| **FTP** `/storage_share/mmc1` | Симлинк на `/tmp/media/mmc1` (нерезолвится через FTP) |
| **SSH** порт 8022 | Прямой доступ к файловой системе (логин неизвестен) |
---
## 4. Абоненты и очереди
### Внутренние номера (расширения)
| Номер | Назначение |
|-------|-----------|
| 1001 | Основной (Инженер) |
| 1002 | Второй (Инженер) |
| 1003 | Дополнительный |
| 1000 | Тестовый |
### Очереди (Queue)
| Номер очереди | Стратегия | Расширения |
|---------------|-----------|------------|
| 6700 | ringall | 1001, 1002 |
| 6701 | rrmemory | 1001 |
| 6702 | ringall | 1002 |
### Транки (линии)
| Транк | Тип | Назначение |
|-------|-----|-----------|
| Mango | VoIP-провайдер | Исходящие/входящие через SIP |
| Tele2 | VoIP-провайдер | Исходящие/входящие через SIP |
| YSGSM | GSM-модуль | Исходящие через GSM-SIM |
---
## 5. База данных CDR
### Структура
БД: `cdr`, таблицы: `cdr` (текущая) + `cdr_YYYYMM` (архивные).
### Колонки таблицы `cdr`
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | int | ID записи |
| `datetime` | datetime | Дата и время звонка |
| `clid` | varchar | Caller ID (кто звонит) |
| `src` | varchar | Номер отправителя |
| `dst` | varchar | Номер получателя |
| `dcontext` | varchar | Контекст диалплана |
| `srctrunk` | varchar | Транк отправителя |
| `dstrunk` | varchar | Транк получателя |
| `lastapp` | varchar | Последнее приложение (Dial, Queue, BackGround...) |
| `lastdata` | varchar | Данные последнего приложения (номер, очередь) |
| `duration` | int | Общая длительность (секунды) |
| `billable` | int | Биллируемая длительность (секунды) |
| `disposition` | varchar | Статус: ANSWERED, NO ANSWER, BUSY, VOICEMAIL, FAILED |
| `calltype` | varchar | Тип: Inbound, Outbound, Internal |
| `accountcode` | varchar | Код аккаунта |
| `uniqueid` | varchar | Уникальный ID звонка |
| `recordfile` | varchar | Имя файла записи (WAV) |
| `recordpath` | varchar | Путь к записи на PBX |
| `monitorfile` | varchar | Имя файла мониторинга |
| `monitorpath` | varchar | Путь к мониторингу на PBX |
| `extfield1-5` | varchar | Дополнительные поля |
| `didnumber` | varchar | DID-номер (входящий номер) |
| `srcchanurl` | varchar | SIP URL канала отправителя |
| `dstchanurl` | varchar | SIP URL канала получателя |
| `companycontact` | varchar | Имя контакта компании |
| `personalcontact` | varchar | Имя личного контакта |
| `contactnumber` | varchar | Контактный номер |
### Примеры запросов
```sql
-- Все звонки за сегодня
SELECT datetime, src, dst, duration, billable, disposition, calltype
FROM cdr.cdr
WHERE DATE(datetime) = CURDATE()
ORDER BY datetime DESC;
-- Звонки за конкретный месяц
SELECT * FROM cdr.cdr_202606 ORDER BY datetime DESC;
-- Входящие звонки на очередь 6700
SELECT datetime, src, dst, duration, disposition
FROM cdr.cdr
WHERE dst LIKE '6700%' AND calltype = 'Inbound'
ORDER BY datetime DESC;
-- Звонки с записями
SELECT datetime, src, dst, duration, recordfile, recordpath
FROM cdr.cdr
WHERE recordfile != ''
ORDER BY datetime DESC;
-- Статистика по дням
SELECT DATE(datetime) as day, COUNT(*) as calls,
SUM(CASE WHEN disposition='ANSWERED' THEN 1 ELSE 0 END) as answered
FROM cdr.cdr
WHERE datetime >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY DATE(datetime)
ORDER BY day DESC;
```
### Таблица `queue_log`
Содержит логи событий очередей (поступление, ответ, ожидание, сброс).
---
## 6. Записи звонков
### Где хранятся
На файловой системе PBX: `/tmp/media/mmc1/autorecords/YYYYMM/`
Формат имени файла:
```
YYYYMMDDHHMMSS-UNIQUEID-SRC-DST-Direction.wav
```
Примеры из CDR:
```
20260110164821-1768052891.15-1001-+79531116788-Outbound.wav
20260402105623-1775116571.13-1001-89531116788-Outbound.wav
20260512162645-1778592395.0-+79898250183-1001-Inbound.wav
```
### Как получить
1. **Путь в CDR**: колонки `recordpath`, `monitorpath` (путь на PBX)
2. **FTP** (`support`/`T4oQSU_?`): записи **недоступны**`/tmp` за пределами FTP-чroot
3. **SSH** (порт 8022, пароль `T4oQSU_?`, логин неизвестен): теоретически доступен, но логин не найден
4. **Web UI**: `CDR and Recordings` — ручное скачивание через браузер
---
## 7. AMI — Доступные команды
### Работает с 1cuser/1csecret
| Команда | Описание | Статус |
|---------|----------|--------|
| `Ping` | Проверка соединения | ✅ |
| `CoreStatus` | Статус ядра (uptime, кол-во звонков) | ✅ |
| `CoreShowChannels` | Список активных каналов | ✅ |
| `CoreSettings` | Настройки ядра (Asterisk, CDR) | ✅ |
| `QueueStatus` | Статус очередей + участников | ✅ |
| `VoicemailUsersList` | Список абонентов голосовой почты | ✅ |
| `PJSIPQualify` | Пинг SIP-абонента | ✅ |
| `Status` | Статус всех каналов | ✅ |
| `ListCommands` | Список доступных команд | ✅ |
### Не работает (Permission denied)
| Команда | Описание |
|---------|----------|
| `Command` | Выполнение CLI-команд (`cdr show` и т.д.) |
| `Originate` | Исходящий звонок |
| `ShowDialPlan` | Просмотр диалплана |
| `Hangup` | Завершение звонка |
| `Redirect` | Переадресация звонка |
### Real-time события
При включённых `Events: On` AMI автоматически отправляет:
- `Event: Cdr` — при завершении звонка (содержит CDR-данные)
- `Event: Newchannel` — при начале звонка
- `Event: Dial` — при наборе номера
- `Event: Hangup` — при завершении
---
## 8. Пример подключения (Python)
### MySQL — запрос CDR
```python
import pymysql
conn = pymysql.connect(
host="192.168.1.150", # или внешний IP через проброс
port=3306, # или 43306 через проброс
user="1cuser",
password="1csecret",
database="cdr",
charset="utf8", # ВАЖНО: не utf8mb4!
connect_timeout=5
)
cursor = conn.cursor()
cursor.execute("SELECT * FROM cdr ORDER BY datetime DESC LIMIT 10")
for row in cursor.fetchall():
print(row)
conn.close()
```
### AMI — real-time события
```python
import socket
import time
def ami_connect(host, port, username, password):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
# Читаем приветствие
greeting = sock.recv(4096).decode()
# Логин
login_msg = (
f"Action: Login\r\n"
f"Username: {username}\r\n"
f"Secret: {password}\r\n"
f"Events: On\r\n"
f"\r\n"
)
sock.send(login_msg.encode())
time.sleep(1)
response = sock.recv(16384).decode()
return sock
# Использование
sock = ami_connect("192.168.1.150", 5038, "1cuser", "1csecret")
# Слушаем события
while True:
data = sock.recv(16384).decode()
if data:
print(data)
```
### AMI — из сервера (через проброс)
```python
# Подключение через проброс портов с сервера
sock = ami_connect("81.177.141.34", 45038, "1cuser", "1csecret")
# Или напрямую (если сервер в локальной сети)
sock = ami_connect("192.168.1.150", 5038, "1cuser", "1csecret")
```
---
## 9. Схема интеграции с AegisOne
```
┌──────────────────┐ MySQL:3306 ┌──────────────────┐
│ py_service │◄────────────────────│ Yeastar S20 │
│ (CDR polling) │ │ (cdr БД) │
│ │ AMI:5038 │ │
│ (real-time) │◄────────────────────│ (события) │
│ │ │ │
│ ┌──────────────┐ │ │ ┌──────────────┐ │
│ │ /service/ │ │ │ │ Записи WAV │ │
│ │ phone/ │ │ FTP:21 │ │ /tmp/media/ │ │
│ │ history │ │◄──(когда будет)────│ │ │ │
│ │ playback │ │ │ └──────────────┘ │
│ └──────────────┘ │ └──────────────────┘
└──────────────────┘
┌──────────────────┐
│ max_bot │
│ (уведомления о │
│ пропущенных) │
└──────────────────┘
```
---
## 10. План интеграции (TODO)
### Фаза 1: CDR (готово к реализации)
- [ ] Модель БД для хранения звонков
- [ ] Периодический опрос MySQL (каждые 5 мин)
- [ ] Страница истории звонков в сервисном портале
- [ ] Фильтры: по дате, номеру, типу, статусу
### Фаза 2: Real-time
- [ ] AMI-клиент для получения событий
- [ ] Уведомления о пропущенных звонках (в max_bot)
- [ ] Статус абонентов (online/offline)
- [ ] Мониторинг очередей
### Фаза 3: Записи
- [x] FTP логин найден: `support` / `T4oQSU_?`
- [ ] FTP-чroot не даёт доступ к `/tmp/media/mmc1/autorecords/` — нужен SSH или другой способ
- [ ] Разобраться с SSH-логином (пароль `T4oQSU_?` известен)
- [ ] Скачивание записей
- [ ] Плеер для прослушивания в портале
### Фаза 4: Управление
- [ ] Исходящие звонки из портала (если AMI получит права)
- [ ] Переключение звонков
- [ ] Статистика и отчёты
---
## 11. Известные проблемы
1. **Записи звонков недоступны через FTP** — логин `support` работает, но `/tmp/media/mmc1/autorecords/` за пределами FTP-чroot. Нужен SSH (порт 8022, пароль `T4oQSU_?`, логин неизвестен)
2. **AMI ограниченные права** — на S20 нельзя расширить привилегии AMI-пользователя через Web UI. Доступны только readonly-команды
3. **REST API не поддерживается** — только для S50/S100/S300
4. **MySQL 5.1.61** — старая версия, не поддерживает `utf8mb4`. Использовать `charset='utf8'`
5. **Кириллица в CDR** — имена контактов отображаются в кодировке CP1251/garbage (проблема кодировки MySQL-клиента)
---
## 12. Ссылки
- [Yeastar S-Series AMI Documentation](https://help.yeastar.com/en/s-series/topic/asterisk-manager-interface-ami.html)
- [Yeastar Developer Guide](https://support.yeastar.com/hc/en-us/articles/235972668)
- [Asterisk AMI Protocol](https://docs.asterisk.org/languages/en/Asterisk_18_Documentation/DAHDI_DAHDI_Hardware_Digium_Interface_Hardware/Administering/Asterisk_Manager_Interface_AMI/)
---
## 13. FTP — Структура каталогов
```
/ (корень FTP, доступен как support)
├── boot/
├── cache/
├── etc/
├── fax/
├── ftp_media/ (пусто)
├── gui_backups/
├── imageupdate/
├── lost+found/
├── rcstop/
├── sounds/
│ ├── moh/ (музыка на удержании)
│ └── record/ (пусто)
├── storage_share/
│ └── mmc1 → /tmp/media/mmc1 (симлинк, нерезолвящийся)
├── support/
│ ├── tmp/
│ │ ├── deviceinfo.txt ← ИНФОРМАЦИЯ ОБ УСТРОЙСТВЕ
│ │ └── testsysmailbox.eml
│ ├── autoptemplate/
│ ├── bin/
│ ├── customcfg/
│ └── fxotune/
├── syslog/
├── syslog_backup/
├── tftpboot/
├── var/
├── webupload/
├── www/
└── ysapps/
```
### Ключевые файлы
| Путь | Содержимое |
|------|-----------|
| `/support/tmp/deviceinfo.txt` | Серийный номер, модель, прошивка |
| `/support/tmp/testsysmailbox.eml` | Тестовый шаблон почты |
| `/storage_share/mmc1` | Симлинк на записанные файлы (недоступен через FTP) |
+267
View File
@@ -0,0 +1,267 @@
Если смотреть не со стороны маркетинга, а со стороны внутренней сервисной системы, то воронка продаж превращается в процесс обработки лида внутри портала.
В этом случае телефон, бот, сайт, почта — это просто каналы поступления обращений в единую систему.
**1. Общая архитектура**
```
Клиент
├── Телефон
├── Telegram-бот
├── Сайт
├── Email
└── WhatsApp
Сервисный портал
Лид (Lead)
Квалификация
Сделка
Проект
Выполнение
Закрытие
```
**2. Роли в системе**
--- Руководитель
Видит:
новые лиды;
активные сделки;
загрузку инженеров;
прибыль;
SLA;
показатели конверсии.
Его задача:
назначать ответственных;
контролировать сроки;
отслеживать KPI.
--- Инженер
Работает с технической частью.
Видит:
назначенные заявки;
технические задания;
историю клиента;
комментарии.
Может:
создавать этапы работ;
запрашивать данные;
прикладывать документы;
переводить задачи между статусами.
--- Техник
Исполнитель.
Видит только:
свои задачи;
инструкции;
сроки;
вложения.
Может:
отмечать выполнение;
прикладывать фото;
добавлять комментарии.
**3. Как выглядит жизненный цикл обращения**
Шаг 1. Обращение через бота
Клиент пишет в бот
Бот собирает контактные данные и создается заявка (необходимо продумать как собрать название организации от клиента)
На основе этой заявки необходимо (тот кто ее обрабатывает) должен создать нового Клиента (Управление - Клиенты) или Добавить к существующему клиенту. Если есть точно совпадающие даннуе, то заявка должна автоматически привязываться к клиенту. Важно - Назавание организации может быть одинаковым у разных клиентов.
Шаг 2. Создание лида
В портале появляется:
```
Клиент №ххх
Источник: Max Bot
Компания: ООО Ромашка
Контакт: Иван Петров
Телефон: +79991234567
Услуга: Пример: Аудит безопасности
Статус: Новый
```
Шаг 3. Как подвязать телефонные звонки
Это одна из самых полезных функций.
Вариант 1. IP-телефония (лучший вариант)
Подключаются:
Asterisk (Локальная АТС)
FreePBX
MikoPBX
Zadarma
Mango Office (Доступ к webui)
Novofon
Каждый звонок автоматически попадает в портал.
--- Входящий звонок
Телефон звонит.
Портал получает событие:
```
{
"phone":"+79991234567",
"type":"incoming"
}
```
Портал ищет клиента.
Если клиент найден:
```
Входящий звонок
ООО Ромашка
Последняя заявка: Аудит безопасности
Ответственный: Иванов
```
Карточка открывается автоматически.
--- Если номер неизвестен
Создается временный клиент:
```
Клиент №ххх
Источник: Телефон
Телефон: +79991234567
Статус: Новый
```
После разговора менеджер заполняет остальные поля и привязывает его к существующему или создает нового клиента.
--- Автоматическая запись звонков
В карточке хранится:
```
Звонок #1
Дата: 03.06.2026
Длительность: 12:34
Запись: play.wav
```
Руководитель может прослушать разговор.
--- Автоматическая расшифровка
После разговора:
```
Whisper
Yandex SpeechKit
Google Speech-to-Text
YandexGPT
```
создают текст.
Карточка получает:
``` Пример:
Краткое содержание:
Клиент интересуется аудитом.
Планирует внедрение в июле.
Бюджет около 500 тыс.
```
Шаг 4. Автоматическая постановка задач
После завершения звонка система может создавать задачи основываясь на расшифровке разговара и запрашивает подтверждение у сотрудника.
Пример:
Менеджер выбрал:
```
Подготовить КП
```
Портал автоматически создает задачу инженеру.
```
Задача #ууу
Подготовить коммерческое предложение
Исполнитель: Инженер Петров
Срок: Завтра 12:00
```
--- Рекомендуемая структура статусов
Для лидов:
```
Новый
Связаться
Квалификация
Коммерческое предложение
Переговоры
Согласование
Выигран
Проигран
```
(Добавить возможность создавать и удалять дополнительно свои)
Для проектов:
```
Подготовка
В работе
Ожидание клиента
Тестирование
Завершено
Закрыто
```
(Добавить возможность создавать и удалять дополнительно свои)
**4. Что особенно полезно реализовать **
Единая карточка клиента
Внутри:
```
Контакты
Компания
История звонков
История чатов
Файлы
Проекты
Акты
Счета
Договоры
Задачи
Заявки / Тикеты
Комментарии
```
Заявкам добавить Критичность (сейчас приоритет)
```
Критичная
Высокая
Средняя
Низкая
```
Никто не ищет информацию по разным системам!
**5. Оптимальная схема для собственной разработки **
```
Telegram Bot
├─────────┐
│ │
Телефония Сайт
│ │
└────┬────┘
Lead Service
CRM Module
Workflow Engine
Engineer Portal
Client Projects
```
Если ваша компания оказывает сервисные услуги (ИБ, IT-аутсорсинг, инженерные работы, обслуживание оборудования и т.д.), то я бы рекомендовал строить систему не как классическую CRM, а как Service Desk + CRM + Телефония в одном портале. Тогда звонок, сообщение из бота, заявка с сайта и дальнейшие технические работы будут проходить через одну карточку клиента и один жизненный цикл обращения. Это существенно упрощает контроль для руководителя и ускоряет работу инженеров и техников.
**6. Что должен видеть руководитель на главной странице**
Блок KPI
```
Новые лиды: 12
Сегодня обработано: 9
Конверсия: 34%
Средний чек: 280 000 ₽
Активные проекты: 18
```
Воронка
```
Новые лиды 100
Квалификация 65
КП отправлено 40
Переговоры 22
Сделка 11
```