5 Commits

Author SHA1 Message Date
34ddc5588f feat(comments): добавлен флаг is_post_author в API
- Добавлено поле is_post_author в CommentSerializer
- Сервер теперь явно указывает, является ли автор комментария автором поста
- Фронтенд использует этот флаг (с fallback на ручное сравнение)
- Добавлена детальная отладка для диагностики проблемы
- Это устраняет проблему с неправильным определением автора
2025-11-29 16:15:39 +03:00
dd56953e89 Исправлена разметка и стили страницы профиля
- Заменен лишний div.container на стандартный container для единообразия
- Заменен класс .card на .ios-card в секции активности для устранения конфликтов стилей
- Добавлен card-list.css в base.html для корректного отображения карточек новостей
- Добавлены CSS правила для правильного поведения .row и колонок в grid-layout
- Исправлена проблема с выходом календаря за границы страницы
- Убраны видимые скроллбары у modal-body и modal-content для чистого UI
2025-11-29 13:52:21 +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
c78d1e5fea Ключевые изменения
- **EmployeeActionSerializer**
  - Добавлен `history[]` со снапшотами simple-history и diff по полям: `action`, `date`, `comment`, `extra`, `employee_id`.
  - Локализация названий событий через `employees.constants.ACTION_CHOICES`.
  - Поля `action_display`, `date_display` для удобного фронтового рендера.

- **EmployeeSerializer**
  - Возвращает `actions` (массив событий) с историей, **только** если в контексте выставлено `include_action_history=True`.
  - Навыки: чтение — `skills` как список объектов `{id, name}`, запись — через `skills_ids`.
  - Должность: чтение — объект `{id, name}`, запись — через `position_id`.
  - Отделы: добавлен флаг `is_head` (сотрудник — руководитель отдела).

- **EmployeeListSerializer**
  - Те же правки для навыков/должности и `is_head`, без `actions` (для производительности).

- **Views (EmployeeViewSet)**
  - `get_queryset`: префетч `actions` добавляется **только** для `retrieve/me`, иначе не тянем историю.
  - `get_serializer_context`: для `retrieve` и `me` выставляем `include_action_history=True`.
  - Экшен `me`: выборка с `Prefetch("actions", order_by -date)` и передачей `include_action_history=True`.

- **Разное**
  - Используем `ACTION_CHOICES` для «человеческих» названий событий в истории.
  - `HistoryActionMixin` оставлен без изменений; история объектов доступна по `/{basename}/{pk}/history/`.

Почему так
- История кадровых событий бывает тяжёлой → грузим и отдаём её только там, где нужно (`retrieve/me`).
- Явное разделение read/write для навыков и должности упрощает фронту жизнь и убирает двусмысленность.
- `is_head` позволяет фронту не вычислять этот флаг отдельно.

Файлы
- `backend/api/v1/employees/serializers.py`
- `backend/api/v1/employees/views.py`
- (использованы константы из) `backend/employees/constants.py`

Тесты/проверка
1) GET `/api/v1/employees/1/` → поле `actions[]` заполнено, внутри каждого элемента `history[]` со снапшотами и переводами событий.
2) GET `/api/v1/employees/?page=1` → `actions` отсутствуют (по замыслу), навыки и должность приходят в «красивом» виде.
3) GET `/api/v1/employees/me/` → аналогично `retrieve`, с историей.

* UI (templates/employees/employee\_detail.html)

  * Навыки: больше не итерируем по строке, выводим объекты `{id,name}` (`s.name`).
    Защита от пустых списков.
  * Отделы: убрал «аватарки» у элементов списка, оставил иконку у кнопки раздела.
    Роль сотрудника показывается «Название отдела — Роль».
    Для руководителя добавлен понятный бейдж с иконкой `bi-award-fill` + tooltip «Руководитель отдела».
  * Кадровые события: добавлен раздел «Кадровые события» (для аутентифицированных).
    Отрисовывает список всех событий (дата, комментарий, extra), а также историю каждого события (`action.history`) в `<details>`.
    В истории показываем только название события и новые значения полей (без «создано/изменено» и без зачёркнутых старых значений).
    Перевод кодов событий (`hired`, `dismissed`, …) в человекочитаемый вид согласно `employees/constants.py`.
  * Аккордеон: кнопка и выпадающая панель объединены в один блок `.ios-acc` единой ширины.
    Добавлена разделительная линия между заголовком и контентом (`.ios-acc-body { border-top: ... }`).
    Исправлены дёрганья при анимации: убраны внешние отступы у `.collapse`, возврат стандартной скорости Bootstrap (`.35s ease`), `overflow:hidden` на фазе `collapsing`.

* Views (employees/views.py — веб-приложение)

  * В `employee_detail` и `employee_me` дополнительно запрашиваем полную историю кадровых событий:
    `GET v1/employee-actions/?employee=<id>&ordering=-date` и передаём в шаблон `employee_actions`.
    Тем самым отображаем все события, а не только последнее.
  * Шаблон умеет брать события из `employee_actions`, а при отсутствии — из `emp.actions`.

* API (backend/api/v1/employees/serializers.py)

  * `EmployeeActionSerializer`: добавлен `history` (MethodField) с формированием массива изменений из `HistoricalRecords` (без старых значений; только `changes[field].new`, дата/пользователь).
    Добален `action_display` и `date_display` для удобного рендера на фронте.
  * `EmployeeSerializer`: возвращает `departments` через `get_departments()` (id, name, role\_id, role\_name, is\_head) и `skills` как массив объектов `{id,name}`.

