129 Commits

Author SHA1 Message Date
281f0b2bbc feat: современное отображение событий календаря с множественным выбором
Реализовано:

1. Множественный выбор календарей через чекбоксы
   - Заменил <select> на чекбоксы для выбора нескольких календарей
   - События создаются одновременно во всех выбранных календарях
   - При редактировании - обновляется только первый выбранный
   - Цветовые индикаторы для каждого календаря

2. Современный дизайн событий
   - Двухцветная схема: левая граница = цвет календаря, фон = цвет события
   - Полупрозрачный фон события (80%) для лучшей видимости
   - Тени и hover эффекты для объёма
   - Закругленные углы (4px)
   - Плавные анимации (transform + transition)

3. Улучшенное отображение накладывающихся событий
   - dayMaxEvents: автоматически показывать '+N еще'
   - dayMaxEventRows: 4 - максимум 4 строки на день
   - moreLinkClick: 'popover' - красивый popover для остальных
   - eventMaxStack: 3 - до 3 событий в стеке
   - eventOrder: сортировка по началу, длительности, алфавиту

4. Новый CSS файл calendar-improvements.css
   - Современные стили для FullCalendar
   - Улучшенный popover с градиентом
   - Стили для выходных дней (розоватый фон)
   - Hover эффекты на события
   - Анимация fadeInEvent при загрузке
   - Улучшенная читаемость текущего дня
   - Адаптивность для мобильных устройств
   - Кастомные scrollbar для списков

5. Передача цвета календаря в события
   - calendar_color добавляется в extendedProps
   - Используется __calendar из интеграции
   - Отдельная обработка eventColor и calendarColor

6. Визуальные улучшения
   - Текст события: font-weight 500, line-height 1.3
   - Отступы: paddingLeft 6px (с цветом календаря)
   - Тень текста для читаемости на любом фоне
   - Паттерн для ежегодных событий (диагональные полосы)
   - Индикатор текущего дня: синий круг с белым текстом

Результат:
✅ События визуально различимы по календарям (левая граница)
✅ Сохранён индивидуальный цвет события (основной фон)
✅ Множество событий в день отображается компактно (+N еще)
✅ Современный Material Design стиль
✅ Плавные анимации и hover эффекты
✅ Улучшенная читаемость и UX
2026-02-11 12:52:25 +03:00
db69efb329 fix: удален дубликат кода в CalendarSubscriptionSerializer
- Удалены повторяющиеся строки в методе validate()
- Fixes: IndentationError: unexpected indent на строке 487
2026-02-11 12:33:06 +03:00
b03ef1308e fix: удален дубликат urlpatterns в api/v1/urls.py
- Удалены повторяющиеся строки с notifications и router.urls
- Исправлена длинная строка с MessageViewSet
- Fixes: IndentationError: unexpected indent
2026-02-11 12:30:49 +03:00
f7d3cd8c91 feat(calendar): add comprehensive test suite for new Calendar API
- Added make_event fixture for creating test events
- Added CalendarSubscription.color_override null=True migration
- Added computed fields (is_personal, is_global, is_department) to CalendarSerializer
- Fixed CalendarViewSet.create() to use CalendarSerializer for response
- Created test_new_calendar_api.py with 29 comprehensive tests:
  * 11 CRUD tests (list, create, retrieve, update, delete)
  * 6 subscription tests (subscribe, unsubscribe, my-subscriptions)
  * 3 event filtering tests (calendar_id filtering, legacy separation)
  * 3 visibility tests (public, private, department)
  * 6 permission tests (is_owner, can_user_view, can_user_edit)

Test results:
- ✅ 34 legacy tests passing (backward compatibility maintained)
- ✅ 29 new tests passing (new functionality validated)
- ✅ Total: 63 tests passing
2026-02-11 08:35:24 +03:00
526ebe6b76 fix(calendar): add calendar_id to serializer fields and fix related_name for departments
- Added calendar_id to CalendarEventWriteSerializer Meta.fields
- Fixed related_name from employee_departments to departments_links
- Updated make_event fixture to support calendar parameter
- Added comprehensive test suite for new Calendar and CalendarSubscription API
- Added fixtures: make_calendar, make_subscription, auth_client, user fixtures
- All legacy tests passing (34 passed)
2026-02-11 08:27:00 +03:00
870a08ee66 feat(calendar): add API layer for optional calendars
Phase 2: API implementation
- Updated CalendarEventWriteSerializer to support calendar_id parameter
- Added CalendarSerializer and CalendarWriteSerializer for Calendar CRUD
- Added CalendarSubscriptionSerializer for subscription management
- Created CalendarViewSet with subscribe/unsubscribe actions
- Created CalendarSubscriptionViewSet for managing subscriptions
- Updated CalendarEventsViewSet to support calendar_id filtering
- Added CalendarManager with get_available_for_user() method
- Added permission methods to Calendar model (is_owner, can_user_view, can_user_edit, can_user_manage)
- Registered new ViewSets in URL routing

