- Интегрирован 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
- 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
Команда синхронизирует Django pk существующих пользователей в LDAP employeeNumber:
- По умолчанию dry-run режим
- Опция --user-id для теста на одном пользователе
- Опция --force для перезаписи существующих значений
- Использует LdapSyncState для поиска DN пользователей
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
- сервис integrations/ldap_writeback.py
- фича-флаг и маппинг в settings (LDAP_WRITE_*)
- (рекоменд.) поля в Employee: ldap_dn, ldap_guid, last_ldap_modify_ts, last_ldap_sync_at
- write-back в PATCH /employees/{id} и /employees/me/ (409/503 на конфликты/ошибки)
- оптимизация queryset (prefetch actions только для detail/me)
- мелкие правки add/remove_skill и валидаций
**Что сделано**
* **API**
* Добавлен `POST /api/v1/auth/register/` — создание пользователя, генерация кода и письмо (повторная отправка, если email есть, но не подтверждён).
* `POST /api/v1/auth/verify-email/` возвращает `user_id` при успехе.
* `POST /api/v1/auth/resend-email/` — без изменений по контракту.
* **Фронт (views\_front)**
* `RegisterView.form_valid` теперь вызывает API `auth/register` (без прямой работы с ORM).
* `EmailVerifyView.post` переводён на API `auth/verify-email` + автологин при успехе.
* Повторная отправка кода (`resend_email`) уже через API, добавлена защита от частых запросов по сессии.
* Исправлена сериализация телефона (E.164/строка) при отправке в API.
* **Клиент API**
* Новый модуль `employees/api_client.py`: безопасная сериализация payload, удобные ошибки (`api_bad_json status=… body=…`) и абсолютные URL через `request.build_absolute_uri`.
* **Middleware**
* `AuthRequiredMiddleware` теперь **пропускает**: `/api/v1/**`, статику/медиа, страницы логина/регистрации/сброса пароля, админ-логин и `OPTIONS`. Остальное для анонима — редирект на `login`.
* **Аутентификация/выход**
* Страница подтверждения выхода (в стиле iOS bottom sheet) + `ConfirmLogoutView`. Пункт «Выйти» ведёт на GET `/auth/logout/`, далее POST подтверждения.
* Шаблон выхода принимает `next` из контекста (без обращений к `request.GET.next` в шаблоне).
* **UX/мелочи**
* Устранены 500 при отсутствии `password_reset` — добавлены/учтены URL’ы потока сброса (или безопасные ссылки).
* Уточнены сообщения об ошибках при регистрации/верификации.
**Как проверить**
1. Аноним может вызвать:
* `POST /api/v1/auth/register/` → `201 {"ok":true}` / `200 {"ok":true,"resent":true}` / `400 {"error":"email_taken"}`.
* `POST /api/v1/auth/verify-email/` с корректным кодом → `200 {"ok":true,"user_id":...}`.
* `POST /api/v1/auth/resend-email/` → `200 {"ok":true}`.
2. UI:
* Регистрация в форме → обращение к API, письмо с кодом, переход на ввод кода.
* Ввод кода → API, автологин, редирект в ленту.
* «Выйти» в меню → страница подтверждения → POST → сообщение «Вы вышли…» и редирект на логин/`next`.
3. Проверить, что `/api/v1/**` не редиректится на логин (middleware whitelist работает).
4. Телефон в форме не ломает JSON (нет `TypeError: PhoneNumber is not JSON serializable`).
**Миграции**: нет.