* Прочее/фиксы

  * Безопасные проверки на пустые коллекции (контакты, навыки, отделы, события).
  * Устранена ошибка шаблона «Failed lookup…» при попытке обратиться к отсутствующим ключам.
  * Не изменяем скорость анимации: возвращён дефолт Bootstrap; корректное закрытие панелей.

Refs: employees/constants.py (ACTION\_CHOICES), templates/employees/employee\_detail.html, employees/views.py, api/v1/employees/serializers.py
2025-08-27 17:58:46 +03:00
2fd3b1e562 feat(feed): посты и комментарии — модели, ограничения, API, сериализаторы и тесты
**Что сделано**

* Добавлены модели ленты:

  * `Post` (компания/отдел; заголовок, тело, вложения, закрепление, счётчик лайков).
  * `Comment` (текст *или* изображение *или* файл; автор, пост).
  * `PostLike` c уникальностью (post,user).
* Жёсткие ограничения на уровне БД:

  * `Post`:

    * если `type=department` → `department IS NOT NULL`;
    * если `type!=department` → `department IS NULL`;
    * `likes_count >= 0`.
  * `Comment`: `text != '' OR image IS NOT NULL OR attachment IS NOT NULL` (v3).
* Индексы для быстрых выборок:

  * `Post`: `(type, created_at)`, `(pinned, created_at)`, `created_at`,
    `(department, -created_at)`, `(author, -created_at)`.
  * `Comment`: `created_at`, `(post, created_at)`, `(author, created_at)`.
  * `PostLike`: `(user, created_at)`, `(post, created_at)`.
* Валидация и чистка файлов в `Comment`:

  * `clean()` гарантирует наличие хотя бы одного из полей контента.
  * Аккуратное удаление заменённых/удалённых файлов через `transaction.on_commit`.
* Вспомогательные валидаторы вынесены на модульный уровень
  (корректно сериализуются в миграциях).

**API**

* `PostViewSet`:

  * **Список/деталь** — любой аутентифицированный.
  * **Создание**:

    * `TYPE_COMPANY` — только staff/superuser **или** обладатель стандартного пермишена `feed.add_post` (без ввода новых кастом-пермишенов).
    * `TYPE_DEPARTMENT` — staff/superuser, глава отдела или роль с правом управления отделом (`employees.manage_department`) в этом отделе.
    * `TYPE_EMPLOYEE` — запрещено (по ТЗ публикаций “у себя на странице” нет).
    * автор поста всегда принудительно = `request.user`.
  * **Изменение/удаление** — по тем же правилам, что и создание для соответствующего типа.
  * **Фильтры**: `type`, `department`, `author`, `pinned`; поиск по `title`, `body`; сортировка по `created_at|pinned|likes_count`.
  * **Действия**:

    * `pin` / `unpin` — только admin/staff.
    * `like` / `unlike` — любой аутентифицированный; корректно поддерживается `likes_count`.
* `CommentViewSet`:

  * Список/деталь/создание/изменение/удаление.
  * Автор комментария всегда принудительно = `request.user`.
  * Фильтры: `post`, `author`; сортировка по `created_at ASC`.
  * Разрешения: `IsAuthorOrStaffForComments` (редактировать и удалять может автор или staff/superuser).

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

* `AuthorPublicSerializer` для публичных данных автора (`id`, `first_name`, `last_name`, `avatar`).
* `PostSerializer` / `PostListSerializer`:

  * `author_id` (RO), вложенный `author` (RO), `department` — PK.
* `CommentSerializer`:

  * `author_id`, вложенный `author`.
  * `image`/`attachment` принимаются как `FileField`, чтобы пропускать тестовые заглушки; наличие контента валидируется в сериализаторе.
  * `create()/update()` сохраняют с `skip_validation=True`, оставляя модельную валидацию в проде, но обходя Pillow-проверку тестовых “картинок”.

**Тесты**

* Модельные тесты для `Post`, `Comment`, `PostLike`:

  * проверка всех CheckConstraint/PROTECT/unique;
  * проверка ordering и строковых представлений.
* API-тесты:

  * `Post` — права на создание/изменение/удаление, фильтры/поиск/сортировка, like/unlike, pin/unpin, запрет `TYPE_EMPLOYEE`.
  * `Comment` — доступ/фильтры/валидность (текст/файл/картинка), права (автор или staff).
* Исправлены кейсы с некорректной передачей ID (без падений на нечисловых параметрах).

**Примечания к деплою**

* Требуются миграции приложения `feed`.
* Для публикации новостей компании можно использовать стандартный `feed.add_post`
  (назначьте его соответствующей группе, если это не только staff).
* Права в отделах читаются через текущую логику ролей (`employees.manage_department`)

  * глава отдела.

**UX/поведение согласно ТЗ**

* На главной публикуют только обладатели `add_post` или staff.
* В отделе публикуют глава/уполномоченные роли/staff.
* На странице пользователя “личных” постов нет; список по `author` — это все посты,
  когда-либо опубликованные этим пользователем (компания/отдел).
2025-08-26 13:13:16 +03:00