111 Commits

Author SHA1 Message Date
d96d6633f0 chore: Add documentation and supporting files for bidirectional loading
Added:
- Autoscroll audit and reports
- Date divider analysis and Telegram comparison
- Bidirectional loading tests and results
- Date group manager and utilities
- Test scripts and HTML test pages
- CSS for chat date groups
- IntersectionObserver utilities
2026-01-13 15:02:20 +03:00
d5be62b34c refactor: восстановлены старые стили пузырей сообщений, аватаров и разделителей (pre-redesign)
- Восстановлен мягкий цвет пузырей: color-mix(92%) вместо жесткого var(--bs-primary)
- Аватары: border-radius 9999px + grid layout для идеальных кругов
- Разделители: правильный spacing, font-weight 700, pointer-events, color-mix прозрачность
- Добавлены оптимизации скролла: rootMargin 300px, limit 50 сообщений (Telegram-стиль)
- Добавлены новые модули: messageLoader, scrollManager, chatUtils
- Рефакторинг логики загрузки сообщений для предотвращения дубликатов
- Создана документация по архитектуре и тестированию
2026-01-12 17:21:41 +03:00
2ff0eaa538 fix: исправлены ошибки в системе уведомлений
- Добавлен эндпоинт удаления комментариев к заявкам (DELETE /api/v1/requests/{pk}/comments/{comment_id}/)
- Исправлены references на переменную в сигналах комментариев (comment -> instance)
- Добавлена обработка статуса cancelled для заявок в notification_type_map
- Обновлены процессоры уведомлений в task_base.py (правильные поля моделей)
- Создан скрипт генерации тестовых пользователей (scripts/create_test_users.py)
2026-01-09 16:57:48 +03:00
55bcdafe80 refactor: Move notifications API to api/v1/ for consistency
- Created api/v1/notifications/ module with views.py and urls.py
- Moved API endpoints from notifications/api_views.py to api/v1/notifications/views.py
- Moved URL routing from notifications/api_urls.py to api/v1/notifications/urls.py
- Updated api/urls.py to remove old routing
- Updated api/v1/urls.py to include new notifications API
- Marked old files as DEPRECATED (will be removed in 2 months)
- Added migration documentation

Benefits:
- Unified API structure (employees, documents, notifications all in api/v1/)
- Proper versioning for notifications API
- Easier to maintain and extend
- All API modules follow same pattern

Breaking changes: None (URL paths unchanged, backward compatible)
Migration guide: docs/completed/NOTIFICATIONS_API_MIGRATION.md
2026-01-09 15:13:36 +03:00
e8af9245ed fix: исправлена отправка аватаров через multipart вместо base64 и ошибка photo->avatar
- Изменена отправка аватаров на multipart/form-data вместо JSON+base64
- Исправлено в employee_create, employee_create_modal, employee_edit, employee_edit_me
- Исправлена ошибка 'Employee' object has no attribute 'photo' (user.photo -> user.avatar)
- Убрано сжатие изображений в _file_to_data_uri (функция deprecated)

Fixes: #400 Bad Request при загрузке больших аватаров
2026-01-09 14:14:01 +03:00
8b391cb388 - 2026-01-09 14:12:54 +03:00
42879e0981 feat(communications): Реализация управления уведомлениями чатов и оптимизация
- Добавлен API endpoint для включения/выключения уведомлений чата
- Реализован frontend для управления уведомлениями через меню чата
- Обновлены сигналы для проверки настроек notifications_enabled
- Стандартизирована логика уведомлений для всех типов чатов
- Оптимизированы запросы к БД (1 запрос вместо N для ChatUserSettings)
- Переведены детальные логи с INFO на DEBUG уровень

