Добавлена полная инфраструктура интеграции мессенджеров: модели, миграции, админ-панель и документация по настройке

This commit is contained in:
admin i 2025-11-30 10:31:48 +03:00
parent 9243fb1b84
commit 510619668d
10 changed files with 1069 additions and 103 deletions

View File

@ -0,0 +1,316 @@
# Настройка интеграции мессенджеров
Этот документ описывает процесс настройки интеграции с WhatsApp, Telegram и WeChat.
## Архитектура
Система интеграции состоит из трех основных компонентов:
1. **MessengerIntegration** - настройки подключения к мессенджеру
2. **MessengerAccount** - привязка аккаунтов сотрудников
3. **MessengerMessage** - синхронизация сообщений
## Шаги настройки
### 1. Доступ к админ-панели
Перейдите в Django Admin: `http://your-domain/admin/communications/`
Вы увидите три новых раздела:
- Интеграции с мессенджерами
- Аккаунты в мессенджерах
- Сообщения из мессенджеров
### 2. Настройка WhatsApp
#### 2.1. Получение API ключей
**Вариант A: WhatsApp Business API (официальный)**
1. Зарегистрируйтесь на https://business.whatsapp.com/
2. Создайте приложение в Facebook for Developers
3. Получите `WhatsApp Business Account ID` и `Access Token`
**Вариант B: WhatsApp Web API (неофициальный, через Baileys/Whatsapp-web.js)**
1. Используйте библиотеку для Node.js
2. Настройте webhook для получения сообщений
3. Получите QR-код для авторизации
#### 2.2. Настройка в системе
1. Откройте "Интеграции с мессенджерами" → "Добавить"
2. Выберите тип: **WhatsApp**
3. Заполните поля:
- **Включена**: ✓
- **Статус**: Active
- **API ключ**: `<ваш Access Token>`
- **Webhook URL**: `https://your-domain/communications/webhooks/whatsapp/`
4. В разделе "Дополнительные настройки" (JSON):
```json
{
"phone_number_id": "YOUR_PHONE_NUMBER_ID",
"business_account_id": "YOUR_BUSINESS_ACCOUNT_ID",
"verify_token": "YOUR_WEBHOOK_VERIFY_TOKEN"
}
```
#### 2.3. Настройка webhook на стороне WhatsApp
1. В Facebook for Developers → WhatsApp → Configuration
2. Укажите webhook URL: `https://your-domain/communications/webhooks/whatsapp/`
3. Укажите Verify Token (из настроек выше)
4. Подпишитесь на события: `messages`, `message_status`
### 3. Настройка Telegram
#### 3.1. Создание бота
1. Найдите @BotFather в Telegram
2. Отправьте команду `/newbot`
3. Следуйте инструкциям для создания бота
4. Получите **Bot Token** (формат: `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`)
#### 3.2. Настройка в системе
1. Откройте "Интеграции с мессенджерами" → "Добавить"
2. Выберите тип: **Telegram**
3. Заполните поля:
- **Включена**: ✓
- **Статус**: Active
- **API ключ**: `<Bot Token от BotFather>`
- **Webhook URL**: `https://your-domain/communications/webhooks/telegram/`
4. В разделе "Дополнительные настройки":
```json
{
"bot_username": "your_bot_name",
"allowed_updates": ["message", "edited_message", "callback_query"]
}
```
#### 3.3. Установка webhook
Выполните запрос (замените токен):
```bash
curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook" \
-H "Content-Type: application/json" \
-d '{"url": "https://your-domain/communications/webhooks/telegram/"}'
```
### 4. Настройка WeChat
#### 4.1. Регистрация WeChat Official Account
1. Зарегистрируйтесь на https://mp.weixin.qq.com/
2. Создайте Official Account
3. Получите `AppID` и `AppSecret`
#### 4.2. Настройка в системе
1. Откройте "Интеграции с мессенджерами" → "Добавить"
2. Выберите тип: **WeChat**
3. Заполните поля:
- **Включена**: ✓
- **Статус**: Active
- **API ключ**: `<AppID>`
- **API секрет**: `<AppSecret>`
- **Webhook URL**: `https://your-domain/communications/webhooks/wechat/`
4. В разделе "Дополнительные настройки":
```json
{
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY"
}
```
## Привязка аккаунтов сотрудников
После настройки интеграции нужно привязать аккаунты сотрудников:
### Автоматическая привязка
Сотрудники могут сами привязать аккаунты:
1. Перейти в профиль → "Мессенджеры"
2. Выбрать мессенджер
3. Следовать инструкциям (QR-код, код подтверждения и т.д.)
### Ручная привязка (админ)
1. Откройте "Аккаунты в мессенджерах" → "Добавить"
2. Выберите пользователя
3. Выберите интеграцию
4. Укажите:
- **External ID**: ID пользователя в мессенджере
- **External Username**: Имя/никнейм
- **Phone Number**: Телефон (для WhatsApp)
5. Установите галочку "Подтвержден"
## Синхронизация сообщений
### Настройка Celery задач
Добавьте в `celery.py`:
```python
from celery.schedules import crontab
app.conf.beat_schedule = {
'sync-messenger-messages': {
'task': 'communications.tasks.sync_messenger_messages',
'schedule': 60.0, # каждую минуту
},
}
```
### Ручная синхронизация
Через Django shell:
```python
from communications.services.messenger_sync import MessengerSyncService
# Синхронизация всех мессенджеров
sync_service = MessengerSyncService()
sync_service.sync_all()
# Синхронизация конкретного мессенджера
sync_service.sync_messenger('whatsapp')
```
## Webhook эндпоинты
Система автоматически создает эндпоинты для получения входящих сообщений:
- **WhatsApp**: `POST /communications/webhooks/whatsapp/`
- **Telegram**: `POST /communications/webhooks/telegram/`
- **WeChat**: `POST /communications/webhooks/wechat/`
Эти URL должны быть доступны извне (HTTPS обязателен для production).
## Безопасность
### Шифрование API ключей
В production используйте `django-cryptography` для шифрования:
```python
# settings.py
INSTALLED_APPS = [
...
'django_cryptography',
]
# Генерация ключа
from cryptography.fernet import Fernet
FIELD_ENCRYPTION_KEY = Fernet.generate_key().decode()
```
Обновите модель:
```python
from django_cryptography.fields import encrypt
class MessengerIntegration(models.Model):
api_key = encrypt(models.CharField(max_length=500))
api_secret = encrypt(models.CharField(max_length=500))
```
### Проверка подписи webhook
Всегда проверяйте подпись входящих запросов:
```python
# views.py
def verify_whatsapp_signature(request):
signature = request.headers.get('X-Hub-Signature-256')
# Проверка HMAC
...
```
## Мониторинг
### Просмотр статистики
В админ-панели "Интеграции с мессенджерами" отображается:
- Количество отправленных сообщений
- Количество полученных сообщений
- Время последней синхронизации
- Последняя ошибка (если есть)
### Логирование
Все операции логируются:
```python
import logging
logger = logging.getLogger('communications.messenger')
# В settings.py
LOGGING = {
'loggers': {
'communications.messenger': {
'level': 'INFO',
'handlers': ['file'],
},
},
}
```
## Troubleshooting
### Проблема: Webhook не получает сообщения
1. Проверьте, что URL доступен извне:
```bash
curl https://your-domain/communications/webhooks/telegram/
```
2. Проверьте SSL сертификат (должен быть валидным)
3. Проверьте логи Django:
```bash
tail -f logs/django.log | grep webhook
```
### Проблема: Сообщения не синхронизируются
1. Проверьте статус интеграции (должен быть "Active")
2. Проверьте поле "Последняя ошибка"
3. Запустите синхронизацию вручную через shell
4. Проверьте Celery tasks (если используется)
### Проблема: Неверный API ключ
1. Перегенерируйте ключ в соответствующем сервисе
2. Обновите в админ-панели
3. Измените статус на "Active" после обновления
## Дальнейшее развитие
### Планируемые функции
- [ ] Групповые чаты в мессенджерах
- [ ] Отправка медиа-файлов
- [ ] Голосовые сообщения
- [ ] Статусы доставки
- [ ] Typing indicators
- [ ] Реакции на сообщения
- [ ] Шаблоны сообщений для WhatsApp Business
### API endpoints для разработчиков
```
POST /api/v1/messenger/send/
GET /api/v1/messenger/accounts/
GET /api/v1/messenger/messages/
POST /api/v1/messenger/link-account/
```
Документация API: `https://your-domain/api/docs/`
## Поддержка
При возникновении проблем:
1. Проверьте документацию мессенджера
2. Проверьте логи системы
3. Обратитесь к системному администратору

