- Добавлено поле 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 (не хранятся в БД)
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
Changed avatar cropper aspect ratio from 1:1 to 3:4 (portrait) for better FaceID recognition.
Improved human silhouette overlay synchronization with crop box during all interactions.
Key changes:
Aspect ratio:
- Changed from 1:1 (square) to 3:4 (portrait)
- Output size: 600x800px (instead of 800x800px)
- Better suited for full body positioning (head, shoulders, torso)
SVG overlay improvements:
- Updated viewBox from 400x400 to 300x400 (3:4 proportions)
- Redesigned human silhouette for portrait format
- Added torso outline up to waist level
- Additional guide line for shoulders
- Better proportions for face/head/shoulders alignment
Synchronization enhancements:
- Added cropstart, cropmove, cropend event handlers
- Overlay now follows crop box during all interactions:
✓ Moving the crop box
✓ Resizing the crop box
✓ Zooming the image
✓ Initial positioning
- Real-time position updates using getCropBoxData()
UI/UX updates:
- Updated instructions: 'Совместите тело с зелёным контуром'
- Face should occupy upper third of frame
- Portrait format 3:4 mentioned in requirements
- Changed label from 'Плечи' to 'Пояс' (waist)
- Templates updated with new size requirements (600x800px)
Technical details:
- minCropBoxHeight: 266px (200 * 4/3 for aspect ratio)
- preserveAspectRatio: none (for proper stretching)
- Event handlers ensure overlay follows crop box smoothly
- No performance impact from additional event listeners
Benefits:
✓ Better for FaceID (full body context)
✓ Standard portrait photo format
✓ More professional appearance
✓ Consistent with ID photo standards
✓ Improved overlay visibility and accuracy
- 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
Changes:
1. employees_list.html - 'Create Profile' button now only visible to staff/superuser
API requires staff/superuser for POST /employees/ (explicit check in create method)
2. department_list.html - Simplified permission check for 'Create Department' button
Changed from: can_create_departments OR perms.employees.add_department OR is_staff
To: is_staff OR is_superuser OR perms.employees.add_department
API uses AdminOrActionOrModelPerms which checks employees.add_department
3. skill_list.html - 'Add Skill' button now requires authentication
API allows any authenticated user to create skills (IsAuthenticated only)
Before:
- Department creation check was redundant with can_create_departments variable
- Skill creation had no check
After:
- All create buttons properly check permissions matching API requirements
- Consistent with feed and documents modules permission patterns
- Added Bootstrap modal for enlarged avatar view
- Made avatar image clickable with cursor pointer
- Added modal trigger to both static and dynamically loaded avatars
- Modal displays full-size avatar centered with dark backdrop
- Close button positioned in top-right corner
- Added intersection observer for automatic loading
- Track nextUrl and hasMore from API response
- Append new employees instead of replacing
- Removed old pagination controls from template
- Show loading spinner while fetching more data
- Упрощен employee_list view - НЕ загружает данные сотрудников
- Сервер отдает только справочники для фильтров
- Убран серверный рендеринг карточек из шаблона
- Добавлен spinner во время загрузки
- Данные загружаются один раз через JavaScript
- Производительность улучшена - нет двойного запроса
- Создан новый JS модуль employeesList.js
- Список сотрудников загружается через API /api/v1/employees/
- Аватары получают правильные глобальные URL
- Сохранена поддержка фильтрации и поиска
- Передается ID текущего пользователя для скрытия кнопок
- Добавлена обработка ошибок загрузки
- Исправлена проблема с localhost URL в аватарах
- Создан новый JS модуль employeeProfile.js
- Аватар сотрудника загружается через API с глобальными URL
- Посты сотрудника загружаются через FeedList с правильными URL медиа
- Добавлены ID контейнеров для динамической загрузки (#employeeAvatar, #employeeName, #employeePosts)
- Подключены скрипты для лайков, комментариев и модала деталей
- Добавлен emoji-picker для форм комментариев
- Добавлен модал для просмотра публикаций
- Исправлена проблема с localhost URL в аватарах и медиа постов
- Заменен лишний div.container на стандартный container для единообразия
- Заменен класс .card на .ios-card в секции активности для устранения конфликтов стилей
- Добавлен card-list.css в base.html для корректного отображения карточек новостей
- Добавлены CSS правила для правильного поведения .row и колонок в grid-layout
- Исправлена проблема с выходом календаря за границы страницы
- Убраны видимые скроллбары у modal-body и modal-content для чистого 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
- ApiClient теперь обрабатывает 401 и 403 ошибки для обновления токена
- Добавлен JWTRefreshMiddleware для проактивного обновления токенов за 5 минут до истечения
- Добавлена настройка JWT_REFRESH_THRESHOLD_MINUTES (по умолчанию 5)
- Исправлена проблема с периодическими 403 ошибками после истечения access token
- Добавлен фильтр created_at__gte в EmployeeViewSet API
- Исправлен параметр is_active -> active в запросе к API
- Добавлено центрирование карусели новых сотрудников (justify-content: center)
- Обновлена документация API endpoints
- Create modal_create_department.html component with simple form (name, description)
- Create createDepartmentModal.js handler for form submission via API
- Update department_list.html to use modal instead of link
- Modal integrates with API v1/departments/ endpoint
- Includes validation, error handling, and success notifications
- Now both employees and departments use consistent modal creation pattern
- Fix department_list.html: use variable for create_url instead of inline yesno with url tag
- Fix feed_list.html: properly handle conditional create_url with with blocks
- Fix document_list.html: use variable for create_modal instead of inline yesno
- Fix request_list_full.html: simplify scope toggle by placing it separately after header
Django templates don't allow nested template tags in include parameters,
so we now prepare variables beforehand using if/with blocks.
- Fix bug in page_header.html: add # to data-bs-target for card create button
- Integrate page_header in department_list.html with search and create card
- Integrate page_header in feed_list.html (no search, staff-only create button)
- Integrate page_header in document_list.html with Mine/All scope toggle
- Integrate page_header in request_list_full.html with filters and scope toggle
- Integrate page_header in chat_list.html with search
- Create _filters.html template for requests_app
- Add page-header.css to all integrated templates for consistent styling
- Replaced esc() and norm() in _department_controls.html with imports
- Replaced norm() in invite_to_department.html with import
- All inline utility duplicates now eliminated across templates
- Total: ~20 lines of duplicate code removed
- All 17 tests passing
- Created static/js/components/teamWheel.js (329 lines)
- Replaced 207-line inline script in department_scripts.html with ES6 module import
- Features: auto-scroll, manual control (mouse/touch/wheel), member focus, fallback
- All 12 tests passing
- Replaced custom applyFilter() function in employees_list.html with ListFilter component
- Reduced code from 16 lines to 11 lines (more declarative)
- Benefits:
* Uses shared, tested ListFilter component
* Automatic debouncing (300ms)
* Cleaner, more maintainable code
* Consistent filtering behavior across app
Analysis of other applyFilter() usage:
- department_scripts.html (2 instances): Complex logic with item selection, form interaction - kept as-is
- search/results.html: Multi-section filtering with radio buttons - kept as-is
- document_list.html: Array filtering, not DOM filtering - kept as-is
ListFilter component is ready for use in new features and templates.
Tests: All 17 tests passing ✅
Календарь (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).
Большое обновление календаря. Объединили модель событий, добавили полноценную повторяемость (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.
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() во вьюсетах и добавить в роутер.
— Переверстал 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: нет
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: нет