61 Commits

Author SHA1 Message Date
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
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
9ef3c53a46 fix: убрана двойная загрузка данных на странице списка сотрудников
- Упрощен employee_list view - НЕ загружает данные сотрудников
- Сервер отдает только справочники для фильтров
- Убран серверный рендеринг карточек из шаблона
- Добавлен spinner во время загрузки
- Данные загружаются один раз через JavaScript
- Производительность улучшена - нет двойного запроса
2025-12-05 12:35:10 +03:00
d370ef678d Валидация ФИО: запрет цифр, обязательность аватара, улучшения JS live validation, фиксы паттернов для отчества 2025-12-02 14:02:10 +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
1b3076fb02 Улучшена обработка ошибок валидации: парсинг detail с извлечением имени поля и сообщения, обработка API ошибок в try-except 2025-12-02 11:07:47 +03:00
94ff4c8fbe Разрешено создание записей LDAP синхронизации через админку, убраны readonly поля для редактирования 2025-12-02 10:53:24 +03:00
3f19f92e0b - 2025-12-02 10:50:47 +03:00
4f94e7533a Добавлена кликабельная ссылка для быстрого создания записи синхронизации:
- При отсутствии записи LdapSyncState теперь показывается кликабельная ссылка
- Клик по "❌ Нет записи" ведет на форму создания записи с предзаполненными полями model и object_pk
- Упрощено создание отсутствующих записей синхронизации одним кликом
2025-12-02 10:43:57 +03:00
2384faa6b3 Добавлено детальное логирование для диагностики проблем с LDAP синхронизацией:
- Добавлены логи при поиске записи синхронизации
- При отсутствии записи выводится список всех object_pk в таблице синхронизации
- Добавлен ID пользователя в сообщение об ошибке для упрощения отладки
- Улучшена обработка исключений с полным логированием стека
2025-12-02 10:42:48 +03:00
677feaa111 Добавлены админ-действия для управления LDAP синхронизацией:
1. Улучшен статус ldap_sync_status с детальной информацией:
   - Локальные пользователи отмечены как "Локальный" (серый)
   - LDAP пользователи с полными данными: ✅ с GUID и DN в подсказке
   - LDAP пользователи с неполными данными: ⚠️ с указанием отсутствующих полей
   - LDAP пользователи без записи: ❌ с пояснением