View File

@ -8,6 +8,9 @@ from .models import (
Message,
MessageAttachment,
MessageReply,
MessengerIntegration,
MessengerAccount,
MessengerMessage,
)
@ -169,3 +172,176 @@ class CrossChatMessageAdmin(admin.ModelAdmin):
list_filter = ("status", "requires_moderation", "sent_at")
search_fields = ("sender__last_name", "target_chat__name")
ordering = ("-sent_at",)
# ===== АДМИН-ПАНЕЛЬ ДЛЯ ИНТЕГРАЦИЙ С МЕССЕНДЖЕРАМИ =====
@admin.register(MessengerIntegration)
class MessengerIntegrationAdmin(admin.ModelAdmin):
list_display = (
"messenger_type",
"is_enabled",
"status",
"messages_sent",
"messages_received",
"last_sync_at",
"updated_at"
)
list_filter = ("messenger_type", "is_enabled", "status")
search_fields = ("messenger_type",)
ordering = ("messenger_type",)
fieldsets = (
("Основная информация", {
"fields": ("messenger_type", "is_enabled", "status")
}),
("API Настройки", {
"fields": ("api_key", "api_secret", "webhook_url", "settings"),
"classes": ("collapse",)
}),
("Статистика", {
"fields": (
"messages_sent",
"messages_received",
"last_sync_at",
"last_error"
),
"classes": ("collapse",)
}),
("Системная информация", {
"fields": ("created_at", "updated_at"),
"classes": ("collapse",)
}),
)
readonly_fields = (
"messages_sent",
"messages_received",
"last_sync_at",
"created_at",
"updated_at"
)
def get_readonly_fields(self, request, obj=None):
if obj and obj.status == 'active':
return self.readonly_fields + ("messenger_type",)
return self.readonly_fields
@admin.register(MessengerAccount)
class MessengerAccountAdmin(admin.ModelAdmin):
list_display = (
"user",
"integration",
"external_username",
"phone_number",
"is_verified",
"is_active",
"updated_at"
)
list_filter = (
"integration__messenger_type",
"is_verified",
"is_active"
)
search_fields = (
"user__last_name",
"user__first_name",
"external_username",
"phone_number",
"external_id"
)
ordering = ("-created_at",)
fieldsets = (
("Пользователь", {
"fields": ("user", "integration")
}),
("Данные в мессенджере", {
"fields": (
"external_id",
"external_username",
"phone_number"
)
}),
("Статус", {
"fields": ("is_verified", "is_active")
}),
("Дополнительно", {
"fields": ("metadata",),
"classes": ("collapse",)
}),
("Системная информация", {
"fields": ("created_at", "updated_at"),
"classes": ("collapse",)
}),
)
readonly_fields = ("created_at", "updated_at")
@admin.register(MessengerMessage)
class MessengerMessageAdmin(admin.ModelAdmin):
list_display = (
"id",
"integration",
"direction",
"status",
"sender_account",
"content_preview",
"external_timestamp",
"synced_at"
)
list_filter = (
"integration__messenger_type",
"direction",
"status",
"external_timestamp"
)
search_fields = (
"external_id",
"content",
"sender_account__external_username"
)
ordering = ("-external_timestamp",)
fieldsets = (
("Основная информация", {
"fields": (
"integration",
"internal_message",
"external_id"
)
}),
("Участники", {
"fields": (
"sender_account",
"recipient_account",
"direction"
)
}),
("Содержимое", {
"fields": ("content", "attachments")
}),
("Статус и время", {
"fields": (
"status",
"external_timestamp",
"synced_at"
)
}),
("Дополнительно", {
"fields": ("raw_data",),
"classes": ("collapse",)
}),
)
readonly_fields = ("synced_at",)
def content_preview(self, obj):
"""Превью содержимого сообщения"""
if len(obj.content) > 50:
return obj.content[:50] + "..."
return obj.content
content_preview.short_description = "Содержимое"