Файлы:
- backend/api/v1/communications/views.py (новый endpoint)
- backend/api/v1/urls.py (новый маршрут)
- backend/static/js/components/chatMenuActions.js (полная реализация)
- backend/communications/notification_signals.py (проверка настроек + оптимизация)
- backend/notifications/services.py (оптимизация логирования)
2026-01-08 14:39:40 +03:00
66e579b15f debug: add logging to comments API and permissions
Add detailed logging to help debug why comments requests fail with 403
2026-01-05 14:44:50 +03:00
2ab98a6af3 fix: allow request participants to access comments
CommentsPermission now allows request owner, recipients, cc_users and approver
to view and add comments to the request they are part of.
2026-01-05 14:42:33 +03:00
5f75d9df50 fix: add v1/ prefix to notifications API routes 2026-01-05 14:36:04 +03:00
53da23f690 fix: revoke role from LDAP group when removing via_assignment role 2025-12-30 12:31:55 +03:00
4462ca1eec debug: add logging to set_member_role to diagnose 400 error 2025-12-30 12:24:39 +03:00
42111a4526 ПОИСК БАГА НЕСОХРАНЕНИЯ РОЛЕЙ В ЛДАП 2025-12-30 11:22:48 +03:00
5d0572d17a fix(roles): enforce LDAP-first pattern for role operations
When LDAP is enabled:
- create_role: creates LDAP group before DB record
- update_role: renames LDAP group before DB update
- delete_role: deletes LDAP group before DB deletion
- assign_role: adds to LDAP group before creating RoleAssignment
- revoke_role: removes from LDAP group before deactivating assignment

On LDAP error: operation fails, no DB changes
On DB error after LDAP success: rollback LDAP changes

Fixes: roles could bypass LDAP when it was enabled
2025-12-30 11:04:39 +03:00
026bc46816 feat(department-roles): complete LDAP architecture redesign with RoleAssignment model
BREAKING CHANGES:
- Removed OU=Roles from LDAP structure, role groups now created directly in department OU
- Role groups use ROLE_<Name> naming convention (similar to DEP_, POS_)

New features:
- RoleAssignment model: allows assigning roles to ANY employee, not just department members
- New API endpoints: /assign/, /revoke/, /assignments/ on DepartmentRoleViewSet
- Permission system updated: has_dept_perm() checks RoleAssignment first
- Employee profile shows external roles with 'внешняя роль' badge
- Employee list filter includes role-only employees with department_relation field
- Role assignment modal now searches all employees via AJAX

LDAP changes:
- _ensure_role_group(): creates ROLE_<Name> group in department OU
- assign_role(), revoke_role(): manage RoleAssignment + LDAP sync
- rename_role_group(), delete_role_group(): full lifecycle support
- Removed all references to OU=Roles

Migrations:
- 0035: Create RoleAssignment model
- 0036: Data migration from EmployeeDepartment.role

Tests:
- 7 new tests for role assignment (all passing)

Docs:
- 9 detailed reports on architecture, models, API, LDAP, tests
2025-12-30 10:35:34 +03:00
4d810459fe fix: add CanViewRequest permission for recipients and cc_users to view request details 2025-12-29 16:08:15 +03:00
25f84cc026 fix(api): добавлен кастомный класс пагинации с поддержкой page_size 2025-12-29 15:48:19 +03:00
36f756988b fix(requests): улучшены названия фильтров дат и исправлена логика фильтрации 2025-12-29 15:42:00 +03:00
0890d02efc refactor: переделать страницу деталей заявления с нуля со стилями из списка
- Полная переделка шаблона request_detail.html с единообразными стилями
- Новая структура: основная карточка (8 колонок) + боковая панель комментариев (4 колонки)
- Единообразные иконки типов заявлений (calendar-check, capsule, umbrella-fill и т.д.)
- Единообразные бейджи статуса и типа как в списке
- Информационные блоки для автора, описания, получателей, одобрившего
- Форма комментариев с правильным стилем message-field--compact
- Emoji picker с поддержкой dropdown-menu поверх контента
- AJAX обработка отправки и удаления комментариев
- Новый CSS файл request-detail.css с адаптивностью
- Добавлена функция _add_type_display() в views.py для отображения типа

Улучшения:
- Единство дизайна со страницей списка заявлений
- Правильное позиционирование emoji picker (fixed positioning)
- Sticky боковая панель с комментариями
- Форма комментария как в ленте (message-field--compact)
- Кнопки одобрения/отклонения с правильными иконками
2025-12-29 14:27:47 +03:00
a417ddfb8f fix(requests): исправлена отправка уведомлений при создании заявления
ПРОБЛЕМА: Уведомления не отправлялись при создании нового заявления