2. Добавлен action "Создать записи LDAP синхронизации" для массового создания записей LdapSyncState
3. Добавлен action "Проверить статус LDAP синхронизации" с детальной статистикой по выбранным сотрудникам
2025-12-02 10:35:41 +03:00
a142d0ccbc Улучшена админка для мониторинга LDAP синхронизации:
1. Добавлена колонка ldap_sync_status в список сотрудников с иконками статуса (✅/⚠️/❌)
2. В детальной информации ldap_sync_info добавлена кликабельная ссылка на запись LdapSyncState
3. Создан кастомный фильтр LdapSyncedFilter для быстрой фильтрации сотрудников по наличию записи синхронизации
4. Улучшена читаемость статусов синхронизации с подсказками при наведении
2025-12-02 10:32:56 +03:00
1384ef1d20 Расширена админка для полного управления моделями и LDAP:
1. Добавлено поле ldap_sync_info в админку Employee для отображения состояния LDAP синхронизации (DN, GUID, направление, timestamps)
2. Добавлены недостающие поля Employee в админку: is_ldap_managed, email_activation_code, sms_activation_code
3. Добавлен is_ldap_managed в list_display и list_filter для быстрой фильтрации
4. Добавлены LDAP поля (ldap_group_dn) в админки Department, DepartmentRole и Position
5. Улучшены fieldsets для всех моделей с группировкой LDAP полей
2025-12-02 10:24:57 +03:00
7950c40636 Улучшена обработка ошибок профиля и добавлена админка для LDAP:
1. Убраны редиректы из employee_edit и employee_edit_me - теперь всегда возвращается JSON
2. Добавлена проверка наличия ldap_dn перед обновлением через LDAP в обоих методах (me и partial_update)
3. Зарегистрирована модель LdapSyncState в админке для просмотра состояния синхронизации
2025-12-02 10:13:17 +03:00
8edc856636 Исправлены ошибки редактирования профиля:
1. Изменен редирект при ошибках валидации: теперь возврат на страницу профиля вместо несуществующего GET endpoint
2. Добавлена проверка наличия ldap_dn перед обновлением через LDAP: локальные пользователи без ldap_dn теперь обновляются напрямую в БД
2025-12-02 10:03:47 +03:00
5291a921f4 Исправлена обработка ошибок валидации: теперь при ошибках валидации в employee_edit и employee_edit_me происходит возврат на форму редактирования с сообщениями об ошибках, вместо редиректа на профиль. 2025-12-02 09:58:25 +03:00
34b04d4c7f закомитил обработчик который обрабатывал сигнал смены аватара 2025-12-02 09:23:51 +03:00
a7aa6cc15b fix: handle missing avatar files gracefully in signals, improve registration phone error, PATCH avatar empty handling 2025-12-01 17:09:44 +03:00
68352580e8 Фикс: корректный placeholder для фото сотрудника при пустом avatar, форма отправляет POST на фронтовую вьюху, убрана JS-валидация и кастомный submit. 2025-12-01 15:44:02 +03:00
bfb87d796c debug: добавлено логирование вызова функции employee_edit_me 2025-12-01 15:16:00 +03:00
87fe9d5c2c debug: добавлено подробное логирование в employee_edit и employee_edit_me для отладки загрузки аватаров 2025-12-01 15:14:50 +03:00
2730b0e019 fix: добавлена поддержка multipart/form-data в employee_edit и employee_edit_me для загрузки аватаров 2025-12-01 15:12:19 +03:00
1be74f407f fix: исправлен путь к аватарам в команде optimize_avatars (используется MEDIA_ROOT) 2025-12-01 14:43:14 +03:00
46ac00255e Добавлена автоматическая оптимизация аватаров: сжатие до 512x512 (до 500KB) + удаление старых файлов 2025-11-30 10:56:52 +03:00
43fa0a2053 Добавлена загрузка/замена/удаление фото сотрудника с автоматическим удалением старых файлов 2025-11-30 10:50:09 +03:00
8fe844780e Fix ApiResponse.content error and add API base URL logging
- Changed resp.content to resp.text in register_view (ApiResponse has no content attribute)
- Added logging for API base URL to debug 404 errors
- This should help identify why API returns 404 for registration endpoint
2025-11-21 13:39:06 +03:00
2637e9ed65 Add detailed logging for registration debugging
- Added logging in RegisterAPIView to track incoming data and validation errors
- Added logging in register_view to track form data and API communication
- Created test_register_api.py script for testing registration API
- Logs include: POST data, Content-Type, validation errors, API payload and responses
- All logs use [REGISTER] and [REGISTER_VIEW] prefixes for easy filtering
2025-11-21 13:35:38 +03:00
35f365a1b0 feat: Add department-based document distribution with department picker in UI
- Added departments ManyToMany field to Document model
- Implemented department selection in document create/edit modals
- Added automatic notification creation for all department employees
- Fixed document filtering to include uploaded documents in 'My Documents'
- Fixed acknowledgements API to include department recipients
- Added bulk notification support with configurable threshold
- Removed acknowledge button, added visual highlighting for unread documents
- Implemented server-side acknowledgment flow with redirect to file
- Fixed mobile responsive layout for document actions
- Removed temporary test files and documentation
2025-11-21 12:46:43 +03:00
ddf22c47da fix: автоматическое обновление JWT токенов
- ApiClient теперь обрабатывает 401 и 403 ошибки для обновления токена
- Добавлен JWTRefreshMiddleware для проактивного обновления токенов за 5 минут до истечения
- Добавлена настройка JWT_REFRESH_THRESHOLD_MINUTES (по умолчанию 5)
- Исправлена проблема с периодическими 403 ошибками после истечения access token
2025-11-20 18:52:29 +03:00
bd44efb218 отмена изменений админки 2025-11-20 13:01:34 +03:00
30e6643b6e Исправлена админка для редактирования дат created_at/updated_at
- Добавлены явные поля в EmployeeChangeForm для дат с виджетами datetime-local
- Переопределен метод save_model для обхода auto_now/auto_now_add
- Используется прямое обновление через queryset для изменения неизменяемых полей
- Только superuser может редактировать эти поля
2025-11-20 12:57:33 +03:00
6dfb19e576 Добавлена возможность редактирования дат создания/обновления в админке
- Superuser теперь может редактировать created_at и updated_at
- Обычные администраторы видят эти поля как readonly
- Добавлен метод get_readonly_fields для динамического контроля прав
2025-11-20 12:54:49 +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
f91f81c9a7 Enforce single department per employee with unique constraint and validation 2025-10-21 07:35:23 +03:00
c4998c709f Apply updates across all files in the repository 2025-10-20 17:08:40 +03:00
5256347829 Merge feat/ldap-writeback into master (create merge/master-and-ldap-writeback) 2025-09-22 14:34:49 +03:00
52f2aed5fd From: You <you@example.com>
Date: Mon, 22 Sep 2025 10:30:00 +0300
Subject: [requests] Drawer «Рассмотреть», корректный чекбокс «Черновик», дружелюбные даты,
 robust-обработка ошибок reject, и глобальный бейдж непрочитанных чатов

- Список заявлений:
  - вместо россыпи кнопок добавлен аккуратный выезжающий блок действий (collapse drawer)
    по клику «Рассмотреть»; компактные нейтральные стили, мягкая анимация.
  - в модале «Изменить» добавлена галочка «Сохранить как черновик» с правильной
    семантикой: hidden `_save_as=submit` по умолчанию + checkbox `_save_as=draft`.
    Теперь снятие галочки действительно снимает draft.
  - улучшены стили и структура разметки.

- Даты:
  - на фронт-обёртке нормализуем datetime-поля (created_at/updated_at/decided_at)
    в человекочитаемый вид, чтобы не светить сырые ISO 8601.

- Обработка ошибок:
  - фикс падения при попытке отклонить уже одобренную заявку: ответ 400 у DRF
    может приходить списком ошибок; теперь сообщение извлекается безопасно.

- Чаты:
  - синхронизировали название ключа контекста с проектом (`chat_unread_total`);
  - бейдж «Чаты» обновляется глобальным событием `chats:unread-total`, даже если
    список чатов не открыт (fallback — суммирование счётчиков на странице).

Примечание: пути к шаблонам могут отличаться в вашем проекте — ниже даны
типовые варианты (`backend/requests_app/templates/...`). Если у вас другой
layout, внесите изменения в соответствующие файлы шаблонов.
2025-09-22 13:01:20 +03:00
cf04e1a27f feat(ldap): write-back с optimistic lock + интеграция в API
- сервис 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 и валидаций
2025-09-18 10:07:49 +03:00
9529b3f268 **feat(feed, perms, tests): новая политика доступа для фида + полный набор автотестов**
Ключевые изменения

* Перевели доступ к объектам отдела (посты) на **ролевую модель внутри отдела**:

  * Используются `DepartmentRole.scoped_permissions` (M2M к `DepartmentPermission`).
  * Проверка прав идёт по **коду** из `DeptPerm` (`CREATE_POST`/`MANAGE_FEED`), а не по `auth.Permission`.
* **Company-посты** теперь контролируются **модельными правами** (`add_post`, `change_post`, `delete_post`) или статусом staff/superuser.
* Убраны остатки старой логики `publish_company_post` / `publish_department_post` (как глобальные права).

