From 510619668df8266e00cc6f6e4d363ce344a0102e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sun, 30 Nov 2025 10:31:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=B0=D1=8F=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=84=D1=80=D0=B0=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82?= =?UTF-8?q?=D1=83=D1=80=D0=B0=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BC=D0=B5=D1=81=D1=81=D0=B5=D0=BD=D0=B4?= =?UTF-8?q?=D0=B6=D0=B5=D1=80=D0=BE=D0=B2:=20=D0=BC=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8,=20=D0=BC=D0=B8=D0=B3=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8,=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD-=D0=BF=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=20=D0=B8=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MESSENGER_INTEGRATION_GUIDE.md | 316 ++++++++++++++++++ backend/communications/admin.py | 176 ++++++++++ ...erintegration_messengeraccount_and_more.py | 119 +++++++ backend/communications/models.py | 286 ++++++++++++++++ backend/communications/views.py | 42 ++- .../communications/messengers/telegram.html | 51 ++- .../communications/messengers/wechat.html | 51 ++- .../communications/messengers/whatsapp.html | 49 ++- .../components/employee_form_contact.html | 18 +- .../templates/employees/employee_detail.html | 64 +--- 10 files changed, 1069 insertions(+), 103 deletions(-) create mode 100644 backend/communications/MESSENGER_INTEGRATION_GUIDE.md create mode 100644 backend/communications/migrations/0013_messengerintegration_messengeraccount_and_more.py diff --git a/backend/communications/MESSENGER_INTEGRATION_GUIDE.md b/backend/communications/MESSENGER_INTEGRATION_GUIDE.md new file mode 100644 index 00000000..ed0ee0b6 --- /dev/null +++ b/backend/communications/MESSENGER_INTEGRATION_GUIDE.md @@ -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 ключ**: `` + - **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/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 ключ**: `` + - **API секрет**: `` + - **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. Обратитесь к системному администратору diff --git a/backend/communications/admin.py b/backend/communications/admin.py index 758973a9..e137b1c9 100644 --- a/backend/communications/admin.py +++ b/backend/communications/admin.py @@ -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 = "Содержимое" diff --git a/backend/communications/migrations/0013_messengerintegration_messengeraccount_and_more.py b/backend/communications/migrations/0013_messengerintegration_messengeraccount_and_more.py new file mode 100644 index 00000000..54ad7a29 --- /dev/null +++ b/backend/communications/migrations/0013_messengerintegration_messengeraccount_and_more.py @@ -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')}, + ), + ] diff --git a/backend/communications/models.py b/backend/communications/models.py index dac12dd5..2125b592 100644 --- a/backend/communications/models.py +++ b/backend/communications/models.py @@ -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}" diff --git a/backend/communications/views.py b/backend/communications/views.py index dfe24906..53972864 100644 --- a/backend/communications/views.py +++ b/backend/communications/views.py @@ -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, }) diff --git a/backend/templates/communications/messengers/telegram.html b/backend/templates/communications/messengers/telegram.html index dd5dd798..599056ce 100644 --- a/backend/templates/communications/messengers/telegram.html +++ b/backend/templates/communications/messengers/telegram.html @@ -76,23 +76,58 @@

{{ messenger_name }} интеграция

- Здесь будет отображаться интеграция с {{ messenger_name }}. - Функционал находится в разработке. + {% if integration_status == 'active' %} + Интеграция активна. Вы можете отправлять и получать сообщения через Telegram. + {% elif integration_status == 'inactive' %} + Интеграция не настроена. Обратитесь к администратору для настройки. + {% else %} + Здесь будет отображаться интеграция с {{ messenger_name }}. + Функционал находится в разработке. + {% endif %}

Вернуться к чатам - + {% if is_admin %} + + Настроить интеграцию + + {% else %} + + {% endif %}
-
- Для настройки интеграции обратитесь к администратору -
+ {% if is_admin %} +
+ У вас есть права администратора +
+
+
Инструкции для настройки:
+
    +
  1. Создайте бота через @BotFather в Telegram
  2. +
  3. Получите Bot Token
  4. +
  5. Откройте админ-панель интеграций
  6. +
  7. Создайте новую интеграцию для Telegram
  8. +
  9. Укажите Bot Token в поле API ключ
  10. +
  11. Настройте webhook
  12. +
  13. Активируйте интеграцию
  14. +
+ +
+ {% else %} +
+ Для настройки интеграции обратитесь к администратору +
+ {% endif %}
diff --git a/backend/templates/communications/messengers/wechat.html b/backend/templates/communications/messengers/wechat.html index 67deec32..e05a2407 100644 --- a/backend/templates/communications/messengers/wechat.html +++ b/backend/templates/communications/messengers/wechat.html @@ -76,23 +76,58 @@