ПРИЧИНА: Сигнал post_save срабатывал ДО установки ManyToMany связей (recipients, cc_users)

РЕШЕНИЕ:
- Добавлен сигнал m2m_changed для отслеживания установки recipients/cc_users
- При создании заявления оно помечается как ожидающее отправки уведомлений
- После установки recipients через .set() срабатывает m2m_changed и отправляет уведомления
- Используется глобальный set _pending_new_requests для отслеживания

УЛУЧШЕНИЯ:
- Добавлено подробное логирование процесса создания и отправки уведомлений
- Разные эмодзи-иконки для разных типов получателей (� адресовано, � в копии, ✅ на согласование)
- Все типы используют notification_type='request_new' (существующий в БД)

Файлы:
- notification_signals.py: добавлены сигналы m2m_changed
- serializers.py: добавлено логирование создания и установки recipients
2025-12-29 11:56:56 +03:00
087cff7901 fix: исключен approver из получателей уведомлений о решении
- Approver (тот кто принял решение) не получает уведомление о своем действии
- Исключены employee.id и approver_id из recipients при approve/reject
- Упрощен signal create_request_notifications (убран лишний запрос в БД)
- Теперь передается актуальный request_obj с установленным approver_id
2025-12-29 11:02:39 +03:00
58d3d1a111 feat: проверка что только получатель заявки может принять решение
- Добавлен IsRecipientOfRequest permission для API
- Обновлен get_allowed_request_qs: теперь включает заявки где пользователь - получатель
- Добавлена проверка recipients в request_process view
- Staff/superuser могут обрабатывать любые заявки (обход проверки)
2025-12-29 10:36:56 +03:00
de4506ae24 fix 2025-12-18 16:03:26 +03:00
58a7c983ee fix(api): add multiple fallbacks for PhoneNumber conversion in Excel export
Проблема: На production сервере некоторые PhoneNumber объекты не конвертируются
даже через str(), вызывая ValueError в openpyxl.

Решение: Добавлена цепочка fallback'ов:
1. format_number() - нормальное форматирование (+7 928 826-66-50)
2. str() - если format_number() падает
3. repr() - если str() тоже не работает
4. '[invalid phone]' - если вообще ничего не работает

Теперь экспорт гарантированно не упадёт на некорректных телефонах.

Test: Протестировано локально на 8 сотрудниках - все работают.
Production: Должно решить проблему с конкретными записями на сервере.
2025-12-18 15:48:25 +03:00
8f273304c3 fix(api): convert PhoneNumber to string in Excel export
Проблема: ValueError при экспорте - PhoneNumber объект нельзя напрямую записать в Excel ячейку.
Ошибка: Cannot convert PhoneNumber(country_code=7, national_number=...) to Excel

Решение:
- Добавлено форматирование PhoneNumber в международный формат (+7 928 826-66-50)
- Использован phonenumbers.format_number() с INTERNATIONAL форматом
- Добавлен fallback на str() в случае ошибки форматирования

Теперь телефоны корректно экспортируются в читаемом виде.
2025-12-18 15:36:50 +03:00
4da93f80a0 feat(api): add Excel export endpoint for employees
Добавлен GET эндпоинт для экспорта списка сотрудников в Excel формат.

Endpoint: GET /api/v1/employees/export-excel/

Функционал:
- Экспорт всех сотрудников в формат .xlsx
- Применяются все фильтры из queryset (department, position, active и т.д.)
- Поддержка поиска и сортировки из основного API

Колонки в Excel:
- ID, Фамилия, Имя, Отчество
- Email, Телефон
- Должность, Отделы (с ролями)
- Дата рождения, Дата регистрации
- Статус: Активен, Email подтвержден
- Навыки
- Мессенджеры: Telegram, WhatsApp, WeChat