Пермишены / утилиты

* `user_has_dept_perm(user, dept, code)`:

  * true для staff/superuser и для руководителя отдела;
  * иначе проверка активной связи `EmployeeDepartment(is_active=True)` и наличия `role__scoped_permissions__code=...`.
  * ВАЖНО: используем поле **`code`**, не `codename`.
* `has_dept_perm(user, department_id, code)`:

  * те же правила, оптимизирован поиск (select\_related для роли).
* `AdminOrDeptAllowed`:

  * Корректно извлекает `department_id` из `kwargs`/`data`/`query_params` и из объекта (`obj.department_id/department.id`).
  * Если код не задан — SAFE-методы разрешены (чтение), небезопасные запрещены.
* `AdminOrActionOrModelPerms`:

  * staff/superuser — полный доступ;
  * поддержка явного `required_perm_code`/`required_perms_by_action`;
  * SAFE-методы требуют `view_<model>`, небезопасные — стандартная карта DjangoModelPermissions.
* `IsSelfOrStaff`/`IsSelfOrStaffOrModelPerms`:

  * Владелец объекта (по `author_id`/`author` и др.) или staff/superuser может изменять/удалять;
  * SAFE-методы — для авторизованных.

API: PostViewSet

* Обновлён `get_permissions()`:

  * `list/retrieve/create/like/unlike` → `IsAuthenticated`;
  * `update/partial_update/destroy` → `AdminOrDeptAllowed` с `required_code_map`:

    * `update/partial_update/destroy` → `DeptPerm.MANAGE_FEED`.
* Бизнес-проверки:

  * `perform_create`:

    * `type=department` → разрешено staff/superuser, head отдела, либо роль с `DeptPerm.CREATE_POST`;
    * `type=company` → нужны модельные права `add_post` (или staff).
    * `author` проставляется на `request.user`.
  * `perform_update/perform_destroy`:

    * dept-посты: staff/superuser, head, либо роль с `DeptPerm.MANAGE_FEED`;
    * company-посты: модельные права `change_post`/`delete_post` (или staff).
* Производительность:

  * Аннотация `is_liked` (через `Exists`).
  * Аннотация `last_comment_id` + батч-подтягивание последнего комментария в `list()`.
* Лайки:

  * `like/unlike` — идемпотентные; счётчик корректируется атомарно.

API: CommentViewSet

* `permission_classes = [IsAuthenticated, (IsSelfOrStaff | AdminOrActionOrModelPerms)]`.
* `perform_create` всегда ставит `author=request.user`.
* Фильтры `?post=`/`?author=` и сортировка `created_at ASC`.
* Обновлён `IsSelfOrStaff` для чтения настроек владельца с вью (например, `owner_id_attrs=("author_id",)`).

Тесты

* Добавлен/переписан комплект для новой модели доступа (фид):

  * **Аноним**: все эндпоинты → 401.
  * **Обычный пользователь**:

    * `GET posts/comments` → 200;
    * `POST comment`, `like/unlike` → 201/200;
    * `POST/PUT/PATCH/DELETE dept-post` → 403 (без роли/прав).
  * **Роль отдела**:

    * С `DeptPerm.CREATE_POST` — может создать пост **в своём отделе**; **в чужом** → 403.
    * С `DeptPerm.MANAGE_FEED` — может редактировать/удалять **в своём отделе**.
  * **Head отдела** — полный доступ к постам своего отдела.
  * **Staff/superuser** — полный доступ; `pin/unpin` только staff/superuser.
  * **Глобальные модельные права на company**:

    * `add_post`/`change_post`/`delete_post` → CRUD **только** для company-постов; dept-посты по-прежнему 403.
  * **Комментарии**:

    * Создание — любой аутентифицированный;
    * Изменение/удаление — автор или staff/superuser.
  * **Лайки**:

    * Идемпотентность `like/unlike`, корректная синхронизация `is_liked`.
  * **Валидация**:

    * `type=department` требует `department`;
    * роль в одном отделе не даёт создавать в другом → 403.
* Тестовые хелперы:

  * `_ensure_permission(model, codename, name)` — создаёт/возвращает `auth.Permission` для company-кейсов;
  * `_ensure_dept_permission(code)` и `_give_dept_role_with_perms(...)` — для департаментных прав (через `DepartmentPermission`/`DepartmentRole.scoped_permissions`).

Fixed / багфиксы

* `FieldError: Unsupported lookup 'codename'…` — заменён на фильтр по `scoped_permissions__code`.
* `IntegrityError: NOT NULL constraint failed: feed_post.author_id` — в `perform_create` постов/комментариев author задаётся принудительно.
* Несоответствие тестам по правам чтения/изменения комментариев — исправлено через корректный пермишен владельца.

Файлы (основные)

* `api/v1/permissions.py`: `AdminOrDeptAllowed`, `AdminOrActionOrModelPerms`, `IsSelfOrStaff`(+вариант `IsSelfOrStaffOrModelPerms`), `user_has_dept_perm`, `has_dept_perm`.
* `api/v1/feed/views.py`: `PostViewSet`, `CommentViewSet` (правки прав, `perform_*`, аннотации, like/unlike).
* `tests/api/v1/feed/test_posts.py`, `tests/api/v1/feed/test_comments.py` (и/или `test_feed_api.py`): полный набор сценариев под новую политику.
* Хелперы тестов: функции создания отделов, прав и ролей, генераторы payload.

Итог

* Политика доступа фида приведена к спецификации: **dept-права — через роли внутри отдела**, **company — через модельные права**.
* Исправлены ошибки в проверках прав и авторстве, покрыто автотестами (CRUD, лайки, комменты, валидация, пермишены).
* Производительность чтения ленты улучшена (аннотации `is_liked`, `last_comment_id` без N+1).
2025-09-11 17:56:33 +03:00
d488b748ad feat(ui+calendar+auth+depts): большой апдейт календаря, форм авторизации и списка отделов
Календарь (rightbar) :

