diff --git a/backend/communications/migrations/0014_remove_messengermessage_sender_account_and_more.py b/backend/communications/migrations/0014_remove_messengermessage_sender_account_and_more.py new file mode 100644 index 00000000..13c6c1dd --- /dev/null +++ b/backend/communications/migrations/0014_remove_messengermessage_sender_account_and_more.py @@ -0,0 +1,98 @@ +# Generated by Django 5.2.4 on 2025-11-30 07:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('communications', '0013_messengerintegration_messengeraccount_and_more'), + ] + + operations = [ + # Сначала удаляем индексы, которые зависят от полей + migrations.RemoveIndex( + model_name='messengermessage', + name='communicati_integra_af291e_idx', + ), + migrations.RemoveIndex( + model_name='messengermessage', + name='communicati_sender__496a25_idx', + ), + migrations.RemoveIndex( + model_name='messengermessage', + name='communicati_status_9d880b_idx', + ), + migrations.RemoveIndex( + model_name='messengermessage', + name='communicati_synced__4496a8_idx', + ), + # Удаляем unique_together + migrations.AlterUniqueTogether( + name='messengermessage', + unique_together=None, + ), + # Теперь удаляем поля + migrations.RemoveField( + model_name='messengermessage', + name='sender_account', + ), + migrations.RemoveField( + model_name='messengermessage', + name='recipient_account', + ), + migrations.RemoveField( + model_name='messengermessage', + name='integration', + ), + migrations.RemoveField( + model_name='messengermessage', + name='internal_message', + ), + # Удаляем индексы MessengerAccount + migrations.RemoveIndex( + model_name='messengeraccount', + name='communicati_user_id_acfa5e_idx', + ), + migrations.RemoveIndex( + model_name='messengeraccount', + name='communicati_externa_2dfbcf_idx', + ), + migrations.RemoveIndex( + model_name='messengeraccount', + name='communicati_phone_n_8df544_idx', + ), + # Удаляем unique_together MessengerAccount + migrations.AlterUniqueTogether( + name='messengeraccount', + unique_together=None, + ), + # Удаляем поля MessengerAccount + migrations.RemoveField( + model_name='messengeraccount', + name='user', + ), + migrations.RemoveField( + model_name='messengeraccount', + name='integration', + ), + # Удаляем индексы MessengerIntegration + migrations.RemoveIndex( + model_name='messengerintegration', + name='communicati_messeng_c8457c_idx', + ), + migrations.RemoveIndex( + model_name='messengerintegration', + name='communicati_status_7fb887_idx', + ), + # Теперь можно удалить модели + migrations.DeleteModel( + name='MessengerMessage', + ), + migrations.DeleteModel( + name='MessengerAccount', + ), + migrations.DeleteModel( + name='MessengerIntegration', + ), + ] diff --git a/backend/communications/urls.py b/backend/communications/urls.py index adca8746..6caffdc2 100644 --- a/backend/communications/urls.py +++ b/backend/communications/urls.py @@ -22,11 +22,6 @@ urlpatterns = [ name="start_private_chat" ), - # Messenger Views - path("messengers/whatsapp/", views.whatsapp_view, name="whatsapp"), - path("messengers/telegram/", views.telegram_view, name="telegram"), - path("messengers/wechat/", views.wechat_view, name="wechat"), - # API Endpoints # Chat management path("api/chat/create/", api_views.create_chat, name="api_create_chat"), diff --git a/backend/communications/views.py b/backend/communications/views.py index dfe24906..7910d148 100644 --- a/backend/communications/views.py +++ b/backend/communications/views.py @@ -452,38 +452,3 @@ def chat_mark_read(request, pk: int): return JsonResponse( {"ok": True, "last_read_at": int(ts.timestamp() * 1000)} ) - - -# ===== Messenger Views ===== - -@login_required -def whatsapp_view(request): - """WhatsApp интеграция""" - from django.shortcuts import render - return render(request, 'communications/messengers/whatsapp.html', { - 'messenger_name': 'WhatsApp', - 'messenger_icon': 'bi-whatsapp', - 'messenger_color': '#25D366' - }) - - -@login_required -def telegram_view(request): - """Telegram интеграция""" - from django.shortcuts import render - return render(request, 'communications/messengers/telegram.html', { - 'messenger_name': 'Telegram', - 'messenger_icon': 'bi-telegram', - 'messenger_color': '#0088cc' - }) - - -@login_required -def wechat_view(request): - """WeChat интеграция""" - from django.shortcuts import render - return render(request, 'communications/messengers/wechat.html', { - 'messenger_name': 'WeChat', - 'messenger_icon': 'bi-wechat', - 'messenger_color': '#09B83E' - }) diff --git a/backend/employees/signals.py b/backend/employees/signals.py index 59a031b9..429e06ce 100644 --- a/backend/employees/signals.py +++ b/backend/employees/signals.py @@ -1,4 +1,5 @@ # users/signals.py +import os from django.db.models.signals import post_save, pre_save from django.dispatch import receiver from django.utils import timezone @@ -9,7 +10,32 @@ from bots.models import BotSubscriber @receiver(pre_save, sender=Employee) -def unbind_bot_subscriber_on_contact_change(sender, instance: Employee, **kwargs): +def delete_old_avatar_on_change(sender, instance: Employee, **kwargs): + """Удаляем старый файл аватара при замене на новый или при удалении.""" + if instance.pk is None: + # Новый пользователь - нечего удалять + return + + try: + old = sender.objects.get(pk=instance.pk) + except sender.DoesNotExist: + return + + # Если аватар изменился или удален + if old.avatar and old.avatar != instance.avatar: + # Удаляем старый файл с диска + if os.path.isfile(old.avatar.path): + try: + os.remove(old.avatar.path) + except Exception as e: + # Логируем ошибку, но не прерываем сохранение + print(f"Error deleting old avatar: {e}") + + +@receiver(pre_save, sender=Employee) +def unbind_bot_subscriber_on_contact_change( + sender, instance: Employee, **kwargs +): # Игнорируем новых пользователей if instance.pk is None: return diff --git a/backend/static/css/components/chat-list-enhanced.css b/backend/static/css/components/chat-list-enhanced.css index 1ed2c66c..e892b86f 100644 --- a/backend/static/css/components/chat-list-enhanced.css +++ b/backend/static/css/components/chat-list-enhanced.css @@ -187,39 +187,7 @@ color: white; } -/* Мессенджеры - ссылки */ -.chat-type-btn[data-messenger] { - text-decoration: none; - transition: background-color 0.2s ease, transform 0.2s ease; -} - -.chat-type-btn[data-messenger]:hover { - background-color: var(--bs-light) !important; - transform: scale(1.05); -} - -.chat-type-btn[data-messenger] i { - transition: transform 0.2s ease; -} - -.chat-type-btn[data-messenger]:hover i { - transform: scale(1.1); -} - -/* Цвета иконок мессенджеров */ -.chat-type-btn[data-messenger="whatsapp"] i { - color: #25D366 !important; -} - -.chat-type-btn[data-messenger="telegram"] i { - color: #0088cc !important; -} - -.chat-type-btn[data-messenger="wechat"] i { - color: #09B83E !important; -} - -.chat-type-badge { +.chat-type-btn .badge { position: absolute; top: 0.25rem; right: 0.25rem; diff --git a/backend/static/js/components/employeeFormHandler.js b/backend/static/js/components/employeeFormHandler.js index b971d8b7..6265bc6c 100644 --- a/backend/static/js/components/employeeFormHandler.js +++ b/backend/static/js/components/employeeFormHandler.js @@ -177,20 +177,43 @@ export function initEmployeeForm(options = {}) { const skills = collectSkillsPayload(); if (skills) payload.skills_ids = skills; - // avatar (Base64) / clear - const avatarFile = document.getElementById('f_avatar')?.files?.[0] || null; - const clearAvatar = form.querySelector('input[name="clear_avatar"]')?.checked; + // avatar (файл через FormData) + const avatarInput = document.getElementById('avatarInput'); + const avatarFile = avatarInput?.files?.[0] || null; + + let body; + let headers = { + 'X-CSRFToken': form.querySelector('[name="csrfmiddlewaretoken"]')?.value || '' + }; + if (avatarFile) { - try { - const dataUrl = await readFileAsDataURL(avatarFile); - payload.avatar = dataUrl; // Base64ImageField ожидает data URL - } catch (_) {} - } else if (clearAvatar) { - payload.avatar = ''; // Очистка + // Если есть файл - используем FormData + const formData = new FormData(); + formData.append('avatar', avatarFile); + + // Добавляем остальные поля + Object.entries(payload).forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => formData.append(key, v)); + } else { + formData.append(key, value); + } + }); + + if (skills) { + skills.forEach(id => formData.append('skills_ids', id)); + } + + body = formData; + // Не устанавливаем Content-Type для FormData - браузер сам добавит boundary + } else { + // Если файла нет - используем JSON + headers['Content-Type'] = 'application/json'; + body = JSON.stringify(payload); } // Если нечего отправлять — просто обновим UI - if (Object.keys(payload).length === 0) { + if (Object.keys(payload).length === 0 && !avatarFile) { setSubmitting(false); if (window.bootstrap) { try { @@ -201,16 +224,10 @@ export function initEmployeeForm(options = {}) { return; } - const csrftoken = form.querySelector('input[name=csrfmiddlewaretoken]')?.value || ''; const resp = await fetch(endpoint, { method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - 'X-Requested-With': 'XMLHttpRequest', - 'X-CSRFToken': csrftoken - }, - body: JSON.stringify(payload), + headers: headers, + body: body, credentials: 'same-origin' }); diff --git a/backend/templates/communications/chat_list.html b/backend/templates/communications/chat_list.html index dab7239e..cf01099e 100644 --- a/backend/templates/communications/chat_list.html +++ b/backend/templates/communications/chat_list.html @@ -61,23 +61,6 @@ 0 - - {# Разделитель #} -
- - {# Мессенджеры #} - - - - - - - - - diff --git a/backend/templates/communications/messengers/telegram.html b/backend/templates/communications/messengers/telegram.html index a72fc37c..e69de29b 100644 --- a/backend/templates/communications/messengers/telegram.html +++ b/backend/templates/communications/messengers/telegram.html @@ -1,80 +0,0 @@ -{# backend/templates/communications/messengers/telegram.html #} -{% extends "base.html" %} -{% load static %} - -{% block title %}{{ messenger_name }}{% endblock %} - -{% block extra_css %} - - -{% endblock %} - -{% block content %} -Загрузка {{ messenger_name }}...
-Загрузка {{ messenger_name }}...
-Загрузка {{ messenger_name }}...
-