54 Commits

Author SHA1 Message Date
8363cbd541 feat: Complete CSS to SCSS migration (100%)
- Migrated all 28 CSS components to SCSS architecture
- Deleted 29 source CSS files (kept only compiled app.css & bootstrap-custom.css)
- Created 30 SCSS component modules in scss/components/
- Cleaned 24 templates from duplicate CSS links
- Updated main.scss with new imports (layout-spacing, spacing-utils, rightbar-calendar)
- Filled empty _navbar.scss with content
- Refactored css/ folder: removed obsolete files, updated README.md
- Created comprehensive documentation (migration complete, quick reference, reports)

Migration Statistics:
- Components migrated: 28 (~4,768 lines SCSS)
- CSS files deleted: 29
- Templates cleaned: 24
- app.css size: 95 KB (compiled from SCSS)
- bootstrap-custom.css: 301 KB
- Total: 396 KB

Phase 6 Complete: 100% SCSS architecture achieved ✅
2026-01-15 13:48:11 +03:00
7838a55ef4 feat: Replace CDN with local vendor libraries + complete Web Push setup
- Замена всех CDN зависимостей на локальные файлы:
  * Bootstrap 5.3.3 (CSS + JS)
  * Bootstrap Icons 1.11.3 (CSS + шрифты woff/woff2)
  * FullCalendar 6.1.8 (JS + локали)
  * Cropper.js 1.6.1
  * emoji-picker-element 1.21.3

- Web Push Notifications:
  * VAPID ключи настроены в settings.py
  * Service Worker (sw.js) в корне backend/
  * API endpoints: /api/v1/notifications/push/
  * Auto-request permission при загрузке страницы
  * Simplified subscribe logic (Phase 1 refactor)
  * resetEverything() для восстановления после ошибок

- Документация:
  * backend/docs/guides/WEB_PUSH_QUICK_TEST.md
  * backend/docs/guides/FIX_NO_PUSH_SUBSCRIPTIONS.md
  * backend/docs/troubleshooting/WEB_PUSH_OFFLINE_ISSUE.md

- Обновлено 10 шаблонов для использования local vendor
- Устранены проблемы с CORS и integrity checks
- Все иконки Bootstrap Icons теперь отображаются корректно
2026-01-06 09:22:16 +03:00
36f756988b fix(requests): улучшены названия фильтров дат и исправлена логика фильтрации 2025-12-29 15:42:00 +03:00
8ebe241174 feat(requests): добавлены фильтры по сотруднику и периоду дат 2025-12-29 15:32:27 +03:00
0b959f33e0 refactor(requests): заменён фильтр 'Область' на nav-pills как в документах 2025-12-29 15:25:18 +03:00
f6a1315d76 feat(requests): текст темы в списке заявлений теперь ссылка на страницу деталей 2025-12-29 14:47:13 +03:00
0890d02efc refactor: переделать страницу деталей заявления с нуля со стилями из списка
- Полная переделка шаблона request_detail.html с единообразными стилями
- Новая структура: основная карточка (8 колонок) + боковая панель комментариев (4 колонки)
- Единообразные иконки типов заявлений (calendar-check, capsule, umbrella-fill и т.д.)
- Единообразные бейджи статуса и типа как в списке
- Информационные блоки для автора, описания, получателей, одобрившего
- Форма комментариев с правильным стилем message-field--compact
- Emoji picker с поддержкой dropdown-menu поверх контента
- AJAX обработка отправки и удаления комментариев
- Новый CSS файл request-detail.css с адаптивностью
- Добавлена функция _add_type_display() в views.py для отображения типа

Улучшения:
- Единство дизайна со страницей списка заявлений
- Правильное позиционирование emoji picker (fixed positioning)
- Sticky боковая панель с комментариями
- Форма комментария как в ленте (message-field--compact)
- Кнопки одобрения/отклонения с правильными иконками
2025-12-29 14:27:47 +03:00
dd219d4752 Исправлена view request_detail
- Добавлены comments в контекст
- Добавлен prefetch recipients и cc_users в DETAIL_REQUEST_QS
- Теперь все данные передаются правильно
2025-12-29 12:31:37 +03:00
be21b9ca4a Полностью переработана страница деталей заявления
Исправлено:
- Добавлена правильная структура card-header с аватаром 48x48px
- Добавлены все необходимые детали: получатели, копия, согласующий
- Исправлены стили для badge и card-body
- Добавлена корректная форма комментария с emoji picker и auto-expand
- Подключены правильные JavaScript компоненты
- Исправлен формат дат (d.m.Y H:i)
- Добавлена передача can_process в контексте view