• Цвет события по умолчанию (#0d6efd) + удобная палитра (input[type=color] + быстрые свотчи).

• Детали события в модале (центрирование, перенос модала в <body> при необходимости), кнопки Редактировать/Удалить.

• Редактирование: заполнение формы из деталки, PATCH по API, возврат к календарю, безопасные обработчики (навешиваются один раз).

• Еженедельная лента теперь собирает события со всех доступных календарей (компания + отделы пользователя), дедупликация по id.

• В ленте недели: учитывается цвет события, показ источника (Компания / Отдел: N), location и краткое описание (≤20 символов).

• Устранены дубли отделов в селекторе календарей; нормализованы id; аккуратные ошибки/403; безопасная работа без токена.

Список отделов:

• Поиск по названию и руководителю (data-haystack); счётчик сотрудников обновляется только по видимым строкам.

• Кнопка «Создать отдел» в стиле списка (вся строка — кликабельна), видна только при наличии прав/статуса.

• Чёткое центрирование текста и иконки без кастомных стилей (Bootstrap-утилиты), стрелка фиксирована справа.

• Прокрутка только у списка (#deptList): шапка, поиск и кнопка остаются на месте; высота как в employees_list.html.

Auth / Регистрация / Логин / Верификация email / Сброс пароля:

• Шаблоны приведены к единой теме: уважаем переключатель тем из base.html, устранён «мигание» темы.

• RegistrationForm: валидация контактов (хотя бы один из Telegram/WhatsApp/WeChat), пароли совпадают.

• Поля телефона (регистрация/логин): inputmode='numeric', pattern для E.164, подсказки; на бэке по-прежнему нормализуется.

• Регистрация: превью аватара с заглушкой, radio для пола; убраны поля навыков/должности из формы (они не нужны здесь).

• API регистрации: принимаем avatar через Base64ImageField и сохраняем user.avatar при создании; остальная логика без изменений.

• Сообщение об ошибке «нужно хотя бы одно контактное поле» возвращается в ключе detail (совместимо с тестом).

• Логин/верификация/сброс: единые заголовки, floating labels, иконки, соответствие теме.

Техническое:

• Аккуратные утилиты JS: извлечение токена, формирование URL с диапазонами дат, нормализация событий, один раз навешиваемые обработчики.

• Безопасные fallbacks для отсутствующих узлов/данных; кросс-браузерные мелочи (overflow-scrolling, aria-атрибуты).

Примечания:

• Миграций БД нет. Изменены шаблоны, фронтовый JS и вью/сериализатор регистрации.

• Если пути файлов в проекте отличаются, коммит всё равно включает все изменённые файлы (git add -A).
2025-09-10 17:03:45 +03:00
0772e4549a **feat(calendar): единая модель/эндпоинт календаря, повторяемость, права, сигналы и фронт**
Большое обновление календаря. Объединили модель событий, добавили полноценную повторяемость (hourly/daily/weekly/monthly/annual), материализацию вхождений в списке, навели порядок с правами отделов и обновили фронтовой шаблон. Покрыли API pytest-тестами и починили мелкие баги.

### Ключевые изменения

**Модель**

* Заменили `CompanyEvent` и `DepartmentEvent` на единую **`CalendarEvent`**:

  * `department` (nullable FK) — если задан, событие отдела; если `NULL`, событие компании.
  * Даты/время: `start_date`, `end_date`, `start_time`, `end_time`, `all_day`.
  * Повторяемость: `recurrence` ∈ {`one_time`, `hourly`, `daily`, `weekly`, `monthly`, `annual`}.
  * Параметры повторов: `recurrence_interval` (int>0), `recurrence_until` (date), `recurrence_count` (int), `weekdays_mask` (bitmask для weekly).
  * Индексы под выборки и сортировку; check-constraint `cal_event_date_time_range_valid` (валидность диапазонов даты/времени; для одного дня `start_time <= end_time` при наличии обоих).
  * Поля оформления: `title`, `description`, `color`, `location`, `created_by`, `created_at`.
* Правка по умолчаниям: `weekdays_mask` теперь с безопасным default=0 (исправляет `NOT NULL` в SQLite).

**Сервисы**

* Добавлен сервис `expand_event_occurrences(event, range_start, range_end, max_instances=1000)`:

  * Материализует все вхождения события в окне `[range_start, range_end)`.
  * Корректно обрабатывает: leap day (29 Feb), месяцы с «дырками» (31-е), еженедельные маски, ежечасные интервалы.
  * Выдаёт ровные границы дат/датавремени для FullCalendar.

**Сериализаторы**

* `CalendarEventWriteSerializer` — строгая валидация:

  * Для hourly требуется `start_time` и `end_time`.
  * Для weekly — пустой/непустой `weekdays` валидируется и мапится в `weekdays_mask`.
  * `recurrence_interval` > 0; взаимоисключение `recurrence_until` и `recurrence_count`.
* `CalendarEventSerializer` — read-вариант с плоскими полями для фронта.
* Доп. сериализатор вьюхи для списка не нужен — материализуем в контроллере (см. ниже).

**ViewSet / API**

* Новый единый роут: **`/api/v1/calendar/events/`** (`ModelViewSet`):

  * `list`: возвращает **материализованные** вхождения в окне `[start, end)` (query-параметры `YYYY-MM-DD`). Если передан `department_id` — фильтрует события отдела, иначе — компании.
  * `retrieve/create/update/partial_update/destroy`: операции над самим `CalendarEvent`.
  * `get_queryset`: для `list` фильтрует по department/company, для detail — полный QS.
  * При `create`: подставляет `department_id` либо из контекста/параметров, либо из body, проставляет `created_by`, чистит кеш.
* **Права**:

  * Используется комбинированный пермишен **`ManageCalendarPerm(AdminOrDeptAllowed)`**.

    * Для компании делегирует на `AdminOrActionOrModelPerms`.
    * Для отдела требует **строковый код**: `"manage_department_events"`.
    * Обновляет инстанс пользователя из БД (сбрасывает кэши), чтобы в одном тесте «выдали право → сразу работает».
  * `get_permissions()` → `[IsAuthenticated(), ManageCalendarPerm()]`.
* Починки:

  * Подключены `parse_date`/`status` где нужно.
  * Исправлен конфликт дат/датавремени в материализации.
  * Возврат полей в `list`: `start`, `end`, `allDay`, `color`, `recurrence`, `department_id` (ID родителя можно оставить как `event.id`).

**Signals**

* Добавлен сигнал на изменение `date_of_birth` сотрудника:

  * Создаёт/обновляет/удаляет соответствующее ежегодное событие в **глобальном** календаре.
  * Идентификация через `source="employee_birthday"` + `source_ref=<employee_id>`.

**Админка**

* `CalendarEventAdmin`:

  * Поиск по названию/описанию/локации.
  * Фильтры по отделу, типу повторения, all\_day, наличию времени, «company vs department».
  * Инлайнов нет; list-представление упрощено (исправлены ошибки `admin.E116` — фильтры теперь валидные `SimpleListFilter`/поля).

**Фронт (шаблон правой колонки)**

* Переписан `<script>` под единый эндпоинт:

  * Список: GET `/api/v1/calendar/events/?[department_id=...]&start=YYYY-MM-DD&end=YYYY-MM-DD`.
  * Создание:

    * Компания: POST `/api/v1/calendar/events/` с `{title, start_date, [end_date], all_day}`.
    * Отдел: POST туда же, но с `department_id` (в query или в body).
    * Если указано время — **оба** (`start_time`, `end_time`) обязательны; иначе сохраняем как all-day.
  * Нормализация дат в локали, аккуратные конвертации для FullCalendar.
  * Заголовки авторизации: `Authorization: Bearer <token>` из `<meta name="api-access">`/`localStorage.api.access`. Добавлен fallback на CSRF-куку при отсутствии токена (для SessionAuth).
  * Улучшен UI выбора отдела (`/api/v1/departments/my-departments/`), подписи, недельная лента.

**Тесты**

* Добавлены/актуализированы pytest-тесты API:

  * Роуты и методы (CRUD).
  * Позитив/негатив создания (валидация дат/времени/повторов).
  * Изменение повторяемости (weekly → daily и пр.).
  * Материализация (hourly alignment, weekly mask, monthly edge-cases, leap day).
  * Права: отдел без/с `manage_department_events` (фикс за счёт строкового кода).
  * Сигналы: апсерт/удаление дней рождения.
* Исправлены прежние падения: `NOT NULL weekdays_mask`, `NameError(parse_date/status)`, сравнения date/datetime, несоответствие `all_day` vs `allDay`, и др.

### Совместимость и заметки по деплою

* **BREAKING**: единый эндпоинт `/api/v1/calendar/events/` заменяет старые `/company-events/` и `/department-events/`. Если есть сторонние клиенты — переориентировать на новый.
* Миграции обязательны (новая таблица, индексы, дефолты).
* Если где-то ещё использовались константы прав — убедиться, что везде строка `"manage_department_events"` (тесты ориентируются на неё).
* Для фронта: убедиться, что реально прокидывается access-токен в `<meta name="api-access">` **или** есть валидная CSRF-кука при SessionAuth. Иначе POST вернёт 401.

### Почему так

* Единая модель/роут упрощают код и устраняют разъезд схем компании/отделов.
* Материализация в контроллере избавляет фронт от логики повторов и держит поведение единообразным.
* Явная строка кода права синхронизирована с тестовой выдачей пермишена и реальным хранилищем прав отделов.
* Валидация сериализатора закрывает большую часть негативных кейсов на уровне API.
2025-09-09 17:55:15 +03:00
0b8c2a4d43 feat(calendar): интеграция выбора отдела через API /my-departments
feat(calendar): правый календарь с выбором источника и созданием событий (Bearer)
fix(layout): выровнял колонки к центральному контейнеру, убрал лишние отступы
fix(templates): убрана несуществующая тэг-функция токена; добавлен meta api-token
feat(chat/ui): полировка chat-root, скролла и кнопки «вниз»
style(feed): «Подробнее/Скрыть» — серые без подчёркивания

- добавлен эндпоинт DepartmentViewSet.my_departments для получения доступных отделов
  (HR → все отделы, иначе только руководимые/активные)
- настроены permissions для нового экшена
- фронтенд: заменена логика дропдауна календаря
  * загрузка списка отделов через /api/v1/departments/my-departments/
  * автогенерация пунктов меню с именами отделов
  * fallback на data-dept-id при пустом ответе
- обновлена подпись текущего календаря (кнопка и модалка «Создать событие»)
- сохранена логика работы с событиями (компания/отделы), переработан только источник данных для списка отделов
— Календарь (правая колонка)
  • Добавлен дропдаун возле «Календарь» для выбора источника: «Компания» и «Мои отделы».
  • «Мои отделы» подгружаются из GET /api/v1/employees/my-departments/ (JSON: [{id, name}]).
  • Рядом с «События» добавлена кнопка «+», открывающая модал создания события.
  • Создание события уходит в POST:
      - /api/v1/calendar/company-events/
      - /api/v1/calendar/departments/<id>/events/
    Форматы: all-day (date) либо с временем (start/end ISO). Не через сессию.
  • FullCalendar переключает источник событий в зависимости от выбранного календаря и
    ходит за ними с параметрами ?start=YYYY-MM-DD&end=YYYY-MM-DD.
  • Вертикальная «На этой неделе» рендерится из тех же данных (без лишних запросов,
    если события уже в памяти FullCalendar).
  • Мобильная версия — Offcanvas + тот же функционал.

— Авторизация / API
  • Все запросы к API — через Authorization: Bearer <token>.
  • В base.html добавлен мета-тег с токеном:
      <meta name="api-token" content="{{ request.user.api_token|default:'' }}">
  • Общий helper authFetch() отключает cookie/сессию и всегда проставляет заголовки.
  • Это устраняет 401 при GET и готовит почву под POST (ранее наблюдался 405 без POST-ручек).

— Вёрстка / контейнер
  • Левую и правую колонки «привязал» к центральному блоку (container-xxl), убраны
    чрезмерные внутренние паддинги, чтобы центральный контент не сжимался.
  • Уточнены вертикальные расстояния между карточками (gap / margin), карточки теперь
    реально «прижимаются» по вертикали, где это нужно.
  • В main контент тянется на всю доступную ширину внутри контейнера.

— Чаты
  • Объяснён и оставлен padding у .chat-root (верх/низ под шапку и композер, включая safe-area).
  • Кнопка «вниз» и стили скролла — без конфликтов с обёртками.

— Шаблоны
  • Удалён несуществующий тэг `{% api_access_token %}` (исправляет TemplateSyntaxError).
  • Вставлен рабочий meta name="api-token" вместо кастомного тега.

— Кнопки «Подробнее/Скрыть»
  • Сделаны серыми и без подчёркивания (btn btn-link text-secondary text-decoration-none).

Затронутые файлы (основные):
  • backend/templates/base.html
  • backend/templates/includes/rightbar_calendar.html        (новый/полностью переписан)
  • backend/templates/feed/_feed_cards.html                  (кнопки «Подробнее/Скрыть»)
  • backend/templates/includes/sidebar.html                  (мелкие правки отступов)
  • стили в базовом шаблоне: правки контейнеров/отступов/гридов
  • (JS в rightbar_calendar): authFetch, загрузка отделов, POST создания события, weekly strip

Примечания по бэку (ожидания фронта):
  • GET  /api/v1/employees/my-departments/ → 200 OK, JSON: [{ "id": <int>, "name": <str> }, …]
  • GET  /api/v1/calendar/company-events/?start=&end=
  • GET  /api/v1/calendar/departments/<id>/events/?start=&end=
  • POST /api/v1/calendar/company-events/                      (body: {title, date|start,end, allDay?, color?})
  • POST /api/v1/calendar/departments/<id>/events/            (аналогично)
  • Все ручки принимают Authorization: Bearer <JWT/токен>.
  • Если раньше был 405 на POST — включить create() во вьюсетах и добавить в роутер.
2025-09-08 17:19:57 +03:00
409632e3f3 feat(ui, departments): новая страница отдела — колесо команды (4 колонки), collapse «Настройки отдела», фиксы назначения ролей
— Переверстал backend/templates/employees/department_detail.html под раскладку как в employee_detail:
  • левая колонка (md-4): «Команда отдела» (круглое окно) + инфо блока;
  • правая колонка (md-8): collapse-блок «Настройки отдела» в стиле ios-card + «Публикации отдела».

— «Колесо команды»:
  • круглое окно с 4 колонками аватарок ВСЕХ участников отдела; каждый следующий ряд смещён на 0.5 размера аватарки;
  • аватарки круглые, без подписей; поддержка тёмной/светлой темы;
  • автоскролл с дуговым индикатором прогресса по правой полуокружности;
  • hover — пауза автоскролла, включается ручной скролл (колёсико/тач);
  • клик по аватарке — фокус: аватарка растягивается на весь круг; под кругом центрировано показываются имя/роль/email;
  • если в отделе нет сотрудников — в круге показывается руководитель;
  • a11y: aria-атрибуты для живого региона и описаний.

— Роли/кнопки:
  • кнопки Invite / Set role / Remove показываются только при наличии прав (is_superuser|is_staff|can_manage|can_assign_roles|head);
  • fixed: «Назначить роль» больше не падает с NoReverseMatch — action формируется из шаблона `/employees/0/departments/<dept>/set-role/` и подменяется на выбранный empId по клику;
  • формы модалок: мелкие правки, валидация, улучшения UX (ajax-поиск, активное состояние, блокировка submit, плавный скролл к collapse на мобильных).

— Настройки отдела:
  • оформлен collapse-блок «Настройки отдела» в стиле ios-card (как в редакторе профиля);
  • добавлена кнопка-шестерёнка в инфо-блоке для быстрого раскрытия;
  • include исправлен: `{% include "employees/_department_controls.html" with dept=dept edit_form=edit_form head_choices=head_choices %}`.

— Стили и скрипты:
  • локализованы в `{% block extra_js %}` (можно вынести в base позже);
  • CSS-переменные для размеров, цветов и дуги; учитывает базовый шаблон и смену темы.

Files:
  M backend/templates/employees/department_detail.html

BREAKING CHANGE: нет
2025-09-05 18:38:10 +03:00
07d5209f43 feat(employees): управление составом отдела + разделение прав; фронт-модалки добавления/удаления; фиксы UI/тесты
Backend / API
- DepartmentViewSet:
  - добавлены экшены POST /departments/{id}/add_member/ и /remove_member/
    • включают/выключают членство (is_active) без изменения роли
    • идемпотентны: add_member активирует существующую связь или создаёт, remove_member не трогает роль
  - set_member_role: не создаёт членство, 404 если сотрудник не состоит в отделе; роль проверяется на принадлежность отделу; возвращает {employee_id, role_id|null, is_active}
  - get_permissions: исправлено пересечение прав
    • add_member/remove_member → только ManagePerm (DeptPerm.MANAGE)
    • set_member_role → только AssignRolePerm (DeptPerm.ASSIGN_ROLE)
    • set_head → ChangeHeadPerm
    • update/partial_update/destroy → ManagePerm
  - queryset/поиск/сортировка: аннотации для employees_count, поиск по имени/описанию/ФИО руководителя, сортировка по name/id
- Исправления валидации head в partial_update/set_head: «ослабление» требования email_verified, если меняет текущий руководитель

Frontend / Views & Templates
- employees/views_front.py:
  - department_detail: берёт агрегат /api/v1/departments/{id}/ui-context/; формирует members_exclude_ids (участники+руководитель)
  - добавлены прокси-вьюхи department_add_member / department_remove_member (POST) — ходят в API, показывают messages и редиректят обратно
  - employees_list: поддержан формат ?format=json для фронт-поиска (без прямого запроса в DRF → нет 401)
  - set_member_role: прокси, защита от изменения собственной роли
- templates/employees/department_detail.html:
  - «голая» шестерёнка в правом нижнем углу инфоблока; запоминает состояние (localStorage), аккуратные отступы
  - описание отдела: white-space: pre-line + overflow-wrap:anywhere (учёт переносов и длинных слов), уменьшен верхний «воздух»
  - секция «Команда отдела» переделана в стилях блока «Профиль» (как в employee_detail.html):
    • строки-элементы с аватаром, ФИО, текущей ролью
    • форма смены роли (если есть права)
    • две отдельные кнопки: «Пригласить» (добавить) и «Удалить»
  - два модальных окна:
    • Пригласить (add): поиск по всем сотрудникам, исключая уже состоящих/руководителя (members_exclude_ids)
    • Удалить (remove): поиск только по сотрудникам текущего отдела; исключаем самого себя
    • список с аватарками, выбор подсвечивается; сабмит не активен до выбора; клавиши Enter/Escape
  - JS для модалок: поиск через фронтовую employees_list (?format=json), дебаунс, «портал» меню в body, обработка ошибок
- templates/employees/_department_controls.html:
  - «Новая роль»: фикс разворота/скрытия формы, сброс состояний
  - выпадающее меню ролей рендерится в body (не клипается родителями)
  - кнопка «Отмена» теперь полноценная кнопка (btn btn-link), работает
  - чипсы прав + скрытые permission_ids синхронизируются корректно

Тесты
- tests/api/v1/employees/test_department_membership_separation.py (новые/обновлённые):
  - test_add_member_requires_manage_and_does_not_assign_role
  - test_remove_member_requires_manage_and_does_not_touch_role
  - test_set_member_role_requires_assign_not_manage
  - test_set_member_role_does_not_create_membership_and_does_not_toggle_active
  - test_all_three_actions_require_auth_and_permissions
- фиксы фабрик/тест-данных: гарантированно уникальные email/phone_number (устранён IntegrityError: UNIQUE constraint failed)
- поправлены ожидания статусов (401 для неаутентифицированных, 403 без прав, 200 при корректных правах); для set_member_role — 404, если членства нет

Bugfixes / прочее
- VariableDoesNotExist (list) в department_detail: использован безопасный контекст и корректный default
- 401 при поиске сотрудников из шаблона: переведено на фронтовую прокси-вьюху (?format=json), вместо прямого запроса в DRF
- мелкие правки стилей и отступов, соответствие стилю «Профиль»

Как проверить
1) API-права:
   - без аутентификации: все 3 экшена → 401
   - с аутентификацией, но без прав: add/remove → 403; set_member_role → 403
   - с DeptPerm.MANAGE: add/remove → 200; set_member_role → 403
   - с DeptPerm.ASSIGN_ROLE: set_member_role → 200; add/remove → 403
2) set_member_role не создаёт членство и не меняет is_active; add/remove не трогают роль
3) UI:
   - шестерёнка показывает/скрывает настройки; состояние помнится
   - модалки «Пригласить»/«Удалить»: поиск, выбор, сабмит; аватарки; корректные фильтры (исключения и текущий отдел)
   - «Отмена» работает, кнопки и выпадающие списки не клипаются