View File

@ -0,0 +1,119 @@
# Generated by Django 5.2.4 on 2025-11-30 07:28
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('communications', '0012_chatmembership_chatreadstate_is_typing_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='MessengerIntegration',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('messenger_type', models.CharField(choices=[('whatsapp', 'WhatsApp'), ('telegram', 'Telegram'), ('wechat', 'WeChat')], max_length=20, unique=True, verbose_name='Тип мессенджера')),
('is_enabled', models.BooleanField(default=False, verbose_name='Включена')),
('status', models.CharField(choices=[('inactive', 'Не активна'), ('active', 'Активна'), ('error', 'Ошибка')], default='inactive', max_length=20, verbose_name='Статус')),
('api_key', models.CharField(blank=True, max_length=500, verbose_name='API ключ')),
('api_secret', models.CharField(blank=True, max_length=500, verbose_name='API секрет')),
('webhook_url', models.URLField(blank=True, max_length=500, verbose_name='Webhook URL')),
('settings', models.JSONField(blank=True, default=dict, verbose_name='Дополнительные настройки')),
('last_sync_at', models.DateTimeField(blank=True, null=True, verbose_name='Последняя синхронизация')),
('messages_sent', models.IntegerField(default=0, verbose_name='Отправлено сообщений')),
('messages_received', models.IntegerField(default=0, verbose_name='Получено сообщений')),
('last_error', models.TextField(blank=True, verbose_name='Последняя ошибка')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')),
],
options={
'verbose_name': 'Интеграция с мессенджером',
'verbose_name_plural': 'Интеграции с мессенджерами',
'indexes': [models.Index(fields=['messenger_type', 'is_enabled'], name='communicati_messeng_c8457c_idx'), models.Index(fields=['status'], name='communicati_status_7fb887_idx')],
},
),
migrations.CreateModel(
name='MessengerAccount',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('external_id', models.CharField(max_length=200, verbose_name='ID в мессенджере')),
('external_username', models.CharField(blank=True, max_length=200, verbose_name='Имя в мессенджере')),
('phone_number', models.CharField(blank=True, max_length=20, verbose_name='Номер телефона')),
('is_verified', models.BooleanField(default=False, verbose_name='Подтвержден')),
('is_active', models.BooleanField(default=True, verbose_name='Активен')),
('metadata', models.JSONField(blank=True, default=dict, verbose_name='Метаданные')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messenger_accounts', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
('integration', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to='communications.messengerintegration', verbose_name='Интеграция')),
],
options={
'verbose_name': 'Аккаунт в мессенджере',
'verbose_name_plural': 'Аккаунты в мессенджерах',
},
),
migrations.CreateModel(
name='MessengerMessage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('external_id', models.CharField(max_length=200, verbose_name='ID в мессенджере')),
('direction', models.CharField(choices=[('incoming', 'Входящее'), ('outgoing', 'Исходящее')], max_length=10, verbose_name='Направление')),
('status', models.CharField(choices=[('pending', 'В ожидании'), ('sent', 'Отправлено'), ('delivered', 'Доставлено'), ('read', 'Прочитано'), ('failed', 'Ошибка')], default='pending', max_length=20, verbose_name='Статус')),
('content', models.TextField(verbose_name='Текст')),
('attachments', models.JSONField(blank=True, default=list, verbose_name='Вложения')),
('external_timestamp', models.DateTimeField(verbose_name='Время в мессенджере')),
('synced_at', models.DateTimeField(auto_now_add=True, verbose_name='Синхронизировано')),
('raw_data', models.JSONField(blank=True, default=dict, verbose_name='Исходные данные')),
('integration', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messenger_messages', to='communications.messengerintegration', verbose_name='Интеграция')),
('internal_message', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='messenger_sync', to='communications.message', verbose_name='Внутреннее сообщение')),
('recipient_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='received_messages', to='communications.messengeraccount', verbose_name='Получатель')),
('sender_account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sent_messages', to='communications.messengeraccount', verbose_name='Отправитель')),
],
options={
'verbose_name': 'Сообщение из мессенджера',
'verbose_name_plural': 'Сообщения из мессенджеров',
'ordering': ['-external_timestamp'],
},
),
migrations.AddIndex(
model_name='messengeraccount',
index=models.Index(fields=['user', 'integration'], name='communicati_user_id_acfa5e_idx'),
),
migrations.AddIndex(
model_name='messengeraccount',
index=models.Index(fields=['external_id'], name='communicati_externa_2dfbcf_idx'),
),
migrations.AddIndex(
model_name='messengeraccount',
index=models.Index(fields=['phone_number'], name='communicati_phone_n_8df544_idx'),
),
migrations.AlterUniqueTogether(
name='messengeraccount',
unique_together={('integration', 'external_id')},
),
migrations.AddIndex(
model_name='messengermessage',
index=models.Index(fields=['integration', 'external_timestamp'], name='communicati_integra_af291e_idx'),
),
migrations.AddIndex(
model_name='messengermessage',
index=models.Index(fields=['sender_account', 'external_timestamp'], name='communicati_sender__496a25_idx'),
),
migrations.AddIndex(
model_name='messengermessage',
index=models.Index(fields=['status'], name='communicati_status_9d880b_idx'),
),
migrations.AddIndex(
model_name='messengermessage',
index=models.Index(fields=['-synced_at'], name='communicati_synced__4496a8_idx'),
),
migrations.AlterUniqueTogether(
name='messengermessage',
unique_together={('integration', 'external_id')},
),
]