API endpoints:
- GET /api/v1/calendar/calendars/ - list available calendars
- POST /api/v1/calendar/calendars/ - create new calendar
- GET /api/v1/calendar/calendars/{id}/ - retrieve calendar details
- PATCH /api/v1/calendar/calendars/{id}/ - update calendar
- DELETE /api/v1/calendar/calendars/{id}/ - delete calendar
- POST /api/v1/calendar/calendars/{id}/subscribe/ - subscribe to calendar
- POST /api/v1/calendar/calendars/{id}/unsubscribe/ - unsubscribe from calendar
- GET /api/v1/calendar/calendars/my-calendars/ - list user's calendars
- GET /api/v1/calendar/subscriptions/ - list user's subscriptions
- POST /api/v1/calendar/subscriptions/ - create subscription
- GET /api/v1/calendar/events/?calendar_id={id} - filter events by calendar

Backward compatibility: all legacy endpoints continue to work
2026-02-11 08:18:47 +03:00
3e4137daa4 feat(ldap): добавлено кэширование LDAP логина с возможностью обновления
- Добавлено поле Employee.username для кэширования LDAP логина
- API endpoint обновлен: поддержка кэша и force_refresh параметра
- Новый дизайн: убран blur, добавлена иконка обновления
- Автоматическая загрузка при инициализации (из кэша или LDAP)
- Визуальная индикация источника данных (cached/fresh)
- Доступно всем пользователям без ограничений
2026-02-06 15:33:48 +03:00
10f7287ae5 fix(employees): добавлено LDAP соединение для LdapRepository
- LdapRepository требует активное соединение в конструкторе
- Добавлен вызов _conn() для получения LDAP соединения
- Исправлена ошибка: TypeError: LdapRepository.__init__() missing 1 required positional argument: 'conn'
- Теперь используется: LdapRepository(_conn())
2026-02-06 14:57:37 +03:00
77238c1154 fix(employees): исправлен доступ к LdapSyncState 2026-02-06 14:54:38 +03:00
17c4e506cf refactor(employees): упрощена логика прав для LDAP endpoint
- Использована встроенная система пермишенов DRF вместо ручной проверки
- Добавлен ldap_info в required_perms_by_action с правом view_ldap_info
- Добавлен ldap_info в get_permissions() для применения IsSelfOrStaff
- Удалена избыточная ручная проверка прав внутри метода ldap_info()
- Проверка прав теперь выполняется автоматически через get_object()
- Пользователь может видеть свой LDAP логин (IsSelfOrStaff)
- Админы и пользователи с view_ldap_info могут видеть любые логины
2026-02-06 14:28:21 +03:00
c6cd2ba20d feat(employees): добавлен функционал отображения LDAP логина в профиле
- Добавлено поле с LDAP логином (sAMAccountName) в профиле сотрудника
- Реализован blur-эффект с раскрытием по клику для безопасности
- Создан API endpoint /api/v1/employees/{id}/ldap-info/ для получения LDAP данных
- Добавлен новый пермишен employees.view_ldap_info
- Стили: SCSS компонент _ldap-login.scss с анимациями и состояниями
- JavaScript: класс LdapLoginField с AJAX запросами и обработкой ошибок
- Доступ только для staff или пользователей с правом view_ldap_info
- Данные запрашиваются из Active Directory on-demand (не хранятся в БД)
2026-02-06 14:24:31 +03:00
da7bf54c6c feat: Реализация полной функциональности опросов в чатах
- Исправлен PollSerializer для создания опросов с опциями
- Добавлено поле read_only_fields для корректной валидации
- Реализована отправка WebSocket уведомлений при создании опроса
- Исправлен импорт serialize_message из communications.serialization
- Добавлен обработчик poll_update в UserConsumer для WebSocket событий
- Исправлен эндпоинт /results/ для возврата user_voted_option_ids
- Добавлено поле voters в PollOptionSerializer для списка проголосовавших
- Реализована инициализация poll виджетов после рендера сообщений
- Добавлено игнорирование poll_update в notification-manager
- Исправлены стили для request list badges (height, word-break, flex)
- Восстановлены стили team-wheel (.wheel-focus, .scroll-arc) из git истории
- Реализовано отображение forwarded message info в messageRendererV2
- Множественные улучшения UI и UX для опросов

Fixes: создание опросов, голосование, отображение результатов, список проголосовавших
2026-01-22 01:33:42 +03:00
ae340f4d03 refactor: extract serialize_message to separate module and remove obsolete ChatConsumer classes
- Create communications/serialization.py with serialize_message function
- Update imports in realtime/consumers.py, api views and serializers
- Remove communications/consumers.py (ChatConsumer and ChatListConsumer now obsolete)
- UserConsumer in realtime/consumers.py handles all WebSocket connections
- Add seed_test_data management command for database population
2026-01-21 17:59:46 +03:00
3f5781cd52 refactor(communications): переименование viewsets.py в views.py
Структурная реорганизация:
- Удален legacy views.py (оставалась только get_available_reactions)
- Переименован viewsets.py → views.py (теперь основной модуль)
- ViewSets теперь в стандартном location (views.py)