BREAKING CHANGES: нет
Migrations: нет
2025-09-04 17:10:27 +03:00
e24caabb76 **feat: управление отделами и ролями (API + шаблоны) + правки тестов**
### Backend / API

* **DepartmentViewSet**

  * Вернул поведение поиска/сортировки как в старой реализации: `?search=` по имени/описанию/ФИО руководителя; `?ordering=name|id` (по умолчанию `name`).
  * Добавил аннотацию **`employees_count`**: считает уникальных активных участников по связям `EmployeeDepartment`, и **прибавляет руководителя**, если он не входит в активные линкы.
  * `partial_update`: смена руководителя требует `change_department_head` **только если значение реально меняется**; остальное редактируется по `manage_department`.
  * `set_head`:

    * `POST /departments/{id}/set_head` — установка нового руководителя (200 + сериализованный отдел).
    * `head_id=null|""` — снятие руководителя (204).
    * Валидация: **разрешаем** назначение любому существующему и активному (`is_active=True`) пользователю; поле `email_verified` не блокирует назначение.
  * `set_member_role`:

    * `POST /departments/{id}/set_member_role` — создание/обновление линка участника с отделом.
    * Валидирует, что `role_id` принадлежит текущему отделу.
    * Возвращает JSON строго в формате, который ждут тесты: `{"employee_id", "role_id", "is_active"}`.

