14 Commits

Author SHA1 Message Date
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
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
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
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
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
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
da183c8fbd мелкие доработки тестов и шаблонов 2025-08-29 17:03:18 +03:00
cfc76e43b7 **feat(employees): Position & Department API, доработка прав головы отдела, усиление пермишенов и полный набор тестов**
### Что сделано

**Position API**

* Добавлен `PositionViewSet` с CRUD и экшенами:

  * `POST /positions/{id}/set_groups`
  * `POST /positions/{id}/add_groups`
  * `POST /positions/{id}/remove_groups`
  * `GET  /positions/{id}/permissions` (агрегирует permissions из связанных групп)
* Чтение: `IsAuthenticated`; запись: staff/superuser **или** наличие модельных прав (`employees.add|change|delete_position`), экшены групп — кастомное право `employees.assign_position_groups`.
* Выставление требуемого права через `view.required_perm_code` в `get_permissions()`.
* Поиск/сортировка: `?search=`, `?ordering=`.
* **Отключена пагинация** (возвращаем plain list, как ожидают тесты).
* Сериализатор включает `groups` и `groups_verbose`.
* Роутер: зарегистрирован `positions` в `api:api_v1`.

**Кастомный пермишен для Position**

* В `Position.Meta.permissions` добавлен `("assign_position_groups", "Can assign auth groups to positions")`.
* В `employees.apps.EmployeesConfig.ready()` добавлен `post_migrate`-хук для гарантированного создания этого пермишена (страхует тестовую БД).

**DepartmentRole API**

* Добавлен `DepartmentRoleViewSet` (CRUD + экшены управления правами роли):

  * `GET  /department-roles/{id}/perms`
  * `POST /department-roles/{id}/set_perms`
  * `POST /department-roles/{id}/add_perms`
  * `POST /department-roles/{id}/remove_perms`
* Фильтр: `?department=<id>` (+ безопасная обработка нечислового значения → пустой список).
* Права:

  * CRUD: требуется `manage_department` (через главу отдела, роль с правом, staff/superuser **или** глобальное модельное право `employees.manage_department`);
  * управление правами роли: `assign_department_role` (аналогично).
* Сериализатор `DepartmentRoleSerializer` с полями `permissions` и `permissions_verbose`.

**Пермишены и проверки доступа**

* `IsDeptManagerForWrite`:

  * Научили работать с объектами, у которых есть `obj.department` (например, `DepartmentRole`).
  * Добавили поддержку **глобальных** модельных прав (`user.has_perm("employees.<code>")`), чтобы уполномоченные пользователи могли управлять **любым** отделом.
* `IsStaffOrHasModelPerm`:

  * Перед `user.has_perm(...)` сбрасываем кэш прав (`_perm_cache`, `_user_perm_cache`, `_group_perm_cache`) — важно для тестов, где права выдают в ходе сценария.

**Department.save(): членство и права главы**

* Перевели создание/активацию линка `EmployeeDepartment` для **нового** главы на **синхронный** режим (без `on_commit`) при **смене** главы.
* **Не** создаём линк на первичном создании отдела с уже заданным `head` (чтобы не ловить `UNIQUE` в кейсах, где тесты добавляют линк вручную).
* При смене главы:

  * для **нового** главы — гарантируем наличие активной ссылки (`is_active=True`, `date_from` при необходимости);
  * для **старого** главы:

    * если `head -> None` — гарантируем наличие ссылки и деактивируем её (`is_active=False`, проставляем `date_to`);
    * если смена `A -> B` — гарантируем, что ссылка старого главы существует и остаётся активной (бизнес-логика и тесты).
* Таким образом, права «главы» корректно переходят и отражаются в ссылках членства.

**Маршрутизация**

* Зарегистрированы в `api/v1/urls.py`:

  * `positions`
  * `department-roles`

**Тесты**

* **positions:** полный набор на права, экшены групп, поиск/сортировку, валидацию payload’ов; фикс хелпера телефонов (уникальные номера).
* **department-roles:** основной набор + расширенный (`extra`) на:

  * роли с правами внутри отдела (manage/assign),
  * глобальные модельные права (доступ к любому отделу),
  * кросс-доступ (роль в отделе A не даёт прав в отделе B),
  * семантику `set_perms` (полная замена, дедуп), идемпотентность `add/remove`,
  * валидацию payload’ов, 404 при удалении несуществующей роли.
* **department\_head\_rights:** сценарии смены/снятия главы:

  * старый глава теряет права (если нет роли),
  * новому создаётся активная ссылка и права работают сразу,
  * снятие головы → деактивация ссылки + `date_to`,
  * у старого главы права сохраняются, если назначена роль с `manage_department`,
  * смена головы не деактивирует ссылку старого главы по умолчанию.
* Все тесты зелёные.

### Технические детали / файлы

* `employees/models.py` — правки `Position.Meta.permissions`, логика `Department.save()`.
* `employees/apps.py` — `post_migrate` для `assign_position_groups`.
* `employees/permissions.py` — обновлён `IsDeptManagerForWrite`, `IsStaffOrHasModelPerm`.
* `api/v1/employees/serializers.py` — `PositionSerializer`, `DepartmentRoleSerializer`.
* `api/v1/employees/views.py` — `PositionViewSet` (без пагинации), `DepartmentRoleViewSet` (CRUD + экшены), безопасный фильтр `department`.
* `api/v1/urls.py` — регистрация роутеров.
* `tests/api/v1/employees/` — новые файлы тестов и доработки существующих.