Обновлены импорты:
- api/v1/urls.py: from .communications.views import ViewSets
- Удален неиспользуемый endpoint: communications/reactions/available/

Удалено legacy FBV (было 18 функций):
- get_user_chats() → ChatViewSet.list()
- upload_message_with_attachments() → MessageViewSet.upload()
- load_chat_messages() → ChatViewSet.messages()
- load_chat_messages_around() → ChatViewSet.messages_around()
- add_reaction() → MessageViewSet.react()
- remove_reaction() → MessageViewSet.unreact()
- pin_chat() → ChatViewSet.pin()
- toggle_chat_notifications() → ChatViewSet.notifications()
- forward_messages() → MessageViewSet.forward()
- bulk_delete_messages() → MessageViewSet.bulk_delete()
- edit_message() → MessageViewSet.update()
- delete_message() → MessageViewSet.destroy()
- create_chat() → ChatViewSet.create()
- get_available_reactions() → удалена (не используется)

Итоги:
- Удалено 1,722 строк дублирующего кода (-56%)
- 100% переход на ViewSets
- Соответствие DRF best practices

Ref: docs/completed/LEGACY_CODE_CLEANUP.md
2026-01-15 12:16:15 +03:00
ba4a7f1eb9 refactor(communications): удален дублирующий poll_views.py
- Удален файл api/v1/communications/poll_views.py (399 строк)
- Причина: полностью дублировал функциональность PollViewSet
- Все 4 функции перенесены в PollViewSet (завершено 14 января)

Удаленные FBV → ViewSet методы:
- create_poll() → PollViewSet.create()
- vote_poll() → PollViewSet.vote()
- close_poll() → PollViewSet.close()
- get_poll_results() → PollViewSet.results()

Ref: docs/completed/LEGACY_CODE_CLEANUP.md
2026-01-15 12:16:03 +03:00
9849c4084e refactor(communications): обновление кода для новых моделей
Обновлен consumers.py:
- serialize_message: использование forward_metadata вместо forwarded_from_*
- _get_message: prefetch forward_metadata и MessageEditHistory
- _edit_message: создание MessageEditHistory записей вместо JSON

Обновлен serializers.py:
- MessageCreateSerializer: удалены forwarded_from_* из fields
- Чистка Meta.fields от удаленных полей модели

Обратная совместимость:
- WebSocket payload не изменился (forwarded_from структура сохранена)
- API endpoints работают без изменений

Ref: COMMUNICATIONS_API_REFACTORING.md
2026-01-15 12:15:55 +03:00
fa0948c159 feat(communications): добавлена поддержка редактирования сообщений с изменением attachments
Изменения:
- Новый endpoint /api/v1/communications/messages/upload-temp/ для загрузки файлов без создания сообщения
- Поле message в MessageAttachment теперь nullable для временных вложений
- Логика перемещения attachments между сообщениями при редактировании
- Frontend использует upload-temp вместо создания временных сообщений
- Celery задача cleanup_orphaned_attachments для очистки забытых файлов (каждый час)
- Исправлена проблема с WebSocket обновлениями при редактировании
- Удалены debug логи после успешного тестирования

Технические детали:
- communications/models.py: добавлено null=True, blank=True к MessageAttachment.message
- communications/migrations/0022: миграция для изменения поля
- api/v1/communications/viewsets.py: новый action upload_temp() + логика перемещения в update()
- static/js/components/chatComposer.js: использование upload-temp вместо upload
- communications/tasks.py: новая Celery задача для очистки старых orphaned attachments
- eusrr_backend/settings.py: добавлен CELERY_BEAT_SCHEDULE с задачей очистки
2026-01-15 11:13:54 +03:00
53ec1a7b87 feat: implement Telegram-style image dimension pre-loading to fix auto-scroll
- Backend: added width/height fields to MessageAttachment model with Pillow auto-extraction
- Migration: 0021_add_image_dimensions + populate_image_dimensions management command
- API: serialize width/height in messageAttachmentSerializer and consumers.py
- Frontend: switched from CSS aspect-ratio to explicit pixel calculation (Telegram approach)
  * messageRendererV2.js: calculate exact dimensions (maxWidth=500px, proportional height)
  * Fixes scroll jump issue: images now reserve exact space before loading
  * Debug logging added to validate dimension rendering
- Testing: 18/18 images populated successfully with dimensions

This prevents scroll position shifts when images load after initial positioning,
implementing the same approach as Telegram Web.

Related: scroll positioning, image preloading, chat UX
2026-01-14 17:32:06 +03:00
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