Добавлено:
- Иконки для каждого раздела (получатели, копия, согласующий)
- Ссылки на карточки сотрудников
- Правильные Badge с subtle цветами
- Форма комментария с message-field компонентом
2025-12-29 12:25:42 +03:00
1349ebce35 fix(requests): временно отключена кнопка 'Отозвать'
URL request_cancel не подключен в urls_front.py
Закомментирована кнопка до добавления функционала
2025-12-29 12:00:52 +03:00
532098499b fix(requests): исправлен namespace для URL request_cancel
Изменено: requests:request_cancel → requests_app:request_cancel
Исправлена ошибка NoReverseMatch при открытии страницы заявления
2025-12-29 11:59:03 +03:00
087cff7901 fix: исключен approver из получателей уведомлений о решении
- Approver (тот кто принял решение) не получает уведомление о своем действии
- Исключены employee.id и approver_id из recipients при approve/reject
- Упрощен signal create_request_notifications (убран лишний запрос в БД)
- Теперь передается актуальный request_obj с установленным approver_id
2025-12-29 11:02:39 +03:00
3941b5c963 fix: уточнен селектор формы решения в request_process.html 2025-12-29 10:15:45 +03:00
849545b11c feat: AJAX обработка формы решения на странице заявления без перезагрузки 2025-12-29 10:01:32 +03:00
eb70384928 � Мобильная оптимизация и новый тип заявления 'Отгул'
MOBILE FIXES:
- request-list.css: переделан мобильный layout с flexbox вместо grid
- page-header.css: оптимизирован padding/margin для мобильных экранов
- requestListHandler.js:
  * Добавлен AbortController с таймаутом (10/15 сек для мобильных)
  * Обработка offline/online событий сети
  * Информативные сообщения об ошибках (отсутствие интернета, таймаут)
  * Адаптивный Intersection Observer (rootMargin 200px для мобильных)
  * Лучшая обработка network failures

UI/UX:
- Скрыт счётчик на экранах < 480px
- Контент теперь отображается на всех разрешениях
- Исчезновение спиннера при ошибке сети
- Визуальное различие между ошибками загрузки

NEW FEATURE:
- Добавлен новый тип заявления 'Отгул' (day_off)
- Обновлена миграция 0009_alter_request_type
- Добавлен во все формы создания/редактирования
- Добавлен в фильтры списка
2025-12-29 10:00:32 +03:00
4d03d027f4 fix: добавлена JavaScript обработка форм на странице заявления
- Форма добавления комментария теперь обрабатывается через AJAX
- Форма удаления комментария с подтверждением
- Форма изменения статуса с подтверждением
- После успешных операций страница перезагружается
- Отображение состояния загрузки (spinner) на кнопках
- Обработка ошибок с alert уведомлениями
2025-12-29 09:28:47 +03:00
434de5c344 fix: приведен шаблон request_detail к стилям проекта
- Использованы компоненты card-list и message-field
- Убраны inline стили, используются классы проекта
- Улучшена структура и читаемость
- Добавлен правильный layout с container-fluid
- Комментарии теперь используют card-list-item класс
- Форма комментариев использует message-field компонент
2025-12-29 09:24:30 +03:00
ee4184953b fix: заменены все request_obj.pk на request_obj.id
- API возвращает 'id', а не 'pk'
- Исправлены все 3 URL: request_cancel, request_comment_delete, request_comment_add
2025-12-29 09:20:53 +03:00
7ff425b707 fix: исправлен namespace в URL шаблона request_detail
- Заменено 'requests_app:' на 'requests:' во всех URL
- В urls_front.py app_name = 'requests', не 'requests_app'
- Исправлена ошибка NoReverseMatch
2025-12-29 09:19:14 +03:00
6621d933e1 fix: исправлен доступ к author в комментариях
- Заменено c.author_name на c.author.full_name|default:c.author.display_name
- Заменено c.author_id на c.author.id (вложенный объект)
- API возвращает вложенный объект author, а не плоские поля
2025-12-29 09:17:50 +03:00
7bd93d99c7 fix: исправлена ошибка доступа к полю username в шаблоне
- Заменено request_obj.employee.username на employee.display_name
- API возвращает full_name и display_name, но не username
- Исправлена ошибка VariableDoesNotExist при рендеринге шаблона
2025-12-29 09:16:48 +03:00
240f734c92 feat: requests detail view - stage 5: функциональность изменения статуса
- Добавлен обработчик для смены статуса заявления в JavaScript
- Интегрирована форма смены статуса в шаблон request_detail.html
- Форма видна только администраторам (can_process=True)
- Поддерживаются переходы: pending->approved/rejected, *->cancelled
- Добавлена валидация и подтверждение перед изменением статуса
- Используются API endpoints: /api/v1/requests/{id}/approve/, reject/, cancel/
- Автоматическое обновление содержимого модали после смены статуса
- Отображение статуса обновления (loading, success, error) в UI
2025-12-26 16:27:09 +03:00
48a96f495e feat: requests detail view - stage 4: функциональность комментариев
- Добавлена функция удаления комментариев в views_front.py
- Добавлена обработка удаления в URL routing
- Расширен JavaScript модуль для работы с добавлением/удалением комментариев
- Добавлена обработка формы добавления комментариев с JSON
- Улучшена обработка ошибок при добавлении комментария
- Добавлена инициализация emoji picker в модали
- Обновлен счетчик комментариев при добавлении/удалении
- Обновлены шаблоны для работы с данными из API
- Добавлена поддержка перезагрузки содержимого модали после каждого действия
2025-12-26 16:25:18 +03:00
003860f892 feat: requests detail view - stage 3: frontend разработка
Этап 3 разработки включает:

✅ Создан JavaScript модуль requestDetail.js:
  - Класс RequestDetailModal для управления модальным окном
  - Функция open(requestId) для открытия заявления
  - Функция loadData(requestId) для загрузки через AJAX
  - Обработка ошибок (404, 403)
  - Поддержка добавления комментариев в модали
  - initRequestDetailModal() для инициализации на странице список

✅ Добавлено модальное окно в request_list_full.html:
  - HTML структура модального окна requestDetailModal
  - ID контейнера для содержимого: requestDetailContent
  - Bootstrap 5 modal с размером modal-lg
  - Scrollable модаль для больших содержимо

✅ Обновлена инициализация скриптов:
  - Подключение модуля requestDetail.js
  - Инициализация RequestDetailModal при загрузке страницы
  - Сохранение экземпляра для использования на странице

✅ Интеграция с кликами:
  - Подготовка к интеграции с существующей логикой списка
  - Возможность открытия заявления из списка или по прямой ссылке

Готово для следующего этапа 4: Функциональность комментариев
2025-12-26 16:03:25 +03:00
f19862ec6b feat: requests detail view - stage 2: backend разработка
Этап 2 разработки включает:

✅ Создан RequestDetailView класс в views_front.py:
  - Наследуется от LoginRequiredMixin, TemplateView
  - Загружает данные о заявлении через API (/api/v1/requests/{pk}/)
  - Загружает комментарии к заявлению
  - Проверяет права доступа (обрабатывает 404, 403)
  - Форматирует даты для отображения

✅ Обновлены URLs (urls_front.py):
  - Добавлен маршрут: path('<int:pk>/', RequestDetailView.as_view(), name='request_detail')
  - Импортирован RequestDetailView

✅ Обновлен шаблон request_detail.html:
  - Раскомментированы комментарии (были закрыты)
  - Добавлена обработка ошибок (404, 403)
  - Оформлено отображение заявления и комментариев
  - Включен функционал добавления комментариев

� URLs в notification_signals.py уже правильные (проверено)

