18 Commits

Author SHA1 Message Date
lizerk
c38493a18b refactor(ldap): sync_service employeeNumber через ORM
- sync_service: заменены read_attrs + modify_user_attrs на LdapUser ORM
  для записи employeeNumber при импорте пользователей
- Убран неиспользуемый импорт modify_user_attrs
2026-03-18 15:10:32 +03:00
lizerk
ea0a7e1f29 refactor(ldap): углубить замену ldap3 → ORM
- user_service: _enable_user через LdapUser ORM (убран ldap_modify_or_ignore)
- user_service: _unique_logins через LdapUser.objects.filter (убран is_taken)
- user_service: удалены импорты MODIFY_REPLACE, is_taken, ldap_modify_or_ignore
- group_service: set_description, list_members, add/remove/replace_members,
  find_dn, groups_with_member — все через LdapGroup ORM
- group_service: удалены ldap_modify_or_ignore, MODIFY_*, BASE, esc_filter
- department_service: _set_ou_managed_by через LdapOrganizationalUnit ORM
  (заменены ~60 строк ADD/REPLACE/DELETE логики на 4 строки ORM)
- department_service: удалены MODIFY_ADD/DELETE/REPLACE
- position_service: delete_position_group и _reconcile_position_nesting
  через groups_with_member (ORM) вместо conn.search

Итого: -94 строки, убраны 3 импорта ldap_repository из сервисов.
2026-03-18 14:48:27 +03:00
lizerk
95ff0dc0af refactor(ldap): заменить ldap3 CRUD на django-ldapdb ORM
- user_service.py: read_attrs/modify_user_attrs → LdapUser ORM,
  sync_user_groups_by_cns → ORM, delete_user без _ldap() контекста
- group_service.py: _wrapped методы через LdapGroup ORM
- department_service.py: _set_ou_description/_delete_department_ou → ORM
- views.py, employees.py: ldap_info через LdapUser ORM
- sync_employee_ids_to_ldap: полная замена на LdapUser ORM
- __init__.py: ORM реэкспорты, обёртки import_*/export_*
- orm_models.py: compatibility shim для django-ldapdb + Django 5.x
- Создан group_utils_orm.py (ORM замена group_utils)
- Удалён deprecated sync_service.py (352 строки)

TODO(ldap-orm) помечены: _set_password, _enable_user, _unique_logins,
_create_user_in_ldap, _move_user_to_base, rename (modify_dn),
sync_catalog (paged search), _set_ou_managed_by
2026-03-18 14:25:53 +03:00
lizerk
8cdfbfb6ef feat(ldap): добавлена интеграция с LdapSyncState
Добавлена полная поддержка связи 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 (разные БД)
2026-03-18 13:53:22 +03:00
lizerk
2eb2da4c5a feat(ldap): интеграция django-ldapdb ORM в employees/ldap/
Добавлена поддержка 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
2026-03-18 13:42:32 +03:00
dbdf608051 feat(ldap): записываем Django pk в LDAP employeeNumber при создании/обновлении пользователей
- Добавлена настройка 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 атрибутов
2026-01-07 15:50:07 +03:00
eb69007aa2 fix: ignore entryAlreadyExists when adding member to LDAP group 2025-12-30 12:27:57 +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
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
992dc6a13f feat: maximize LDAP avatar quality (384px, progressive JPEG, 4:4:4)
- Увеличено разрешение: 120px -> 384px (+900% пикселей)
- Повышено качество JPEG: 50-95 -> 75-98
- Добавлен progressive JPEG для лучшего сжатия
- Включён subsampling 4:4:4 (без потери цветности)
- Добавлена обработка EXIF ориентации
- Добавлено сохранение пропорций (thumbnail вместо resize)
- Обработка прозрачности PNG

Улучшения в:
- employees/ldap/utils/image_utils.py
- employees/ldap/services/user_service.py
- employees/ldap/sync_service.py

Добавлены тестовые скрипты:
- test_ldap_simple.py (быстрый тест)
- test_avatar_simple.py (тест системного сжатия)
- TEST_IMAGE_SCRIPTS.md (документация)
2025-12-16 14:55:58 +03:00
298524ecc6 fix: use PositionService instead of DirectoryService for position groups
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.
2025-12-08 13:29:53 +03:00
aa9fcbe964 fix: handle empty position_id and unwillingToPerform LDAP error
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.
2025-12-08 13:14:40 +03:00
2fbf0e29fc fix: синхронизация аватара LDAP <-> БД при обновлении пользователя (avatar теперь сохраняется и в employee.avatar) 2025-12-02 12:49:29 +03:00
e69de73814 fix: синхронизируем avatar из LDAP обратно в БД после обновления 2025-12-02 12:27:01 +03:00
d078b2f50b Merge master and ldap-writeback: added LDAP optional mode, refactored templates and JavaScript modules 2025-11-20 09:00:55 +03:00
4d04aafdb9 refactor: Replace inline CSS with components (Phase 3)
- Replaced feed-* inline styles in base.html with feed-cards.css component (~60 lines)
- Replaced ios-search inline styles in 3 templates with ios-search.css component
  * employees/employees_list.html
  * search/results.html
  * employees/department_list.html
- Eliminated ~150 lines of duplicate CSS across templates
- All 17 tests passing
2025-10-28 08:54:38 +03:00
c4998c709f Apply updates across all files in the repository 2025-10-20 17:08:40 +03:00