Изменения:
- EmployeeActionViewSet: 240 → 115 строк (-125 строк, -52%)
- _apply_effects(): удалена вся LDAP логика (~140 строк)
Удалено:
- Прямые вызовы DirectoryService
- Обработка LdapSyncState
- Логика перемещения в OU=Dismissed и обратно
- Удаление из департаментов в LDAP
- Все try/except блоки для LDAP ошибок
- Импорты: DirectoryService, DirectoryLdapError, DirectoryServiceError, DirectoryDbError, LdapSyncState, settings, _is_ldap_enabled
Осталось только работа с БД:
- При ACTION_DISMISSED: is_active=False + деактивация EmployeeDepartment
- При других действиях: is_active=True
LDAP синхронизация через существующие сигналы:
- sync_employee_to_ldap_on_save: обрабатывает is_active (активация/деактивация)
- sync_member (signals_department.py): обрабатывает EmployeeDepartment (удаление из отделов)
Функционал сохранен, код стал в 2 раза короче
Изменения:
- VerifyEmailAPIView: 331 → 271 строк (-60 строк)
- Удалена вся логика if ldap_enabled с DirectoryService
- Удалены проверки LdapSyncState и обработка LDAP ошибок
- Активация пользователя теперь через сигнал sync_employee_to_ldap_on_save
- Используется _ldap_changes для передачи данных в сигнал
- Удален импорт DirectoryServiceError
Логика:
- ViewSet устанавливает is_active = True и _ldap_changes
- Сигнал автоматически синхронизирует активацию в LDAP
- Обработка ldap_enabled и ошибок происходит в сигнале
- Код стал в 3 раза короче и понятнее
- Удален метод ui_context (42 строки)
- Все его данные доступны через отдельные endpoints:
- dept → GET /departments/{id}/
- roles → можно добавить /departments/{id}/roles/
- links → GET /departments/{id}/members/
- user_perms → GET /departments/{id}/user-perms/
- choices → UI логика (фронтенд)
Преимущества:
- Кешируемые отдельные endpoints
- Фронт запрашивает только нужное
- Проще тестировать
- Более RESTful
-42 строки (480 → 438)
- Удалены все @transaction.atomic из actions (set_head, set_member_role, add_member, remove_member)
- Удален импорт transaction
- Одиночные операции БД уже атомарны по умолчанию
- Django request уже в транзакции если настроен ATOMIC_REQUESTS
-5 строк (485 → 480)
Методы дублировали стандартную функциональность ModelViewSet:
- create(): serializer.save() делает то же самое
- destroy(): стандартный метод идентичен
-25 строк (510 → 485)
- Создан employees/signals_department.py
- post_save(Department): создание/обновление OU в LDAP
- При created: создает OU, устанавливает description, managedBy
- При update: синхронизирует name, description, head
- post_delete(Department): удаление OU из LDAP
- post_save(EmployeeDepartment): синхронизация членства
- При is_active=True: перемещает пользователя в OU отдела + роль
- При is_active=False: перемещает обратно в Users OU
Подключено в employees/apps.py
- Удалены импорты DirectoryService, DirectoryDepartmentDTO, LDAP errors
- Удален импорт _is_ldap_enabled
- Убраны все блоки if ldap_enabled: ... else: ...
- ViewSet теперь только работает с БД (сигналы синхронизируют LDAP)
Методы упрощены до чистой БД логики:
- partial_update: просто обновляет поля
- set_head: просто устанавливает head + head_appointed_at
- set_member_role: обновляет link.role и RoleAssignment
- add_member: создает/активирует EmployeeDepartment
- remove_member: деактивирует EmployeeDepartment
- create: просто создает Department
- destroy: просто удаляет Department
DepartmentViewSet теперь чистый CRUD: -189 строк (699 → 510)
- Убрана логика создания LDAP пользователей из сигналов
- Сигналы ТОЛЬКО синхронизируют изменения существующих LDAP пользователей
- Создание LDAP пользователей - ответственность RegisterAPIView
- Убран _ldap_password из perform_create (создание через API не создает LDAP пользователей)
- Создан LdapPasswordMixin для endpoint'ов работы с паролями
- perform_create теперь только создает Employee в БД (административное создание)
Архитектурное улучшение: разделение ответственности
- Auth views (RegisterAPIView) - создают LDAP пользователей напрямую
- Сигналы - только синхронизация изменений
- LdapPasswordMixin - для endpoint'ов смены пароля
- Удалён ExternalSystemSyncMixin из EmployeeViewSet
- ViewSet теперь использует временные атрибуты (_ldap_password, _ldap_avatar, _ldap_changes)
- Создан employees/signals_ldap.py с post_save/post_delete handlers
- Сигналы автоматически синхронизируют Employee с LDAP через DirectoryService
- Убран дублирующий блок required_perms_by_action из EmployeeViewSet
- Упрощены perform_create и perform_update (-170 строк)
Архитектурное улучшение: разделение ответственности ViewSet (HTTP) и signals (синхронизация)
- sync_service: заменены read_attrs + modify_user_attrs на LdapUser ORM
для записи employeeNumber при импорте пользователей
- Убран неиспользуемый импорт modify_user_attrs
Добавлена полная поддержка связи Employee ↔ LdapUser через LdapSyncState.
Ключевые изменения:
- orm_services.py теперь автоматически создает/обновляет/удаляет LdapSyncState
- create_user() создает LdapSyncState при указании employee_pk
- update_user() обновляет last_django_modify_ts в LdapSyncState
- delete_user() удаляет LdapSyncState при hard delete
- Добавлен метод get_user_dn_by_employee_pk() для быстрого получения DN
- Улучшен метод get_user_by_employee_pk() - сначала ищет через LdapSyncState
Архитектура связи:
Employee (pk=123, PostgreSQL)
↓ object_pk
LdapSyncState (ldap_dn='CN=...', PostgreSQL)
↓ ldap_dn
LdapUser (dn='CN=...', employee_number='123', LDAP server)
Обновлена документация:
- ORM_INTEGRATION.md теперь объясняет связь моделей
- Добавлена диаграмма архитектуры
- Описаны роли каждой модели
- Объяснено почему нет ForeignKey (разные БД)
Добавлена поддержка django-ldapdb для упрощения работы с LDAP через Django ORM.
Ключевые изменения:
- Установлен django-ldapdb==1.5.1 в requirements.txt
- Созданы LDAP ORM модели (LdapUser, LdapGroup, LdapOrganizationalUnit) в employees/ldap/orm_models.py
- Реализованы ORM сервисы для CRUD операций в employees/ldap/orm_services.py
- Настроен database router в eusrr_backend/db_routers.py
- Добавлена LDAP database конфигурация в settings.py
- Создана документация ORM_INTEGRATION.md
Архитектура:
✅ GET → Employee (PostgreSQL) - быстро
✅ POST/PUT/DELETE → LdapUser (django-ldapdb ORM) - просто
Преимущества:
- Django ORM вместо низкоуровневого ldap3
- Автоматическая валидация через Django fields
- Унифицированный CRUD API
- Меньше кода, больше удобства
Файлы:
+ employees/ldap/orm_models.py - LDAP ORM модели (LdapUser, LdapGroup, LdapOrganizationalUnit)
+ employees/ldap/orm_services.py - ORM сервисы (LdapOrmUserService, LdapOrmGroupService, LdapOrmDepartmentService)
+ employees/ldap/ORM_INTEGRATION.md - документация по интеграции
+ eusrr_backend/db_routers.py - database router для LDAP моделей
~ requirements.txt - добавлен django-ldapdb==1.5.1
~ eusrr_backend/settings.py - настроена LDAP database и router
- Добавлен RETURN_ACTION_MAPPING для vacation/sick_leave → returned_from_leave
- Migration команда теперь создаёт события возврата на date_to + 1 день
- Исправлена конвертация date в datetime с timezone
- Обработка случаев когда основное событие уже существует, но нужно создать возврат
- Статистика показывает количество созданных событий возврата
- Добавлена management команда migrate_requests_to_actions для миграции существующих заявок
- Добавлен conftest.py с фикстурой user_factory для тестов requests_app
- Добавлены 13 тестов для проверки автоматизации создания EmployeeAction
- Покрытие: немедленные/отложенные события, дубликаты, условия срабатывания, маппинги
- Добавлены сигналы в employees/signals.py для автоматизации создания кадровых событий
- Реализованы стратегии: немедленные (transfer, dismissal) и отложенные (vacation, sick_leave)
- Добавлены Celery tasks для отложенного создания событий и авто-возврата
- Настроен periodic task для очистки пропущенных возвратов
- Архитектура: employees (ядро) обрабатывает события из requests_app (модуль)
Backend:
- Полная миграция на ChatMembership для всех типов чатов
- Удалены проверки participants M2M поля
- Добавлена обработка participants при создании чатов
- API методы для создания/редактирования/удаления кадровых событий
Frontend:
- Редизайн страницы пользователя с lucide-react иконами
- Добавлено редактирование профиля (ФИО, контакты, аватар)
- Функциональные кадровые события (создание/редактирование/удаление)
- История кадровых событий с цветовой кодировкой
- Исправлено создание дубликатов чатов (поиск по member_ids)
- Удалены визуальные статусные индикаторы (is_active ≠ online)
- Добавлен статус сотрудника из последнего action
Fixes:
- type 'private' вместо 'direct' для личных чатов
- ChatMembership проверки в user_can_access_chat()
- member_ids в ChatListSerializer для поиска чатов
- Удалена устаревшая документация из backend/docs/
- Актуальные гайды сохранены в backend/docs/guides/ и других подпапках
- Удален migrate_participants_to_memberships.py (уже был создан в communications/management/commands/)
АРХИТЕКТУРНЫЙ РЕФАКТОРИНГ:
✅ Убрано дублирование логики Chat.participants vs ChatMembership.is_active
✅ Теперь единственный источник истины - ChatMembership.is_active
✅ Исправлен баг с изменением ролей (роли не отображались после изменения)
ИЗМЕНЕНИЯ:
Backend (viewsets.py):
- get_queryset: мигрировано на memberships__is_active=True
- create: убрано participants.add, только memberships
- leave/remove_member: только is_active=False + left_at
- add_member: восстанавливает is_active=True если был False
- change_role: явно устанавливает is_active=True + left_at=None
Backend (serializers.py):
- Убрано поле participants из ChatDetailSerializer
- get_participant_details: через memberships.filter(is_active=True)
- get_participant_names: через memberships
Backend (models.py):
- get_participants(): только через ChatMembership.is_active=True
- __str__(): получаем участников через memberships
- participants помечен как устаревший (в комментариях)
Backend (rules.py):
- is_member_of_chat: только ChatMembership.is_active=True
- has_send_messages_permission: убран fallback на participants
Миграции:
- Создана 0038: убирает 'owner' из choices, очищает help_text
- Исправлены зависимости в requests_app/documents/feed 0010/0014/0006
(заменили __latest__ на конкретную версию 0037)
- Создан management command migrate_participants_to_memberships
(перенос данных participants → memberships)
Frontend:
- Добавлено оптимистичное обновление UI при изменении ролей
- Детальное логирование для диагностики
Тесты:
- test_change_role_improved.py: проверка изменения ролей с задержкой
- Обновлен test_chat_roles.py
Удаление устаревшей документации:
- Удалены старые отчеты из docs/ (73 файла)
- Сохранены актуальные руководства
ИСПРАВЛЕННЫЙ БАГ:
До: При изменении роли is_active оставался False → membership не попадал в filter(is_active=True) → исчезал с фронтенда
После: Явно устанавливаем is_active=True при любом действии с членством
Backend:
- Исправлен user_can_access_chat() для поддержки context-based чатов
- Обновлен is_chat_member() для использования get_participants()
- Добавлена проверка has_send_messages_permission (блокирует гостей)
- Исправлен MessageViewSet.get_queryset() для комментариев
- Добавлена поддержка Feed Post resolver в resolve_chat_participants()
- Чаты типа 'comments' исключены из общего списка
- Добавлены permissions для mark_read, messages, messages_around
- Добавлена команда manage_chat_permissions для управления ролями
Frontend:
- Скрыто поле ввода сообщений для гостей (role='guest')
- Добавлена проверка can_send_messages из membership
- Показывается информативная заглушка для пользователей без прав
Tests & Docs:
- Добавлены тесты для guest роли (send_message + reactions)
- Документация по ролям и правам в чатах
- Troubleshooting guide для отладки permissions
Fixes:
- 403 при доступе к чатам-комментариям
- 404 при удалении/редактировании комментариев
- Комментарии в общем списке чатов
- Гости могли отправлять сообщения
- Delete legacy Comment models (feed.Comment, documents.DocumentComment, requests_app.RequestComment)
- Create migration scripts to move data to communications.Message
- Add comments_helpers.py for unified comment operations
- Update API endpoints: /api/v1/posts/{id}/comments/, /api/v1/requests/{id}/comments/
- Fix notifications.0010_drop_old_tables migration with SeparateDatabaseAndState
- Implement real-time comments_count via GenericForeignKey annotations
- Update frontend API client to use new endpoints
- Update JavaScript files for new comment API routes
- Remove deprecated API endpoints and serializers
- Clean up signal handlers and admin registrations
Deployment tested: all migrations apply successfully on fresh database
- Обновлена фикстура department_chat: использует GenericFK и type='channel'
- Обновлен тест test_create_department_chat для новой модели
- Обновлен скрипт check_chat_access.py: удалена логика department
- Добавлена поддержка типов group/channel в диагностике
- Удалены все ссылки на удаленное поле department
- Тип 'department' был полностью удален из communications
- Чаты отделов теперь используют type='channel'
- Исправлено 2 вхождения в create_main_department_chat()
- Добавлен флаг COMMUNICATIONS_AUTO_NOTIFY (по умолчанию True)
- Оба сигнала теперь проверяют флаг перед выполнением
- Улучшена документация: объяснено что сигналы опциональны
- Для standalone использования можно отключить автоуведомления
BREAKING CHANGE: notifications теперь опциональная зависимость.
Проблема:
- communications имел жесткую зависимость от notifications модуля
- from notifications.signals import notify - обязательный импорт
- Нарушало независимость модуля
Решение:
- Импорт notifications обернут в try/except ImportError
- Создана функция _send_notification() - обертка над notify.send()
- Если notifications не установлен - уведомления тихо игнорируются
- Модуль работает БЕЗ notifications (graceful degradation)
Изменения:
- notifications/handlers.py:
* Опциональный импорт: try/except ImportError
* Флаг NOTIFICATIONS_AVAILABLE для проверки доступности
* Функция _send_notification() вместо прямых вызовов
* 4 вызова notify.send() → _send_notification()
Результат:
✅ communications работает БЕЗ notifications модуля
✅ Если notifications установлен - все работает как раньше
✅ Полная независимость для standalone использования
✅ System check: 0 errors
Теперь можно использовать communications как:
1. С notifications - полная функциональность
2. Без notifications - только чаты без уведомлений
3. С собственной системой уведомлений - переопределить _send_notification()
BREAKING CHANGE: Тип чата 'department' полностью удален из модуля.
Для миграции существующих department чатов используйте:
- type='group' вместо type='department'
- context_object (GenericFK) для привязки к объектам
- flags={'is_department': True} для пометки
- COMMUNICATIONS_PARTICIPANT_RESOLVER callback для логики участников
Удалено из:
- models.py: тип 'department' из CHAT_TYPE_CHOICES
- models.py: упоминания Department из комментариев
- api/viewsets.py: логика фильтрации по department (30 строк)
- consumers.py: WebSocket логика для department (19 строк)
- utils.py: проверки доступа для department
- rules.py: is_department_chat → deprecated_is_department_chat (disabled)
- notifications/: обработка department в handlers и config
- signals.py: комментарии обновлены на универсальные
Добавлено:
- migrations/0037: удаление department из choices
- Обновлен help_text для context_content_type (без Department)
- Поддержка type='comments' в utils.py и consumers.py
Результат:
✅ 100% независимость от проекта EUSRR
✅ Нет импортов из employees
✅ Нет упоминаний department (кроме deprecated функции)
✅ Универсальный модуль готов для PyPI
Статистика: 9 файлов, -85/+44 строк
BREAKING CHANGE: Тип 'department' помечен как deprecated.
Изменения:
- Добавлен новый тип чата 'comments' для систем комментариев
- Тип 'department' перемещен в конец списка и помечен как [Legacy]
- Обновлен help_text: рекомендуется использовать 'group' + context_object
- Обновлен __str__() для обработки типа 'comments'
- Создана миграция 0036_add_comments_type_deprecate_department
Универсальность:
- 'comments' + context_object работает с любыми моделями
- Треды, реакции, вложения доступны из коробки
- Real-time обновления через WebSocket
- Автоматические уведомления при упоминаниях и ответах
Рекомендации:
- Для проектно-специфичных чатов используй type='group' + context_object
- Department: type='group' + context_object=department + flags={'is_department': True}
- Comments: type='comments' + context_object=any_model
Документация:
- Создан docs/guides/COMMENTS_USAGE.md с примерами
- Описаны все возможности системы комментариев
- Примеры API, WebSocket, миграции с legacy типов