{{ messenger_name }} интеграция

- Здесь будет отображаться интеграция с {{ messenger_name }}. - Функционал находится в разработке. + {% if integration_status == 'active' %} + Интеграция активна. Вы можете отправлять и получать сообщения через WeChat. + {% elif integration_status == 'inactive' %} + Интеграция не настроена. Обратитесь к администратору для настройки. + {% else %} + Здесь будет отображаться интеграция с {{ messenger_name }}. + Функционал находится в разработке. + {% endif %}

Вернуться к чатам - + {% if is_admin %} + + Настроить интеграцию + + {% else %} + + {% endif %}
-
- Для настройки интеграции обратитесь к администратору -
+ {% if is_admin %} +
+ У вас есть права администратора +
+
+
Инструкции для настройки:
+
    +
  1. Зарегистрируйте WeChat Official Account
  2. +
  3. Получите AppID и AppSecret
  4. +
  5. Откройте админ-панель интеграций
  6. +
  7. Создайте новую интеграцию для WeChat
  8. +
  9. Укажите AppID и AppSecret
  10. +
  11. Настройте webhook
  12. +
  13. Активируйте интеграцию
  14. +
+ +
+ {% else %} +
+ Для настройки интеграции обратитесь к администратору +
+ {% endif %}
diff --git a/backend/templates/communications/messengers/whatsapp.html b/backend/templates/communications/messengers/whatsapp.html index cc35071b..77bbaf80 100644 --- a/backend/templates/communications/messengers/whatsapp.html +++ b/backend/templates/communications/messengers/whatsapp.html @@ -76,23 +76,56 @@

{{ messenger_name }} интеграция

- Здесь будет отображаться интеграция с {{ messenger_name }}. - Функционал находится в разработке. + {% if integration_status == 'active' %} + Интеграция активна. Вы можете отправлять и получать сообщения через WhatsApp. + {% elif integration_status == 'inactive' %} + Интеграция не настроена. Обратитесь к администратору для настройки. + {% else %} + Здесь будет отображаться интеграция с {{ messenger_name }}. + Функционал находится в разработке. + {% endif %}

Вернуться к чатам - + {% if is_admin %} + + Настроить интеграцию + + {% else %} + + {% endif %}
-
- Для настройки интеграции обратитесь к администратору -
+ {% if is_admin %} +
+ У вас есть права администратора +
+
+
Инструкции для настройки:
+
    +
  1. Получите API ключи от WhatsApp Business API
  2. +
  3. Откройте админ-панель интеграций
  4. +
  5. Создайте новую интеграцию для WhatsApp
  6. +
  7. Укажите API ключ и webhook URL
  8. +
  9. Активируйте интеграцию
  10. +
+ +
+ {% else %} +
+ Для настройки интеграции обратитесь к администратору +
+ {% endif %}
diff --git a/backend/templates/employees/components/employee_form_contact.html b/backend/templates/employees/components/employee_form_contact.html index e8aeb6b5..fb27c979 100644 --- a/backend/templates/employees/components/employee_form_contact.html +++ b/backend/templates/employees/components/employee_form_contact.html @@ -26,36 +26,24 @@
- + -
Для уведомлений (найдите ID через @userinfobot)
- + -
Номер телефона в формате E.164
- + -
Ваш WeChat идентификатор
diff --git a/backend/templates/employees/employee_detail.html b/backend/templates/employees/employee_detail.html index b57197b5..9a610e62 100644 --- a/backend/templates/employees/employee_detail.html +++ b/backend/templates/employees/employee_detail.html @@ -72,40 +72,6 @@ {% endif %} - {# Быстрые действия с мессенджерами #} - {% if emp.telegram or emp.whatsapp or emp.wechat %} -
- {% if emp.telegram %} - - - Telegram - - {% endif %} - - {% if emp.whatsapp %} - - - WhatsApp - - {% endif %} - - {% if emp.wechat %} - - {% endif %} -
- {% endif %} -
@@ -134,33 +100,9 @@ {{ emp.phone_number }}
{% endif %} - - {# Мессенджеры с цветными иконками и ссылками #} - {% if emp.telegram %} - - {% endif %} - - {% if emp.whatsapp %} - - {% endif %} - - {% if emp.wechat %} -
- - WeChat: {{ emp.wechat }} -
- {% endif %} - + {% if emp.telegram %}
{{ emp.telegram }}
{% endif %} + {% if emp.whatsapp %}
{{ emp.whatsapp }}
{% endif %} + {% if emp.wechat %}
{{ emp.wechat }}
{% endif %} {% if not emp.email and not emp.phone_number and not emp.telegram and not emp.whatsapp and not emp.wechat %}
Контакты не указаны
{% endif %}