* **DepartmentRoleViewSet**

  * Доступ на изменение ролей отделов только при скоуп-праве **`assign_department_role`** (через `AdminOrDeptAllowed`).
  * Новые эндпоинты:

    * `GET /department-roles/perm_choices/` — отдает справочник прав из `DeptPerm.CHOICES`; при необходимости создаёт записи в БД (id + code + name).
    * `GET /department-roles/{id}/perms/` — текущие права роли (id, code, name).
    * `POST /department-roles/{id}/set_perms` — полная замена прав по `permission_ids` **или** `permission_codes`.
  * Использование константного набора прав **`DeptPerm`**: `MANAGE`, `CHANGE_HEAD`, `ASSIGN_ROLE` (+ `CHOICES`).

* **Сериализаторы**

  * `DepartmentSerializer`: `head` — вложенно (кратко), `head_id` — write-only (`PrimaryKeyRelatedField`), `employees_count` — read-only (берётся из аннотации).
  * `EmployeeBriefSerializer` — для краткого отображения руководителя и кандидатов.

* **Права**

  * Везде применён общий пермишен `AdminOrDeptAllowed(required_code=...)` для скоуп-прав по отделу:

    * `manage_department` — редактирование/удаление отдела,
    * `change_department_head` — назначение/снятие руководителя,
    * `assign_department_role` — операции с ролями и назначениями участникам.