Особенности:
- Стилизованные заголовки (синий фон, белый текст)
- Автоподбор ширины колонок (максимум 50 символов)
- Закрепленная первая строка (заголовки)
- Перенос текста в ячейках
- Имя файла: employees_YYYYMMDD_HHMMSS.xlsx

Зависимости:
- openpyxl==3.1.5 (добавлена в requirements.txt)

Использование:
GET /api/v1/employees/export-excel/
GET /api/v1/employees/export-excel/?department=5
GET /api/v1/employees/export-excel/?active=true&search=Иванов
2025-12-18 15:29:40 +03:00
21b0614c69 fix(ldap): handle dismissal of employees without departments
Проблема: При увольнении сотрудника, не состоящего ни в одном отделе,
он оставался в текущей OU (например, OU=Users) вместо перемещения в OU=Dismissed.

Причина: Цикл по active_departments был пустым, remove_member() не вызывался,
перемещение в OU=Dismissed не происходило.

Решение:
- Добавлена проверка departments_processed
- Если сотрудник не состоял ни в одном отделе, выполняется явное перемещение
- Используется get_base_dn_for_employee() для определения целевой OU
- Прямой вызов _move_user_to_base() для перемещения в OU=Dismissed

Тест: test_dismissal_without_department_moves_to_dismissed
- Проверяет увольнение сотрудника без отделов
- Убеждается, что LDAP соединение создано для перемещения
- Подтверждает деактивацию в LDAP (userAccountControl=514)

Edge case покрыт: Теперь все уволенные попадают в OU=Dismissed независимо
от наличия отделов.
2025-12-18 12:32:09 +03:00
b3ee4f1960 feat(ldap): separate dismissed employees to OU=Dismissed
Implemented automatic movement of dismissed employees to dedicated OU.

Core Changes:
- Configuration: Added LDAP_DISMISSED_BASE setting for dismissed employees OU
- Utility: Created get_base_dn_for_employee() to determine target OU by is_active status
- Service Layer: Modified DepartmentService.remove_member() to use status-based OU selection
- View Logic: Updated EmployeeActionViewSet._apply_effects() to:
  * Deactivate employee BEFORE removing from departments (critical for correct OU routing)
  * Loop through all departments and call remove_member() for each
  * Handle restoration: move from OU=Dismissed back to OU=Users on activation

Infrastructure:
- Management Command: ensure_dismissed_ou.py to create OU=Dismissed in AD
  * Checks OU existence before creation
  * Supports --force flag for recreation
  * Configurable description

Testing:
- test_dismissal_moves_to_dismissed_ou: Verifies dismissal → OU=Dismissed movement
- test_restoration_moves_from_dismissed_to_users: Verifies activation → OU=Users movement

Edge Cases Handled:
- Repeat dismissal: Idempotent (already inactive employees stay in OU=Dismissed)
- Restoration: Automatic move from OU=Dismissed to OU=Users on any activating action
- Missing OU: ensure_container_exists() creates OU=Dismissed if missing (fallback to OU=Users on error)
- LDAP errors: Graceful logging without blocking database operations

Technical Details:
- OU=Dismissed: OU=Dismissed,OU=company,DC=robotail,DC=local
- Dismissal flow: is_active=False → deactivate EmployeeDepartment → update LDAP UAC → remove from all departments
- Restoration flow: is_active=True → update LDAP UAC → check current OU → move if in OU=Dismissed
- Backward compatible: Works with existing dismissals (they will move to OU=Dismissed on next department change)