### Миграции/заметки

* Требуется миграция для добавления `assign_position_groups` в `Position.Meta.permissions`.
* `post_migrate` гарантирует наличие права в тестовой БД.
* После деплоя: `python manage.py migrate`.

---

Таким образом, добавлены полноценные API для **Position** и **DepartmentRole**, корректно выстроена модель прав (глава/роль/глобальные), а сценарии смены/снятия главы полностью покрыты тестами.
2025-08-25 17:01:58 +03:00
5af1f9ec91 feat(api): регистрация/верификация через API, whitelist в middleware, iOS-экран подтверждения выхода
**Что сделано**

* **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`).

**Миграции**: нет.
2025-08-20 17:49:38 +03:00
b401e86249 доработка стилей 2025-08-20 11:19:02 +03:00
2c85086f78 feat(feed,employees,communications): dept news, invites, chat sync; forms/admin cleanup; templates polish
## Что сделано

### Модели
- **employees**
  - : теперь включает отделы, где сотрудник является **руководителем**.
  - Уточнены :
    -  →  у ,  у .
  - : добавлены удобные геттеры , ; улучшён .
  - Модели , ,  приведены к актуальным полям (без start_date/end_date/level и т.п., чтобы не конфликтовать с формами/админкой).

- **feed**
  - Вынесены константы типов постов: , ,  + .
  - Жёсткие **CheckConstraint**:
    - для департаментских постов ,
    - для остальных — .
  - Индексы по , , .

- **communications**
  - :
    -  для  (чаты отдела удаляются вместе с отделом).
    - Метод : активные сотрудники отдела + **руководитель**; для глобального — все активные.
    - Запрет удаления **основных** чатов (global/department), но обычные удаляются нормально.

### Админка
- Приведена в соответствие моделям: убраны несуществующие поля, добавлены фильтры/поиск по реальным полям, inlines для отделов (связи сотрудников).
- Исправлены ошибки system check (E033/E108/E116).

### Формы
- , : валидация аватара, корректные required/optional, PhoneNumberField для WhatsApp/WeChat.
- ,  (динамический queryset сотрудников, не состоящих в отделе).
- , , : соответствуют актуальным моделям, с базовой валидацией диапазонов дат (если применимо).

### Вьюхи (employees)
- Переписаны аккуратно и проще:
  - Списки/детали отделов на CBV (, ), в контексте — сотрудники, новости отдела, новые сотрудники.
  - Профиль и чужой профиль использует общий шаблон с подключаемыми блоками.
  - : доступ только **админу/руководителю**; добавление связи  и автодобавление участника (и руководителя) в **чат отдела**.
  - : подготовлено к фильтрации по .

### Вьюхи (feed)
-  — **универсальная** форма:
  - контекст определяется автоматически из GET:  | ,
  - скрываем поля , чтобы пользователь не ломал контекст,
  - редирект — на **красивые** страницы: главная лента / страница отдела / лента сотрудника.
- : только админ — для компании, только **руководитель отдела** — для департамента.
- : комментарии + форма добавления.
- **Добавлены** CRUD для постов и комментариев:
  - ,  — автор/админ/руководитель отдела.
  - ,  — только автор комментария.

### URL-ы
- : добавлены маршруты , , , .
- : все нужные эндпоинты в рабочем состоянии,  на месте.

### Шаблоны
- : опрятная шапка, линк на «Все сотрудники отдела», кнопка «Пригласить» (для админа/руководителя), внизу — include **новостей отдела**.
- : приведение к общему стилю, кнопка «Новая публикация» с автоподстановкой department.
- : отображает бейдж контекста (Компания/Отдел/Личное), **одна** кнопка «Отмена»,  приходит из вьюхи.
- : контекстная кнопка «Назад к ленте» (определяется автоматически), кнопки **Редактировать/Удалить** (по правам), комментарии с кнопками **ред./удал.** у автора.
- Исправлены include-пути (везде , без обратных слешей).

## Почему всё ок
- Нет циклических импортов,  выровнены.
- Никакой магии в шаблонах: вся логика определения контекста — во вьюхах.
- Чаты отдела удаляются вместе с отделом; основные чаты защищены от удаления.
- Учитываем **руководителя отдела** везде, где нужно (новости, чат, права).

## Что проверить локально
1) Миграции:
   - python manage.py makemigrations
   - python manage.py migrate
2) Создать отдел, назначить руководителя.
3) Пригласить сотрудника → автоматически попадёт в чат отдела.
4) Создать новости:
   - с главной → тип company,
   - со страницы отдела → тип department, поле Department скрыто,
   - из ленты сотрудника → тип employee.
5) Закрепление новостей:
   - компания — только staff,
   - отдел — только head.
6) Редактирование/удаление постов и комментариев — по правам.

## Примечания
- Если ранее в БД были посты с типом 'personal' — вручную мигрируйте на 'employee'.
2025-08-08 15:16:19 +03:00
Игорь Надеин
a42204dda0 0 2025-08-05 15:30:41 +03:00