Готово для следующего этапа 3: Frontend разработка
2025-12-26 15:59:16 +03:00
592a799cc7 test: add my_requests.html and IP restriction test scripts 2025-12-05 16:18:45 +03:00
c364d0635a fix(requests): сохраняем шапку таблицы при ajax загрузке, исправлен порядок, счетчик комментариев, preventDefault для Enter
- Добавлен отдельный контейнер для данных заявлений
- JS работает только с #reqListContent, шапка всегда видна
- Исправлен порядок заявлений (order_by)
- comments_count через annotate
- Исправлен обработчик формы комментария (preventDefault)
- Удаление начального спиннера после загрузки
2025-12-05 15:59:21 +03:00
19757d2f98 feat: полная AJAX-загрузка списка заявлений с infinite scroll
- Обновлен requestListHandler.js: рендеринг в табличном формате
- Сохранена структура таблицы req-table-header + req-row
- Шаблон: удален SSR-цикл, сохранена шапка таблицы
- Добавлены данные пользователя в dataset (userId, canProcess)
- Включен useAjaxList режим в initRequestListPage
- Удалены старые CSS для req-item (не используются)
- Структура совместима с существующими стилями request-list.css
2025-12-05 15:00:36 +03:00
5d1fd340fb refactor: использование attachment_url из API + AJAX компонент для заявлений
- Шаблоны используют obj.attachment_url напрямую из API
- Удален templatetag absolute_url (больше не нужен)
- Упрощена проверка наличия вложения
- Создан requestListHandler.js для AJAX загрузки с infinite scroll
- Обновлен requestList.js для поддержки AJAX режима
- Подход аналогичен приложению документов
2025-12-05 14:53:52 +03:00
32f8800165 refactor: удалены неиспользуемые файлы заявлений
- Удалены шаблоны: my_requests.html, all_requests.html (старые)
- Удален JS компонент: requestListHandler.js (неиспользуемый)
- Обновлены views: my_requests() и all_requests() теперь редиректят на requests:request_list
- Обновлены все редиректы с requests_app:my_requests на requests:request_list
- Основная страница заявлений: /requests/ (request_list_full.html)
2025-12-05 14:33:15 +03:00
897fdb587a fix: использование абсолютных URL для вложений в заявлениях
- Добавлен templatetag absolute_url для преобразования относительных путей
- Обновлены все шаблоны заявлений: request_list_full, request_detail, request_process
- Теперь все ссылки на вложения используют {% absolute_url %} для полных URL
- Добавлен use_url=True для attachment_url в сериализаторе API
- Исправлена проблема с локальными URL вместо доменных
2025-12-05 14:25:55 +03:00
462ebdb41f fix: добавлено поле attachment_url в RequestReadSerializer
- Добавлен attachment_url для полного URL к прикрепленному файлу
- Исправлен шаблон my_requests.html (удалены лишние теги)
- Теперь API заявлений работает аналогично API документов
2025-12-05 13:42:59 +03:00
4822eee65e feat: добавлена бесконечная прокрутка для заявлений
- Создан компонент requestListHandler.js для AJAX загрузки
- Упрощен view my_requests - данные загружаются через API
- Обновлен шаблон my_requests.html на AJAX загрузку
- Добавлен поиск по типу заявления и комментарию
- Intersection Observer для автоматической подгрузки
- Фильтрация по статусу заявления
- Удалена серверная пагинация
- Добавлены спиннеры загрузки
2025-12-05 13:31:55 +03:00
5ca0feb9eb feat(requests): улучшена UI списка заявлений и добавлены расширенные фильтры
Стили комментариев:
- Приведены к единому стилю с лентой новостей
- Убрана фоновая подложка блока комментариев
- Убрана разделительная линия между заявлением и комментариями
- Обновлена структура: comment-header, comment-body, comment-separator
- Добавлены кликабельные ссылки на профили авторов

Исправления формы комментариев:
- Исправлена перезагрузка страницы при нажатии Enter
- Улучшена обработка submit события (preventDefault + stopPropagation)
- Правильная проверка формы через matches() вместо closest()

API endpoints:
- Использование правильных API endpoints для комментариев
- Переход с /requests/comments/{id}/ на /api/v1/requests/{id}/comments/
- Устранено дублирование запросов к старым и новым endpoints

CSRF токены:
- Добавлена функция getCsrfToken() для извлечения из cookie
- CSRF токен добавлен во все POST запросы к API
- Исправлены ошибки 403 Forbidden для действий (cancel, approve, reject)

Расширенные фильтры:
- Добавлен фильтр 'Все доступные' (свои + адресованные + с правами)
- Добавлен фильтр 'Мои заявления' (только созданные мной)
- Добавлен фильтр 'Мне адресовано' (recipients + cc_users + sent_to_all_department)
- Поддержка параметра addressed_to_me=true в API
- Обновлена логика views_front.py для обработки новых фильтров
- Исправлены lint ошибки (длинные строки)
2025-12-03 08:49:23 +03:00
cce465d44b feat(requests): добавлен custom template filter для форматирования ISO дат
- Создан filter format_iso_date в req_tags.py
- Парсит ISO даты из API (с/без времени, с/без Z)
- Поддерживает форматы: d.m.Y, H:i, d.m.Y H:i, Y-m-d
- Обновлен шаблон для использования нового фильтра
- Более читаемое и надежное отображение дат и времени
2025-12-03 07:45:45 +03:00
c01c615cb2 fix(requests): исправлено отображение дат в табличном виде
Проблема: API возвращает даты как строки ISO, фильтр date не работал
Решение: используется slice для извлечения даты и времени из ISO строк
- date_from/date_to: slice:":10" для YYYY-MM-DD
- created_at: slice:":10" для даты, slice:"11:16" для времени
- Добавлены проверки на существование значений
2025-12-03 07:44:25 +03:00
2ec6cdfce8 refactor(requests): настоящий табличный вид с фиксированными колонками
Структура таблицы:
- Иконка (48px) - тип заявления
- Заголовок (расширяемая, 2fr) - название, тип, превью комментария
- Автор (1fr) - имя сотрудника с ссылкой
- Даты (100px) - date_from и date_to
- Статус (110px) - badge со статусом
- Создано (80px) - дата и время создания
- Действия (80px) - кнопки одобрить/отклонить/редактировать