Files Changed:
- backend/.env: Added LDAP_DISMISSED_BASE configuration
- backend/eusrr_backend/settings.py: Registered LDAP_DISMISSED_BASE setting
- backend/employees/ldap/utils/ldap_utils.py: New get_base_dn_for_employee() utility
- backend/employees/ldap/services/department_service.py: Modified remove_member() logic
- backend/api/v1/employees/views.py: Enhanced dismissal and restoration logic
- backend/employees/management/commands/ensure_dismissed_ou.py: New management command
- backend/tests/api/v1/employees/test_employee_actions.py: Added tests for new behavior
2025-12-18 11:56:51 +03:00
1c9aa9284e feat: sync employee dismissal/activation with LDAP
- Added LDAP synchronization in EmployeeActionViewSet._apply_effects()
- Dismissal sets userAccountControl=514 (disabled) in LDAP
- Other actions set userAccountControl=512 (enabled)
- Graceful error handling: DB succeeds even if LDAP fails
- Works correctly when LDAP is disabled
- Added 5 comprehensive tests for LDAP sync behavior
2025-12-17 08:15:35 +03:00
ba0aaccfd8 fix: prevent QueryDict conversion breaking form data for non-admin users
Problem: Regular users couldn't create requests while admins could
Root cause: to_internal_value() used dict(QueryDict) which converts
all values to lists, breaking validation

Solution:
- For QueryDict: use _mutable flag to modify in-place
- For regular dict: use dict() conversion as before
- Avoids ['value'] instead of 'value' conversion

This fixes 400 errors for non-admin users when creating requests
with multipart/form-data.
2025-12-08 12:41:56 +03:00
895c019592 debug: add more detailed logging for request creation
- Log Content-Type header
- Log POST, FILES, and data keys separately
- Log actual values and types of key fields (type, title, dates, comment)
- This will help identify multipart parsing issues
2025-12-08 12:35:02 +03:00
5d2d0841c8 feat: add detailed logging for request creation errors
Backend (API):
- Added logging of user ID and request data keys before validation
- Log validation errors with full details before raising exception
- Helps diagnose 400 Bad Request errors from users

Frontend (JS):
- Added console logging of all FormData entries before submit
- Improved error message display with field-specific validation errors
- Shows structured error messages instead of generic 'HTTP 400'

This will help identify why some users get 400 errors when creating requests.
2025-12-08 12:29:44 +03:00
cbdb888168 fix: avatar upload with Base64ImageField and ForeignKey handling
- Changed EmployeeSerializer.avatar from ImageField to Base64ImageField
  to properly handle base64-encoded images from employee_edit_me view
- Added conversion of empty strings to None for ForeignKey fields (_id)
  to prevent 'Field expected a number but got empty string' errors