View File

@ -850,3 +850,289 @@ class ChatMembership(models.Model):
def __str__(self):
return f"{self.user} в {self.chat} ({self.get_role_display()})"
# ===== МОДЕЛИ ДЛЯ ИНТЕГРАЦИИ МЕССЕНДЖЕРОВ =====
class MessengerIntegration(models.Model):
"""
Настройки интеграции с внешними мессенджерами (WhatsApp, Telegram, WeChat)
"""
MESSENGER_TYPES = [
('whatsapp', 'WhatsApp'),
('telegram', 'Telegram'),
('wechat', 'WeChat'),
]
STATUS_CHOICES = [
('inactive', 'Не активна'),
('active', 'Активна'),
('error', 'Ошибка'),
]
messenger_type = models.CharField(
max_length=20,
choices=MESSENGER_TYPES,
unique=True,
verbose_name="Тип мессенджера"
)
is_enabled = models.BooleanField(
default=False,
verbose_name="Включена"
)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='inactive',
verbose_name="Статус"
)
# API настройки (зашифрованы в production)
api_key = models.CharField(
max_length=500,
blank=True,
verbose_name="API ключ"
)
api_secret = models.CharField(
max_length=500,
blank=True,
verbose_name="API секрет"
)
webhook_url = models.URLField(
max_length=500,
blank=True,
verbose_name="Webhook URL"
)
# Дополнительные настройки (JSON)
settings = models.JSONField(
default=dict,
blank=True,
verbose_name="Дополнительные настройки"
)
# Статистика
last_sync_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="Последняя синхронизация"
)
messages_sent = models.IntegerField(
default=0,
verbose_name="Отправлено сообщений"
)
messages_received = models.IntegerField(
default=0,
verbose_name="Получено сообщений"
)
last_error = models.TextField(
blank=True,
verbose_name="Последняя ошибка"
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создано")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Обновлено")
class Meta:
verbose_name = "Интеграция с мессенджером"
verbose_name_plural = "Интеграции с мессенджерами"
indexes = [
models.Index(fields=['messenger_type', 'is_enabled']),
models.Index(fields=['status']),
]
def __str__(self):
return f"{self.get_messenger_type_display()} - {self.get_status_display()}"
class MessengerAccount(models.Model):
"""
Привязка аккаунта сотрудника к мессенджеру
"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='messenger_accounts',
verbose_name="Пользователь"
)
integration = models.ForeignKey(
MessengerIntegration,
on_delete=models.CASCADE,
related_name='accounts',
verbose_name="Интеграция"
)
# ID пользователя в мессенджере
external_id = models.CharField(
max_length=200,
verbose_name="ID в мессенджере"
)
# Имя/никнейм в мессенджере
external_username = models.CharField(
max_length=200,
blank=True,
verbose_name="Имя в мессенджере"
)
# Телефон (для WhatsApp)
phone_number = models.CharField(
max_length=20,
blank=True,
verbose_name="Номер телефона"
)
is_verified = models.BooleanField(
default=False,
verbose_name="Подтвержден"
)
is_active = models.BooleanField(
default=True,
verbose_name="Активен"
)
# Дополнительные данные
metadata = models.JSONField(
default=dict,
blank=True,
verbose_name="Метаданные"
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создано")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Обновлено")
class Meta:
verbose_name = "Аккаунт в мессенджере"
verbose_name_plural = "Аккаунты в мессенджерах"
unique_together = [('integration', 'external_id')]
indexes = [
models.Index(fields=['user', 'integration']),
models.Index(fields=['external_id']),
models.Index(fields=['phone_number']),
]
def __str__(self):
return f"{self.user} - {self.integration.get_messenger_type_display()} ({self.external_username or self.external_id})"
class MessengerMessage(models.Model):
"""
Синхронизированные сообщения из внешних мессенджеров
"""
DIRECTION_CHOICES = [
('incoming', 'Входящее'),
('outgoing', 'Исходящее'),
]
STATUS_CHOICES = [
('pending', 'В ожидании'),
('sent', 'Отправлено'),
('delivered', 'Доставлено'),
('read', 'Прочитано'),
('failed', 'Ошибка'),
]
integration = models.ForeignKey(
MessengerIntegration,
on_delete=models.CASCADE,
related_name='messenger_messages',
verbose_name="Интеграция"
)
# Связь с внутренним сообщением (если синхронизировано)
internal_message = models.OneToOneField(
Message,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='messenger_sync',
verbose_name="Внутреннее сообщение"
)
# ID сообщения во внешнем мессенджере
external_id = models.CharField(
max_length=200,
verbose_name="ID в мессенджере"
)
# Отправитель/получатель
sender_account = models.ForeignKey(
MessengerAccount,
on_delete=models.SET_NULL,
null=True,
related_name='sent_messages',
verbose_name="Отправитель"
)
recipient_account = models.ForeignKey(
MessengerAccount,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='received_messages',
verbose_name="Получатель"
)
direction = models.CharField(
max_length=10,
choices=DIRECTION_CHOICES,
verbose_name="Направление"
)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='pending',
verbose_name="Статус"
)
# Содержимое
content = models.TextField(verbose_name="Текст")
# Вложения (JSON с URLs)
attachments = models.JSONField(
default=list,
blank=True,
verbose_name="Вложения"
)
# Временные метки
external_timestamp = models.DateTimeField(
verbose_name="Время в мессенджере"
)
synced_at = models.DateTimeField(
auto_now_add=True,
verbose_name="Синхронизировано"
)
# Дополнительные данные из мессенджера
raw_data = models.JSONField(
default=dict,
blank=True,
verbose_name="Исходные данные"
)
class Meta:
verbose_name = "Сообщение из мессенджера"
verbose_name_plural = "Сообщения из мессенджеров"
unique_together = [('integration', 'external_id')]
indexes = [
models.Index(fields=['integration', 'external_timestamp']),
models.Index(fields=['sender_account', 'external_timestamp']),
models.Index(fields=['status']),
models.Index(fields=['-synced_at']),
]
ordering = ['-external_timestamp']
def __str__(self):
return f"{self.integration.get_messenger_type_display()} - {self.external_id}"

View File

@ -460,10 +460,22 @@ def chat_mark_read(request, pk: int):
def whatsapp_view(request):
"""WhatsApp интеграция"""
from django.shortcuts import render
from .models import MessengerIntegration
try:
integration = MessengerIntegration.objects.get(
messenger_type='whatsapp'
)
integration_status = integration.status if integration.is_enabled else 'inactive'
except MessengerIntegration.DoesNotExist:
integration_status = 'not_configured'
return render(request, 'communications/messengers/whatsapp.html', {
'messenger_name': 'WhatsApp',
'messenger_icon': 'bi-whatsapp',
'messenger_color': '#25D366'
'messenger_color': '#25D366',
'integration_status': integration_status,
'is_admin': request.user.is_staff or request.user.is_superuser,
})
@ -471,10 +483,22 @@ def whatsapp_view(request):
def telegram_view(request):
"""Telegram интеграция"""
from django.shortcuts import render
from .models import MessengerIntegration
try:
integration = MessengerIntegration.objects.get(
messenger_type='telegram'
)
integration_status = integration.status if integration.is_enabled else 'inactive'
except MessengerIntegration.DoesNotExist:
integration_status = 'not_configured'
return render(request, 'communications/messengers/telegram.html', {
'messenger_name': 'Telegram',
'messenger_icon': 'bi-telegram',
'messenger_color': '#0088cc'
'messenger_color': '#0088cc',
'integration_status': integration_status,
'is_admin': request.user.is_staff or request.user.is_superuser,
})
@ -482,8 +506,20 @@ def telegram_view(request):
def wechat_view(request):
"""WeChat интеграция"""
from django.shortcuts import render
from .models import MessengerIntegration
try:
integration = MessengerIntegration.objects.get(
messenger_type='wechat'
)
integration_status = integration.status if integration.is_enabled else 'inactive'
except MessengerIntegration.DoesNotExist:
integration_status = 'not_configured'
return render(request, 'communications/messengers/wechat.html', {
'messenger_name': 'WeChat',
'messenger_icon': 'bi-wechat',
'messenger_color': '#09B83E'
'messenger_color': '#09B83E',
'integration_status': integration_status,
'is_admin': request.user.is_staff or request.user.is_superuser,
})

View File

@ -76,23 +76,58 @@
<h2 class="messenger-title">{{ messenger_name }} интеграция</h2>
<p class="messenger-description">
Здесь будет отображаться интеграция с {{ messenger_name }}.
Функционал находится в разработке.
{% if integration_status == 'active' %}
Интеграция активна. Вы можете отправлять и получать сообщения через Telegram.
{% elif integration_status == 'inactive' %}
Интеграция не настроена. Обратитесь к администратору для настройки.
{% else %}
Здесь будет отображаться интеграция с {{ messenger_name }}.
Функционал находится в разработке.
{% endif %}
</p>
<div class="messenger-actions">
<a href="{% url 'communications:chat_list' %}" class="btn btn-outline-secondary messenger-btn">
<i class="bi-arrow-left"></i> Вернуться к чатам
</a>
<button class="btn btn-primary messenger-btn" style="background-color: {{ messenger_color }}; border-color: {{ messenger_color }};" disabled>
<i class="bi-link-45deg"></i> Подключить {{ messenger_name }}
</button>
{% if is_admin %}
<a href="/admin/communications/messengerintegration/" class="btn btn-primary messenger-btn" style="background-color: {{ messenger_color }}; border-color: {{ messenger_color }};">
<i class="bi-gear"></i> Настроить интеграцию
</a>
{% else %}
<button class="btn btn-primary messenger-btn" style="background-color: {{ messenger_color }}; border-color: {{ messenger_color }};" disabled>
<i class="bi-link-45deg"></i> Подключить {{ messenger_name }}
</button>
{% endif %}
</div>
<div class="mt-5">
<div class="alert alert-info d-inline-block">
<i class="bi-info-circle"></i> Для настройки интеграции обратитесь к администратору
</div>
{% if is_admin %}
<div class="alert alert-success d-inline-block">
<i class="bi-shield-check"></i> У вас есть права администратора
</div>
<div class="mt-3">
<h5>Инструкции для настройки:</h5>
<ol class="text-start" style="max-width: 600px; margin: 0 auto;">
<li>Создайте бота через @BotFather в Telegram</li>
<li>Получите Bot Token</li>
<li>Откройте <a href="/admin/communications/messengerintegration/">админ-панель интеграций</a></li>
<li>Создайте новую интеграцию для Telegram</li>
<li>Укажите Bot Token в поле API ключ</li>
<li>Настройте webhook</li>
<li>Активируйте интеграцию</li>
</ol>
<div class="mt-3">
<a href="/static/communications/MESSENGER_INTEGRATION_GUIDE.md" target="_blank" class="btn btn-sm btn-outline-info">
<i class="bi-book"></i> Полная документация
</a>
</div>
</div>
{% else %}
<div class="alert alert-info d-inline-block">
<i class="bi-info-circle"></i> Для настройки интеграции обратитесь к администратору
</div>
{% endif %}
</div>
</div>
</div>

View File

@ -76,23 +76,58 @@
<h2 class="messenger-title">{{ messenger_name }} интеграция</h2>
<p class="messenger-description">
Здесь будет отображаться интеграция с {{ messenger_name }}.
Функционал находится в разработке.
{% if integration_status == 'active' %}
Интеграция активна. Вы можете отправлять и получать сообщения через WeChat.
{% elif integration_status == 'inactive' %}
Интеграция не настроена. Обратитесь к администратору для настройки.
{% else %}
Здесь будет отображаться интеграция с {{ messenger_name }}.
Функционал находится в разработке.
{% endif %}
</p>
<div class="messenger-actions">
<a href="{% url 'communications:chat_list' %}" class="btn btn-outline-secondary messenger-btn">
<i class="bi-arrow-left"></i> Вернуться к чатам
</a>
<button class="btn btn-primary messenger-btn" style="background-color: {{ messenger_color }}; border-color: {{ messenger_color }};" disabled>
<i class="bi-link-45deg"></i> Подключить {{ messenger_name }}
</button>
{% if is_admin %}
<a href="/admin/communications/messengerintegration/" class="btn btn-primary messenger-btn" style="background-color: {{ messenger_color }}; border-color: {{ messenger_color }};">
<i class="bi-gear"></i> Настроить интеграцию
</a>
{% else %}
<button class="btn btn-primary messenger-btn" style="background-color: {{ messenger_color }}; border-color: {{ messenger_color }};" disabled>
<i class="bi-link-45deg"></i> Подключить {{ messenger_name }}
</button>
{% endif %}
</div>
<div class="mt-5">
<div class="alert alert-info d-inline-block">
<i class="bi-info-circle"></i> Для настройки интеграции обратитесь к администратору
</div>
{% if is_admin %}
<div class="alert alert-success d-inline-block">
<i class="bi-shield-check"></i> У вас есть права администратора
</div>
<div class="mt-3">
<h5>Инструкции для настройки:</h5>
<ol class="text-start" style="max-width: 600px; margin: 0 auto;">
<li>Зарегистрируйте WeChat Official Account</li>
<li>Получите AppID и AppSecret</li>
<li>Откройте <a href="/admin/communications/messengerintegration/">админ-панель интеграций</a></li>
<li>Создайте новую интеграцию для WeChat</li>
<li>Укажите AppID и AppSecret</li>
<li>Настройте webhook</li>
<li>Активируйте интеграцию</li>
</ol>
<div class="mt-3">
<a href="/static/communications/MESSENGER_INTEGRATION_GUIDE.md" target="_blank" class="btn btn-sm btn-outline-info">
<i class="bi-book"></i> Полная документация
</a>
</div>
</div>
{% else %}
<div class="alert alert-info d-inline-block">
<i class="bi-info-circle"></i> Для настройки интеграции обратитесь к администратору
</div>
{% endif %}
</div>
</div>
</div>

View File

@ -76,23 +76,56 @@
<h2 class="messenger-title">{{ messenger_name }} интеграция</h2>
<p class="messenger-description">
Здесь будет отображаться интеграция с {{ messenger_name }}.
Функционал находится в разработке.
{% if integration_status == 'active' %}
Интеграция активна. Вы можете отправлять и получать сообщения через WhatsApp.
{% elif integration_status == 'inactive' %}
Интеграция не настроена. Обратитесь к администратору для настройки.
{% else %}
Здесь будет отображаться интеграция с {{ messenger_name }}.
Функционал находится в разработке.
{% endif %}
</p>
<div class="messenger-actions">
<a href="{% url 'communications:chat_list' %}" class="btn btn-outline-secondary messenger-btn">
<i class="bi-arrow-left"></i> Вернуться к чатам
</a>
<button class="btn btn-primary messenger-btn" style="background-color: {{ messenger_color }}; border-color: {{ messenger_color }};" disabled>
<i class="bi-link-45deg"></i> Подключить {{ messenger_name }}
</button>
{% if is_admin %}
<a href="/admin/communications/messengerintegration/" class="btn btn-primary messenger-btn" style="background-color: {{ messenger_color }}; border-color: {{ messenger_color }};">
<i class="bi-gear"></i> Настроить интеграцию
</a>
{% else %}
<button class="btn btn-primary messenger-btn" style="background-color: {{ messenger_color }}; border-color: {{ messenger_color }};" disabled>
<i class="bi-link-45deg"></i> Подключить {{ messenger_name }}
</button>
{% endif %}
</div>
<div class="mt-5">
<div class="alert alert-info d-inline-block">
<i class="bi-info-circle"></i> Для настройки интеграции обратитесь к администратору
</div>
{% if is_admin %}
<div class="alert alert-success d-inline-block">
<i class="bi-shield-check"></i> У вас есть права администратора
</div>
<div class="mt-3">
<h5>Инструкции для настройки:</h5>
<ol class="text-start" style="max-width: 600px; margin: 0 auto;">
<li>Получите API ключи от WhatsApp Business API</li>
<li>Откройте <a href="/admin/communications/messengerintegration/">админ-панель интеграций</a></li>
<li>Создайте новую интеграцию для WhatsApp</li>
<li>Укажите API ключ и webhook URL</li>
<li>Активируйте интеграцию</li>
</ol>
<div class="mt-3">
<a href="/static/communications/MESSENGER_INTEGRATION_GUIDE.md" target="_blank" class="btn btn-sm btn-outline-info">
<i class="bi-book"></i> Полная документация
</a>
</div>
</div>
{% else %}
<div class="alert alert-info d-inline-block">
<i class="bi-info-circle"></i> Для настройки интеграции обратитесь к администратору
</div>
{% endif %}
</div>
</div>
</div>

View File

@ -26,36 +26,24 @@
</div>
<div class="col-sm-4">
<label class="form-label" for="f_telegram">
<i class="bi-telegram text-primary"></i> Telegram
</label>
<label class="form-label" for="f_telegram">Telegram</label>
<input id="f_telegram" class="form-control" name="telegram"
value="{{ emp.telegram|default:'' }}" inputmode="text"
placeholder="@username или chat_id"
data-init="{{ emp.telegram|default:'' }}" data-contact="1">
<div class="form-text small">Для уведомлений (найдите ID через @userinfobot)</div>
<div class="invalid-feedback"></div>
</div>
<div class="col-sm-4">
<label class="form-label" for="f_whatsapp">
<i class="bi-whatsapp" style="color: #25D366;"></i> WhatsApp
</label>
<label class="form-label" for="f_whatsapp">WhatsApp</label>
<input id="f_whatsapp" class="form-control" name="whatsapp"
value="{{ emp.whatsapp|default:'' }}" inputmode="tel"
placeholder="+79001234567"
data-init="{{ emp.whatsapp|default:'' }}" data-contact="1">
<div class="form-text small">Номер телефона в формате E.164</div>
<div class="invalid-feedback"></div>
</div>
<div class="col-sm-4">
<label class="form-label" for="f_wechat">
<i class="bi-wechat" style="color: #09B83E;"></i> WeChat
</label>
<label class="form-label" for="f_wechat">WeChat</label>
<input id="f_wechat" class="form-control" name="wechat"
value="{{ emp.wechat|default:'' }}" inputmode="text"
placeholder="WeChat ID"
data-init="{{ emp.wechat|default:'' }}" data-contact="1">
<div class="form-text small">Ваш WeChat идентификатор</div>
<div class="invalid-feedback"></div>
</div>
</div>

View File

@ -72,40 +72,6 @@
{% endif %}
</div>
{# Быстрые действия с мессенджерами #}
{% if emp.telegram or emp.whatsapp or emp.wechat %}
<div class="mt-3 d-flex gap-2 justify-content-center flex-wrap">
{% if emp.telegram %}
<a href="https://t.me/{{ emp.telegram }}" target="_blank"
class="btn btn-sm btn-outline-primary d-flex align-items-center gap-1"
title="Написать в Telegram">
<i class="bi-telegram"></i>
<span class="d-none d-sm-inline">Telegram</span>
</a>
{% endif %}
{% if emp.whatsapp %}
<a href="https://wa.me/{{ emp.whatsapp }}" target="_blank"
class="btn btn-sm d-flex align-items-center gap-1"
style="background-color: #25D366; color: white; border-color: #25D366;"
title="Написать в WhatsApp">
<i class="bi-whatsapp"></i>
<span class="d-none d-sm-inline">WhatsApp</span>
</a>
{% endif %}
{% if emp.wechat %}
<button class="btn btn-sm d-flex align-items-center gap-1"
style="background-color: #09B83E; color: white; border-color: #09B83E;"
title="WeChat ID: {{ emp.wechat }}"
onclick="navigator.clipboard.writeText('{{ emp.wechat }}'); this.innerHTML='<i class=\'bi-check\'></i> Скопировано'; setTimeout(() => this.innerHTML='<i class=\'bi-wechat\'></i> <span class=\'d-none d-sm-inline\'>WeChat</span>', 2000)">
<i class="bi-wechat"></i>
<span class="d-none d-sm-inline">WeChat</span>
</button>
{% endif %}
</div>
{% endif %}
<!-- Contacts -->
<div class="mt-3 text-start">
<div class="ios-acc">
@ -134,33 +100,9 @@
<a href="tel:{{ emp.phone_number }}">{{ emp.phone_number }}</a>
</div>
{% endif %}
{# Мессенджеры с цветными иконками и ссылками #}
{% if emp.telegram %}
<div class="ios-cell">
<i class="bi-telegram text-primary"></i>
<a href="https://t.me/{{ emp.telegram }}" target="_blank" class="text-decoration-none">
<span>Telegram: {{ emp.telegram }}</span>
</a>
</div>
{% endif %}
{% if emp.whatsapp %}
<div class="ios-cell">
<i class="bi-whatsapp" style="color: #25D366;"></i>
<a href="https://wa.me/{{ emp.whatsapp }}" target="_blank" class="text-decoration-none">
<span>WhatsApp: {{ emp.whatsapp }}</span>
</a>
</div>
{% endif %}
{% if emp.wechat %}
<div class="ios-cell">
<i class="bi-wechat" style="color: #09B83E;"></i>
<span>WeChat: {{ emp.wechat }}</span>
</div>
{% endif %}
{% if emp.telegram %}<div class="ios-cell"><i class="bi-telegram"></i><span>{{ emp.telegram }}</span></div>{% endif %}
{% if emp.whatsapp %}<div class="ios-cell"><i class="bi-whatsapp"></i><span>{{ emp.whatsapp }}</span></div>{% endif %}
{% if emp.wechat %}<div class="ios-cell"><i class="bi-wechat"></i><span>{{ emp.wechat }}</span></div>{% endif %}
{% if not emp.email and not emp.phone_number and not emp.telegram and not emp.whatsapp and not emp.wechat %}
<div class="ios-cell"><span class="text-secondary">Контакты не указаны</span></div>
{% endif %}