Особенности:
- Grid layout с фиксированными шириной колонок
- Автоматический перенос текста в пределах ячейки (word-wrap)
- Выравнивание всех элементов по сетке
- Футер с комментариями выровнен с содержимым
- Адаптивный дизайн (планшеты, мобильные)
- На мобильных - две колонки (иконка + остальное)
2025-12-03 07:39:10 +03:00
e1188b985a style(requests): табличный вид списка заявлений без разделительных линий
- Сеточный layout с выравниванием колонок (иконка, метаданные, действия)
- Убраны разделительные линии между блоками внутри заявления
- Тонкая линия между заявлениями для визуального разделения
- Убрана граница над блоком комментариев (card-actions border-top: none)
- Выравнивание комментариев по левому краю текста заявления
- Адаптивный дизайн для мобильных устройств
- Мягкий hover-эффект для строк
2025-12-03 07:33:29 +03:00
aa32439e8b refactor(requests): Улучшен UX выбора получателей в стиле email-клиента
Переработан RecipientPicker компонент:
✨ Chip-based UI (как в Gmail/Outlook)
- Пользователи отображаются как chips с крестиком для удаления
- Autocomplete с живым поиском по имени/email
- Dropdown с аватарами и должностями пользователей

� Интуитивный интерфейс:
- Структура To:/CC:/Отделы: (как в почтовых клиентах)
- Получатели всегда видны (убраны collapse)
- Кнопка '+ Копия' для показа поля CC
- Inline флаг 'Всем сотрудникам'

� Улучшенная верстка:
- Модальные окна расширены до modal-lg
- Получатели перенесены в начало (как тема в email)
- Добавлены иконки для визуальной группировки
- Добавлены emoji в типы заявлений для удобства

� CSS обновлен:
- Email-style компактный дизайн
- Фокус-состояния для inputs
- Hover-эффекты для dropdown items
- Адаптивность для мобильных устройств
- Поддержка темной темы

� UX улучшения:
- Поиск с автодополнением вместо select
- Визуальные аватары пользователей (инициалы)
- Показ должности и email в dropdown
- Автофокус после добавления пользователя
- Ограничение результатов поиска до 10
2025-12-02 18:09:19 +03:00
deec86f3f9 feat(requests): Этап 5 - реализован frontend для получателей заявлений
Создан компонент RecipientPicker:
- Множественный выбор отделов (ManyToMany)
- Выбор основных получателей (recipients)
- Выбор пользователей в копии (CC users)
- Флаг "Отправить всем сотрудникам отделов"
- Динамический рендеринг выбранных пользователей
- Валидация: нельзя указать recipients с флагом sent_to_all_department

Интеграция:
- Добавлены секции получателей в модальные окна создания и редактирования
- Обновлен requestCrudHandler.js для передачи данных получателей в API
- Добавлены стили recipientPicker.css
- Подключен CSS в request_list_full.html

Функциональность:
- Загрузка списка пользователей и отделов из API
- Добавление/удаление получателей через UI
- Автоматическая очистка форм после создания
- Поддержка редактирования существующих получателей
2025-12-02 17:49:54 +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
c1a907cd60 feat(ui): restyle message composers 2025-11-28 11:56:47 +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
e171d2fd59 Fix template syntax errors in page_header integration
- 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.
2025-11-20 09:32:25 +03:00
8618b7022f Integrate page_header component across all list pages
- 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
2025-11-20 09:28:28 +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
f1b72ffb91 refactor: Extract request-list CSS and deduplicate styles
- Created static/css/components/request-list.css (170 lines)
- Removed duplicate styles from request_list_full.html (~120 lines)
- Removed duplicate ios-search styles from chat_list.html (~29 lines)
- Reused document-list.css in request_list_full.html
- All tests passing (17 OK)
2025-10-28 09:59:20 +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
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
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