- Убран фильтр по календарю при поиске существующих событий
- События автоматически переносятся из personal-XX в общий календарь birthdays
- Удаляются дубликаты событий ДР для одного сотрудника
- Изменено событие на длительность до конца дня вместо 2 дней
- Все ДР теперь добавляются в общий календарь '🎂 Дни рождения'
- Убрана логика личных календарей для каждого сотрудника
- Установлен пакет rules==3.5
- Добавлен ObjectPermissionBackend в AUTHENTICATION_BACKENDS
- Созданы базовые правила для employees:
* Предикаты: is_hr_staff, is_department_head, is_own_profile и др.
* Правила: view/change/delete employee, view reports и контакты
- Созданы базовые правила для documents:
* Предикаты: is_document_owner, has_document_access, can_approve и др.
* Правила: view/change/delete/approve/publish/share documents
- Правила регистрируются в apps.py (ready() метод)
- Создана подробная документация с примерами использования
* В views, templates, DRF permissions, Django Admin
* Сценарии: многоуровневое согласование, временные ограничения
* Сравнение с django-guardian, отладка, тестирование
Документация: docs/guides/DJANGO_RULES_INTEGRATION.md
- Интегрирован django-service-objects 0.7.1 для Service Layer Pattern
- Перенесена вся логика дней рождения из calendar_app → employees:
* UpsertBirthdayEventService - создание/обновление событий ДР
* DeleteBirthdayEventService - удаление событий ДР
* BulkSyncBirthdaysService - массовая синхронизация
* signals_birthday.py - автосинхронизация через post_save/post_delete
* management command sync_birthdays - CLI команда синхронизации
* тесты (11 tests, 6 passing)
- Обновлена миграция 0013_migrate_to_django_scheduler:
* Добавлен ШАГ 5: автоматическая синхронизация дней рождения (131 событие)
* Исключены старые события ДР из миграции (source.endswith(':birthday'))
* Исправлены имена сотрудников (f'{last_name} {first_name}' вместо get_full_name())
* Добавлен цвет #FFC107 для событий дней рождения
- Очищен calendar_app от birthday сервисов:
* Удалены calendar_app/services/birthday_events.py
* Удалены calendar_app/signals_scheduler.py
* Удалены calendar_app/management/commands/sync_birthdays.py
* Удалены calendar_app/tests/test_services.py
* Обновлен apps.py - удалены ссылки на signals_scheduler
- API улучшения:
* Добавлен api/v1/schedule/permissions.py (IsCalendarOwnerOrAdmin)
* Обновлен views.py с правильными permissions
- Документация:
* DJANGO_SERVICE_OBJECTS_INTEGRATION.md - руководство по Service Layer
* CALENDAR_MIGRATION_ANALYSIS.md - анализ миграции
* MODERNIZATION_ANALYSIS.md - анализ модернизации кодовой базы
Цель: подготовка к будущему удалению calendar_app, централизация
логики сотрудников в employees приложении.
Refs: #birthday-refactoring
- Добавлена функция toLocalDateTimeString для корректной конвертации UTC в локальное время
- Исправлено отображение времени в модальном окне событий (было 21:00 UTC вместо 00:00 MSK)
- Упрощена логика datetime-local inputs (удалена сложная условная логика)
- Добавлен allDayAccessor в react-big-calendar для корректного отображения событий
- Унифицирован EventModal для обоих календарей (~718 строк удалено)
- Добавлен CustomToolbar для BigCalendar со стрелками навигации
- Выпадающий список для выбора вида календаря
- Management command для создания тестовых пользователей и календарей
- Создано 3 тестовых пользователя (anna_ivanova, petr_petrov, maria_sidorova)
- Создано 5 календарей с 11 событиями
- Убраны подписки (они для старой системы calendar_app)
- Исправлены стили тулбара BigCalendar
- Модалы календаря рендерятся на корневом уровне AppShell
- Добавлено поле Employee.username для кэширования LDAP логина
- API endpoint обновлен: поддержка кэша и force_refresh параметра
- Новый дизайн: убран blur, добавлена иконка обновления
- Автоматическая загрузка при инициализации (из кэша или LDAP)
- Визуальная индикация источника данных (cached/fresh)
- Доступно всем пользователям без ограничений
- Использована встроенная система пермишенов DRF вместо ручной проверки
- Добавлен ldap_info в required_perms_by_action с правом view_ldap_info
- Добавлен ldap_info в get_permissions() для применения IsSelfOrStaff
- Удалена избыточная ручная проверка прав внутри метода ldap_info()
- Проверка прав теперь выполняется автоматически через get_object()
- Пользователь может видеть свой LDAP логин (IsSelfOrStaff)
- Админы и пользователи с view_ldap_info могут видеть любые логины
- Добавлено поле с 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 (не хранятся в БД)
- 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
- Изменена отправка аватаров на 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 при загрузке больших аватаров
Команда синхронизирует Django pk существующих пользователей в LDAP employeeNumber:
- По умолчанию dry-run режим
- Опция --user-id для теста на одном пользователе
- Опция --force для перезаписи существующих значений
- Использует LdapSyncState для поиска DN пользователей
- Добавлена настройка LDAP_EMPLOYEE_ID_ATTR (по умолчанию 'employeeNumber')
- UserService.create_user() теперь записывает pk в LDAP после создания Employee
- SyncService.import_users() записывает pk при создании новых пользователей
- SyncService.import_users() проверяет и заполняет пустой employeeNumber при update
- Добавлен диагностический скрипт check_employee_id_attrs.py для проверки AD атрибутов
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
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
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
- Added dismissal form on employee profile page with permissions
- Shows warning when dismissing (LDAP deactivation notice)
- Inactive employees filtered by default in employee list (active=true)
- Team wheel now shows only active employees
- Auto-submit filter form on status change
- Added redirect to set active=true by default on first visit
- Add DeptPerm.MANAGE_EQUIPMENT scoped permission constant
- Create migration 0035 for new DepartmentPermission
- Refactor CanManageEquipment permission class with:
* Admin/staff/superuser → full access
* Model permissions → full access (add/change/delete_equipment)
* Department head → own department, can choose responsible
* Scoped permission → own department, responsible forced to head
* Regular members → no access
- Update EquipmentListSerializer with:
* Permission level detection (full/dept_head/scoped/None)
* Auto-fill department and responsible based on permission level
* Validation that responsible is in same department
- Add create_options endpoint returning:
* allowed_departments, can_choose_department, can_choose_responsible
* default_responsible, permission_level
- Add bulk equipment creation support with auto-generated inventory numbers
- Implement 45 comprehensive permission tests (all passing)
- Fix is_active=True in test make_user() fixture
Tests: 45/45 passing ✅
Problem:
- When assigning/changing position, got 502 error
- Error: 'DirectoryService' object has no attribute '_ensure_position_group'
- Method was called on wrong service class
Root Cause:
DirectoryService doesn't have _ensure_position_group method.
This method exists in PositionService (line 190 of position_service.py).
Solution:
- Import and use PositionService for position-specific operations
- Keep DirectoryService for general group operations (add/remove members)
- Use pos_svc._ensure_position_group() to create/get position group DN
- Use dir_svc for add_group_members/remove_group_members
Now position assignment/changes work correctly through LDAP sync.
Problem:
- When clearing employee position (position_id: ''), LDAP sync failed
- Error: LDAP modify failed: 'unwillingToPerform' (code 53)
- System tried to remove user from position group but LDAP refused
Root Causes:
1. Empty string '' not explicitly converted to None
2. unwillingToPerform error not handled (user may not be in group)
Solution:
1. Convert position_id: '' to None (no position) at parsing stage
2. Wrap remove_group_members in try/except
3. Ignore unwillingToPerform errors with warning log
4. Add detailed logging for position group operations
This allows users to clear positions without LDAP sync failures.
- 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
- 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
- Упрощен employee_list view - НЕ загружает данные сотрудников
- Сервер отдает только справочники для фильтров
- Убран серверный рендеринг карточек из шаблона
- Добавлен spinner во время загрузки
- Данные загружаются один раз через JavaScript
- Производительность улучшена - нет двойного запроса
- При отсутствии записи LdapSyncState теперь показывается кликабельная ссылка
- Клик по "❌ Нет записи" ведет на форму создания записи с предзаполненными полями model и object_pk
- Упрощено создание отсутствующих записей синхронизации одним кликом
- Добавлены логи при поиске записи синхронизации
- При отсутствии записи выводится список всех object_pk в таблице синхронизации
- Добавлен ID пользователя в сообщение об ошибке для упрощения отладки
- Улучшена обработка исключений с полным логированием стека
1. Улучшен статус ldap_sync_status с детальной информацией:
- Локальные пользователи отмечены как "Локальный" (серый)
- LDAP пользователи с полными данными: ✅ с GUID и DN в подсказке
- LDAP пользователи с неполными данными: ⚠️ с указанием отсутствующих полей
- LDAP пользователи без записи: ❌ с пояснением
2. Добавлен action "Создать записи LDAP синхронизации" для массового создания записей LdapSyncState
3. Добавлен action "Проверить статус LDAP синхронизации" с детальной статистикой по выбранным сотрудникам
1. Добавлена колонка ldap_sync_status в список сотрудников с иконками статуса (✅/⚠️/❌)
2. В детальной информации ldap_sync_info добавлена кликабельная ссылка на запись LdapSyncState
3. Создан кастомный фильтр LdapSyncedFilter для быстрой фильтрации сотрудников по наличию записи синхронизации
4. Улучшена читаемость статусов синхронизации с подсказками при наведении