- Improved logging for debugging multipart form processing
2025-12-08 10:32:31 +03:00
7d15369df4 fix: handle empty avatar field in profile update (complete fix)
- Skip empty avatar field in views_front.py before sending to API
- Add validate_avatar() in serializer to convert empty strings to None
- Prevent avatar update when value is None in serializer.update()
- Fix ValidationError when updating profile without changing avatar
2025-12-08 10:15:10 +03:00
834f7cec9e fix: handle empty avatar field in employee profile update
- Add validate_avatar() to ignore empty strings from FormData
- Prevent avatar field update when value is None (no file selected)
- Fix ValidationError 'Загруженный файл не является корректным файлом'
- User can now update profile without changing avatar
2025-12-08 09:58:06 +03:00
0c91d0e544 Fix reg 2025-12-05 16:39:42 +03:00
c364d0635a fix(requests): сохраняем шапку таблицы при ajax загрузке, исправлен порядок, счетчик комментариев, preventDefault для Enter
- Добавлен отдельный контейнер для данных заявлений
- JS работает только с #reqListContent, шапка всегда видна
- Исправлен порядок заявлений (order_by)
- comments_count через annotate
- Исправлен обработчик формы комментария (preventDefault)
- Удаление начального спиннера после загрузки
2025-12-05 15:59:21 +03:00
5d1fd340fb refactor: использование attachment_url из API + AJAX компонент для заявлений
- Шаблоны используют obj.attachment_url напрямую из API
- Удален templatetag absolute_url (больше не нужен)
- Упрощена проверка наличия вложения
- Создан requestListHandler.js для AJAX загрузки с infinite scroll
- Обновлен requestList.js для поддержки AJAX режима
- Подход аналогичен приложению документов
2025-12-05 14:53:52 +03:00
897fdb587a fix: использование абсолютных URL для вложений в заявлениях
- Добавлен templatetag absolute_url для преобразования относительных путей
- Обновлены все шаблоны заявлений: request_list_full, request_detail, request_process
- Теперь все ссылки на вложения используют {% absolute_url %} для полных URL
- Добавлен use_url=True для attachment_url в сериализаторе API
- Исправлена проблема с локальными URL вместо доменных
2025-12-05 14:25:55 +03:00
2aed80c872 fix: использовать FileField вместо SerializerMethodField для attachment_url
- Изменено attachment_url на serializers.FileField (как в DocumentReadSerializer)
- Удален ручной метод get_attachment_url
- DRF автоматически преобразует FileField в абсолютный URL при наличии request в контексте
2025-12-05 14:13:09 +03:00
6e003cf841 fix: исправлена генерация attachment_url для полного URL
- Изменено поле attachment_url на SerializerMethodField
- Добавлен метод get_attachment_url с использованием request.build_absolute_uri()
- Теперь возвращается полный абсолютный URL вместо относительного пути
2025-12-05 14:08:03 +03:00
462ebdb41f fix: добавлено поле attachment_url в RequestReadSerializer
- Добавлен attachment_url для полного URL к прикрепленному файлу
- Исправлен шаблон my_requests.html (удалены лишние теги)
- Теперь API заявлений работает аналогично API документов
2025-12-05 13:42:59 +03:00
32b7aba4dd fix: использование локального адреса для внутренних API запросов (избежание SSL ошибок) 2025-12-05 10:28:23 +03:00
ebaf02362d refactor: убран base64 для изображений, динамический API URL, ImageField в сериализаторах 2025-12-05 10:25:24 +03:00
dfd4462d20 API теперь возвращает полные URL для медиа-файлов
- Обновлены serializers для автоматической генерации полных URL
- image, attachment и avatar теперь возвращаются с доменом
- Упрощены шаблоны - убрана сложная логика проверки URL
- Используется request.build_absolute_uri() для правильного формирования URL
- Медиа корректно работает на любом домене (локальном и глобальном)
2025-12-05 09:57:54 +03:00
c190552821 communications: добавлен API endpoint для создания чата, исправлен JS для использования нового URL, реализована поддержка создания глобальных/групповых/канальных чатов через модал 2025-12-03 15:25:58 +03:00
0df247565e Документы: исправлена инициализация формы, добавлен CSRF-токен для всех CRUD-операций, исправлен рендер docList для пустого списка, улучшена отладка JS 2025-12-03 14:42:26 +03:00
20bd4daade feat(requests): добавлена клиентская валидация формы создания заявления
- RecipientPicker.validate() проверяет наличие получателей/отделов
- RecipientPicker.setError()/clearError() для визуальной индикации ошибок
- Валидация в requestCrudHandler для create и edit
- Проверка: автор не может быть получателем
- Автоматическая очистка ошибки при изменении получателей
- Удалено временное логирование из сериализатора
2025-12-03 07:30:39 +03:00
117100bf9d fix(requests): Исправлена ошибка с employee_departments и добавлены тесты
- Заменено employee_departments на departments_links в notification_signals.py
- Добавлен импорт Department в serializers.py
- Создан комплексный тестовый скрипт test_recipients.py (7 тестов)
- Создан скрипт test_recipients_api.py для API тестирования
- Добавлены утилиты для создания тестовых данных

Результаты тестирования:
✓ 7/7 тестов модели прошли успешно (100%)
✓ Проверены все методы: add_recipient, is_recipient, all_recipients
✓ Проверена работа с ManyToMany полями
✓ Проверена сериализация через API
✓ Проверен флаг sent_to_all_department
2025-12-02 17:39:09 +03:00
5eb9cc90b1 feat(requests): Этап 3 - обновлена логика доступа с поддержкой получателей
- Обновлен get_queryset в RequestViewSet с учетом recipients и cc_users
- Добавлен параметр ?addressed_to_me=true для фильтрации заявок, адресованных текущему пользователю
- Получатели теперь видят заявки через recipients, cc_users или sent_to_all_department
- Департаментные права работают с новым полем departments (ManyToMany)
- Добавлена поддержка видимости по нескольким отделам
2025-12-02 17:15:55 +03:00