### Frontend / Templates

* **Новый частичный шаблон** `backend/templates/employees/_department_controls.html`

  * Разбит на **3 блока**:

    1. *Управление отделом* (название/описание) — доступно при `can_manage`.
    2. *Роли отдела* — доступно при `can_assign_roles`.

       * Кнопка «Создать роль» открывает панель с формой + модал выбора прав.
       * Редактирование существующей роли: выпадающий список ролей, изменение названия и прав.
       * **Одна кнопка «Сохранить изменения»**: JS сам определяет, что изменилось, и вызывает нужные операции (`op=update` и/или `op=set_perms`) последовательно.
       * Отдельно — «Удалить роль».
       * Выбор прав выполнен через модальные окна, как в блоке с должностями в `_employee_edit.html`; визуал выбранных прав — «чипсы».
    3. *Назначение руководителя* — доступно при `can_change_head` (выпадающий список кандидатов).
  * Разметка и стили согласованы с `_employee_edit.html` (карточки, ios-аккордеон, кнопки, модалки). Дополнительный CSS **не** вводился — всё опирается на базовые стили проекта и Bootstrap.
* **`department_detail` (view для фронта)**:

  * Сначала грузит отдел, затем роли, сотрудников и посты.
  * Собирает `head_choices` и `links` для шаблона.
  * Подтягивает `dept_perm_choices` из `GET /api/v1/department-roles/perm_choices/`.

### Тесты

* Полностью переписаны/обновлены:

  * `tests/api/v1/employees/test_departments.py`

    * Поиск/сортировка по имени.
    * `employees_count` считает head, если его нет в активных линках.
    * `set_head`: валидирует активность; снятие — 204; изменения head без доп. права — 403; сохранение того же head — 200.
    * `set_member_role`: «счастливый путь», запрет чужих ролей, формат ответа.
  * `tests/api/v1/employees/test_department_roles.py` и `test_department_roles_extra.py`

    * Список с фильтром по отделу, стабильный `ordering`.
    * Создание/обновление/удаление с учётом скоуп-прав.
    * `perm_choices`, `perms`, `set_perms` (валидаторы ids/codes).
  * `tests/api/v1/employees/test_department_head_rights.py`

    * Руководитель может править отдел без явных ролей.
    * Руководитель может менять head и **теряет** права после снятия.
* Фиксы фикстур:

  * `make_user` всегда создаёт пользователя с валидным телефоном, `email` в нижнем регистре, отключена отсылка писем; приводим к активному состоянию, чтобы не мешать правам.
* Убраны ошибки:

  * `NameError: User is not defined` → везде используется `get_user_model()`.
  * `NoReverseMatch` в шаблоне → корректные URL’ы: `employees:department_edit`, `employees:department_set_head`, `employees:edit_department_role`.

### Прочее

* Повсеместно добавлены докстринги для новых классов и функций (соответствие внутреннему гайдлайну).
* Без миграций данных/схемы.

**Итог:** добавлены управляемые через API роли отделов с выбором прав (скоуп-прав), унифицированы проверки доступа, восстановлено ожидаемое поведение списка/деталей отделов, подготовлены UI-блоки в едином стиле с «должностями», обновлены и позеленели тесты (остальные регрессии устранены).
2025-09-03 17:17:44 +03:00
1228fb8d87 employees: доработки detail/list шаблонов и управление отделом
- employee_list.html:
  • убраны лишние внешние отступы у карточек сотрудников
  • нормализованы внутренние паддинги (feed-hd, feed-actions, feed-body)
  • добавлены стили для более компактного списка (#empList)

- department_detail.html:
  • добавлены блоки управления отделом для staff и руководителя:
    - редактирование названия и описания (POST /departments/<id>/edit/)
    - назначение/снятие руководителя (POST /departments/<id>/set-head/)
  • секция «Команда отдела» теперь показывает только сотрудников с назначенными ролями
  • реализована форма изменения роли участника (POST /departments/<id>/set_member_role/)
    доступно только staff и руководителю, кроме самого себя
  • исправлены условия в шаблоне (убран синтаксис с круглыми скобками)
  • корректный вывод ФИО и fallback «Сотрудник #id», если данных нет
  • ссылки на сотрудников и отделы нормализованы через {% url %}
  • сообщения «пока нет сотрудников» и «пока нет публикаций» для пустых состояний

- views_front.py:
  • department_detail теперь подтягивает:
    - dept (детали отдела)
    - roles (роли отдела)
    - links (активные employee-departments)
    - posts (публикации отдела)
  • department_edit: редирект исправлен на employees:department_detail
  • department_set_head и set_member_role проксируют данные в API и возвращают флеш-сообщения

- базовый шаблон:
  • уточнено, что стили можно выносить в base.html
  • сохранён общий вид feed-card, но для списка сотрудников применяется упрощённый layout

Итог: появилась полноценная страница отдела —
редактируем основные поля, управляем руководителем,
назначаем роли сотрудникам. Список сотрудников в отделе
отображается компактно и с корректными отступами.
2025-09-02 17:13:25 +03:00