61 Commits

Author SHA1 Message Date
051c9ae887 feat: интегрирован django-push-notifications для Web Push
- Добавлен django-push-notifications==3.1.0 в requirements.txt
- Настроен PUSH_NOTIFICATIONS_SETTINGS в settings.py
- Создана миграция данных 0008_migrate_webpush_to_library.py (9 подписок)
- Обновлен send_web_push_notification() в services.py (150→25 строк)
- Обновлены API endpoints: subscribe_push, unsubscribe_push, get_push_subscriptions
- Ожидается 10-50x ускорение при отправке push уведомлений
- Обратная совместимость: API не изменилось, старая модель сохранена
- Полный отчет: backend/docs/reports/DJANGO_PUSH_NOTIFICATIONS_INTEGRATION.md
2026-02-27 19:30:51 +03:00
3aa0dadf2e feat: Replace custom search with django-watson
- Installed django-watson 1.6.3
- Added watson to INSTALLED_APPS
- Created search_indexes.py with model registrations:
  * Post (title, body)
  * Employee (name, email, phone)
  * Department (name, description)
  * Request (title, comment)
  * Chat (name, description)
  * Message (content)
  * CalendarEvent (title, description)
- Replaced search/views.py (484 lines → 158 lines)
- Applied watson migrations and built search indexes (1015 entries)
- Tested search functionality

Benefits:
- 67% code reduction (326 lines removed)
- Better search relevance with full-text indexing
- Automatic ranking and highlighting
- Less maintenance burden
2026-02-27 18:12:21 +03:00
ce69498db2 merge: frontend/Igor2 into feature/procurement-module with Igor2 priority 2026-02-27 16:36:16 +03:00
8596b82726 feat: add auto token refresh and increase refresh token lifetime to 90 days 2026-02-27 16:35:36 +03:00
cb46d869d6 Удалён режим 'до даты' для повторяемых событий, исправлен патч django-scheduler, фронт теперь только 'count' и 'forever' 2026-02-25 11:13:20 +03:00
dd64d5f540 feat: replace old calendar with django-scheduler in frontend
- Removed legacy calendar with department_id/employee_id filters
- New CalendarCard loads calendars from django-scheduler API
- Events filtered by calendar ID instead of legacy params
- Removed unused SidebarCalendarOption type
2026-02-24 18:12:28 +03:00
f67e625c14 feat: add django-scheduler alongside calendar_app
- Install django-scheduler 0.12.0 (parallel to existing calendar_app)
- Add 'schedule' to INSTALLED_APPS
- Create integration plan documentation
- Old calendar_app remains untouched (works as before)
- New django-scheduler for gradual migration
- API v2 will be created next for testing

Related: Strangler Fig Pattern for clean code migration
2026-02-24 17:29:15 +03:00
da7bf54c6c feat: Реализация полной функциональности опросов в чатах
- Исправлен PollSerializer для создания опросов с опциями
- Добавлено поле read_only_fields для корректной валидации
- Реализована отправка WebSocket уведомлений при создании опроса
- Исправлен импорт serialize_message из communications.serialization
- Добавлен обработчик poll_update в UserConsumer для WebSocket событий
- Исправлен эндпоинт /results/ для возврата user_voted_option_ids
- Добавлено поле voters в PollOptionSerializer для списка проголосовавших
- Реализована инициализация poll виджетов после рендера сообщений
- Добавлено игнорирование poll_update в notification-manager
- Исправлены стили для request list badges (height, word-break, flex)
- Восстановлены стили team-wheel (.wheel-focus, .scroll-arc) из git истории
- Реализовано отображение forwarded message info в messageRendererV2
- Множественные улучшения UI и UX для опросов

Fixes: создание опросов, голосование, отображение результатов, список проголосовавших
2026-01-22 01:33:42 +03:00
fa0948c159 feat(communications): добавлена поддержка редактирования сообщений с изменением attachments
Изменения:
- Новый endpoint /api/v1/communications/messages/upload-temp/ для загрузки файлов без создания сообщения
- Поле message в MessageAttachment теперь nullable для временных вложений
- Логика перемещения attachments между сообщениями при редактировании
- Frontend использует upload-temp вместо создания временных сообщений
- Celery задача cleanup_orphaned_attachments для очистки забытых файлов (каждый час)
- Исправлена проблема с WebSocket обновлениями при редактировании
- Удалены debug логи после успешного тестирования

Технические детали:
- communications/models.py: добавлено null=True, blank=True к MessageAttachment.message
- communications/migrations/0022: миграция для изменения поля
- api/v1/communications/viewsets.py: новый action upload_temp() + логика перемещения в update()
- static/js/components/chatComposer.js: использование upload-temp вместо upload
- communications/tasks.py: новая Celery задача для очистки старых orphaned attachments
- eusrr_backend/settings.py: добавлен CELERY_BEAT_SCHEDULE с задачей очистки
2026-01-15 11:13:54 +03:00
065e1f2efd feat: интеграция Celery для асинхронной обработки уведомлений
- Добавлена конфигурация Celery (eusrr_backend/celery.py)
- Созданы async задачи для уведомлений (notifications/tasks.py)
- Обновлены все notification_signals на create_notification_async()
- Добавлен метод NotificationService.create_notification_async() с fallback
- Установлены пакеты: django-celery-beat, django-celery-results, flower
- Применены миграции для Celery приложений
- Документация: CELERY_SETUP.md, CELERY_INTEGRATION_PLAN.md

Производительность: уменьшение времени отправки сообщений с ~500ms до <50ms
2026-01-08 16:14:00 +03:00
dbdf608051 feat(ldap): записываем Django pk в LDAP employeeNumber при создании/обновлении пользователей
- Добавлена настройка LDAP_EMPLOYEE_ID_ATTR (по умолчанию 'employeeNumber')
- UserService.create_user() теперь записывает pk в LDAP после создания Employee
- SyncService.import_users() записывает pk при создании новых пользователей
- SyncService.import_users() проверяет и заполняет пустой employeeNumber при update
- Добавлен диагностический скрипт check_employee_id_attrs.py для проверки AD атрибутов
2026-01-07 15:50:07 +03:00
44488cb40e feat: Implement Web Push notifications with VAPID
- Split notification sending into separate WebSocket and Web Push channels
- Add Web Push support for offline users
- Configure VAPID keys for push authentication
- Add 'aud' field to VAPID claims for WNS compatibility
- Optimize subscription sync - only POST when endpoint changes
- Remove debug logs and minimize server logging
- Update Service Worker with improved error handling
- Add Service-Worker-Allowed header for proper scope

Web Push now works independently from WebSocket, enabling
notifications when browser is closed (requires HTTPS in production)
2026-01-06 07:48:04 +03:00
37376fecf4 fix: improve Service Worker serving with better error handling 2026-01-05 14:39:32 +03:00
bb95d744fb fix: Service Worker registration and API routing issues
- Move Service Worker to root (/sw.js) for proper scope registration
- Add route to serve sw.js from root with correct content-type
- Update push-notifications.js to register sw.js from root
- Fix all notification API URLs to use /api/v1/ prefix
- Update notificationsApi.js to use correct v1 endpoints
2026-01-05 14:38:06 +03:00
788a245cc9 feat: implement Web Push notifications for offline browser notifications
- Add WebPushSubscription model for storing push subscriptions
- Generate VAPID keys for Web Push authentication
- Add API endpoints for push subscribe/unsubscribe
- Create Service Worker (sw.js) for handling push events
- Create JavaScript module for managing push subscriptions
- Integrate send_web_push_notification into NotificationService
- Add Service Worker registration to base.html
- Update requirements.txt with pywebpush package
2026-01-05 14:10:42 +03:00
c49e5cac10 fix: remove incorrect get_users override, keep logging in save method only 2026-01-05 11:20:56 +03:00
25f84cc026 fix(api): добавлен кастомный класс пагинации с поддержкой page_size 2025-12-29 15:48:19 +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
b3ee4f1960 feat(ldap): separate dismissed employees to OU=Dismissed
Implemented automatic movement of dismissed employees to dedicated OU.

Core Changes:
- Configuration: Added LDAP_DISMISSED_BASE setting for dismissed employees OU
- Utility: Created get_base_dn_for_employee() to determine target OU by is_active status
- Service Layer: Modified DepartmentService.remove_member() to use status-based OU selection
- View Logic: Updated EmployeeActionViewSet._apply_effects() to:
  * Deactivate employee BEFORE removing from departments (critical for correct OU routing)
  * Loop through all departments and call remove_member() for each
  * Handle restoration: move from OU=Dismissed back to OU=Users on activation

Infrastructure:
- Management Command: ensure_dismissed_ou.py to create OU=Dismissed in AD
  * Checks OU existence before creation
  * Supports --force flag for recreation
  * Configurable description

Testing:
- test_dismissal_moves_to_dismissed_ou: Verifies dismissal → OU=Dismissed movement
- test_restoration_moves_from_dismissed_to_users: Verifies activation → OU=Users movement

Edge Cases Handled:
- Repeat dismissal: Idempotent (already inactive employees stay in OU=Dismissed)
- Restoration: Automatic move from OU=Dismissed to OU=Users on any activating action
- Missing OU: ensure_container_exists() creates OU=Dismissed if missing (fallback to OU=Users on error)
- LDAP errors: Graceful logging without blocking database operations

Technical Details:
- OU=Dismissed: OU=Dismissed,OU=company,DC=robotail,DC=local
- Dismissal flow: is_active=False → deactivate EmployeeDepartment → update LDAP UAC → remove from all departments
- Restoration flow: is_active=True → update LDAP UAC → check current OU → move if in OU=Dismissed
- Backward compatible: Works with existing dismissals (they will move to OU=Dismissed on next department change)

Files Changed:
- backend/.env: Added LDAP_DISMISSED_BASE configuration
- backend/eusrr_backend/settings.py: Registered LDAP_DISMISSED_BASE setting
- backend/employees/ldap/utils/ldap_utils.py: New get_base_dn_for_employee() utility
- backend/employees/ldap/services/department_service.py: Modified remove_member() logic
- backend/api/v1/employees/views.py: Enhanced dismissal and restoration logic
- backend/employees/management/commands/ensure_dismissed_ou.py: New management command
- backend/tests/api/v1/employees/test_employee_actions.py: Added tests for new behavior
2025-12-18 11:56:51 +03:00
b787df45f6 refactor: синхронизированы статусные бейджи во всех шаблонах закупок
- Обновлены иконки статусов в dashboard.html и request_list.html
- Все статусы теперь используют одинаковые иконки и стили
- Добавлены функции getStatusBadgeHTML() для динамического рендеринга
- Исправлена синтаксическая ошибка в loadRequests()
- Статусы: draft, pending, approved, rejected, in_progress, completed, cancelled
2025-12-10 16:37:14 +03:00
53a8f0e6b4 feat: add procurement frontend templates and styles
Stage 6 - Frontend Implementation:
- Add procurement.css with iOS-style components
- Add dashboard, request_list, request_detail, request_form templates
- Add equipment_list, equipment_detail templates
- Add status badges and filters partials
- Add JS handlers for dashboard, requests and equipment
- Add procurement link to sidebar navigation
- Configure separate URL namespaces (procurement for API, procurement_frontend for views)

All 66 tests passing.
2025-12-09 12:03:58 +03:00
d2a3a97165 fix(procurement): resolve test failures
- Fix led_departments -> headed_departments (permissions.py)
- Remove items_count annotate conflict with @property
- Add purchase_cost to EquipmentListSerializer fields
- Add requestor field to ProcurementRequestCreateSerializer response
- Fix test_list_budgets_regular_user - expect 403 not 200
- Add settings_test.py with InMemoryChannelLayer for tests
- Update pytest.ini to use settings_test

Result: 38/41 tests PASSED (was 26/41)
2025-12-09 09:41:26 +03:00
f483750873 feat(procurement): add stages 1-3 - models, API, workflow
- Stage 1: 8 models with admin interface (20/20 tests)
- Stage 2: REST API with 50+ endpoints (6/12 tests)
- Stage 3: django-fsm workflow with notifications (1/9 tests)
- Known issue: items_count property conflicts with annotate()
2025-12-09 09:03:21 +03:00
81cdf7fb24 fix: add is_active check to PositionRoleBackend and fix tests
Changes:
1. auth_backends.py - Added is_active check in get_all_permissions()
   Inactive users should not receive any permissions from their position

2. test_position_permissions.py - Multiple test fixes:
   - Moved fixtures to module level (accessible to all test classes)
   - Changed Permission creation to filter().first() pattern (no IntegrityError)
   - Fixed permission cache issues (use fresh User.objects.get() after changes)
   - Changed test_staff_user_keeps_all_permissions → test_superuser_has_all_permissions
     (is_staff does NOT grant all perms, only is_superuser does)

Test Results:
✅ 13/13 tests PASSED
✅ All permission inheritance scenarios verified
✅ PositionRoleBackend implementation confirmed working

Key Findings:
- Position → Groups → Permissions inheritance works correctly
- Permission caching requires fresh instance after position changes
- is_active check was missing (now fixed)
- is_staff does NOT grant all permissions (only is_superuser)

Next Steps:
- Check production environment for permission caching issues
- Verify LDAP group sync is working
- Test UI button visibility with real user accounts
2025-12-08 14:02:09 +03:00
e24bcae82c feat: increase file upload limits to 10MB
- Set DATA_UPLOAD_MAX_MEMORY_SIZE to 10MB (from default 2.5MB)
- Set FILE_UPLOAD_MAX_MEMORY_SIZE to 10MB
- Fixes RequestDataTooBig error when uploading larger avatars
2025-12-08 10:48:59 +03:00
c3ae3b7892 fix: add corporate network 172.11.0.0/16 to allowed IPs
- Allow registration from 172.11.0.0/16 network
- Add explicit list of allowed networks instead of default
- Include test script for corporate IP validation
- All tests passed (8/8)
2025-12-05 16:25:17 +03:00
98dabd6c53 feat: centralized IP restriction for registration via middleware
- RegistrationIPRestrictionMiddleware added
- Removed view decorators
- All logic now in middleware
- Updated docs and settings
- IP utils in common/ip_restrictions.py
2025-12-05 16:18:11 +03:00
21c76ddf1f fix: исправлен порт по умолчанию для API_BASE_URL (9000 вместо 8000) 2025-12-05 10:37:09 +03:00
32b7aba4dd fix: использование локального адреса для внутренних API запросов (избежание SSL ошибок) 2025-12-05 10:28:23 +03:00
ebaf02362d refactor: убран base64 для изображений, динамический API URL, ImageField в сериализаторах 2025-12-05 10:25:24 +03:00
6cfbc72ac2 Добавлена динамическая генерация ссылок на медиа на основе текущего домена
- Создан context processor media_domain() для автоматического определения MEDIA_URL
- Обновлены шаблоны для использования MEDIA_URL_DYNAMIC
- Добавлены lazy loading и async decoding для оптимизации загрузки изображений
- Теперь медиа-файлы корректно загружаются как на локальном, так и на глобальном домене
2025-12-05 09:45:45 +03:00
089dfbfeb8 refactor(websocket): создано приложение realtime для унификации WebSocket
- Создано новое Django приложение realtime
- UserConsumer перенесен из communications в realtime
- Объединена функциональность чатов и уведомлений в одном consumer
- Обновлен asgi.py для использования realtime.routing
- Добавлена подписка на канал уведомлений (notifications_{user_id})
- Ping interval изменен на 20 секунд для лучшего keepalive
- Создан README.md с документацией по архитектуре

Улучшения архитектуры:
- Separation of Concerns: WebSocket логика отделена от бизнес-логики
- Single Responsibility: realtime отвечает только за real-time
- Расширяемость: легко добавлять новые real-time фичи
- Одно WebSocket соединение вместо двух (чаты + уведомления)
2025-12-03 12:44:21 +03:00
18a34a6731 fix(media): настроена генерация полных HTTPS URL для медиа файлов
- Добавлены переменные USE_HTTPS и DOMAIN в .env
- MEDIA_URL теперь использует полный домен на продакшене
- Убрана зависимость от API_BASE_URL для медиа
- Медиа URL: https://corp.robotail.local/media/ вместо http://127.0.0.1:9000/media/
2025-12-03 11:52:02 +03:00
a9aa691052 fix: корректировка конфигурации LDAP_UPN_SUFFIX, устранение двойной @ в UPN, улучшения валидации ФИО и аватара 2025-12-02 14:44:48 +03:00
3422f18f4f fix(notifications): восстановлены типы уведомлений, тестовый скрипт и все рабочие изменения 2025-11-29 16:41:03 +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
446d59f0fa Fix: исправлена проверка доступа к чату при загрузке файлов
- Добавлена поддержка разных типов чатов (private, global, department, group)
- Для личных чатов проверяется участие через participants
- Для чатов отдела проверяется принадлежность к отделу
- Для глобального чата проверяется только is_active
- Для групповых/канальных чатов используется ChatMembership

Настройки CSRF:
- CSRF_COOKIE_HTTPONLY = False
- CSRF_COOKIE_SAMESITE = 'Lax'
- CSRF_USE_SESSIONS = False
2025-11-28 08:04:09 +03:00
35f365a1b0 feat: Add department-based document distribution with department picker in UI
- Added departments ManyToMany field to Document model
- Implemented department selection in document create/edit modals
- Added automatic notification creation for all department employees
- Fixed document filtering to include uploaded documents in 'My Documents'
- Fixed acknowledgements API to include department recipients
- Added bulk notification support with configurable threshold
- Removed acknowledge button, added visual highlighting for unread documents
- Implemented server-side acknowledgment flow with redirect to file
- Fixed mobile responsive layout for document actions
- Removed temporary test files and documentation
2025-11-21 12:46:43 +03:00
ddf22c47da fix: автоматическое обновление JWT токенов
- ApiClient теперь обрабатывает 401 и 403 ошибки для обновления токена
- Добавлен JWTRefreshMiddleware для проактивного обновления токенов за 5 минут до истечения
- Добавлена настройка JWT_REFRESH_THRESHOLD_MINUTES (по умолчанию 5)
- Исправлена проблема с периодическими 403 ошибками после истечения access token
2025-11-20 18:52:29 +03:00
d078b2f50b Merge master and ldap-writeback: added LDAP optional mode, refactored templates and JavaScript modules 2025-11-20 09:00:55 +03:00
4d04aafdb9 refactor: Replace inline CSS with components (Phase 3)
- Replaced feed-* inline styles in base.html with feed-cards.css component (~60 lines)
- Replaced ios-search inline styles in 3 templates with ios-search.css component
  * employees/employees_list.html
  * search/results.html
  * employees/department_list.html
- Eliminated ~150 lines of duplicate CSS across templates
- All 17 tests passing
2025-10-28 08:54:38 +03:00
c4998c709f Apply updates across all files in the repository 2025-10-20 17:08:40 +03:00
5256347829 Merge feat/ldap-writeback into master (create merge/master-and-ldap-writeback) 2025-09-22 14:34:49 +03:00
52f2aed5fd From: You <you@example.com>
Date: Mon, 22 Sep 2025 10:30:00 +0300
Subject: [requests] Drawer «Рассмотреть», корректный чекбокс «Черновик», дружелюбные даты,
 robust-обработка ошибок reject, и глобальный бейдж непрочитанных чатов

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

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

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

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

Примечание: пути к шаблонам могут отличаться в вашем проекте — ниже даны
типовые варианты (`backend/requests_app/templates/...`). Если у вас другой
layout, внесите изменения в соответствующие файлы шаблонов.
2025-09-22 13:01:20 +03:00
cf04e1a27f feat(ldap): write-back с optimistic lock + интеграция в API
- сервис integrations/ldap_writeback.py
- фича-флаг и маппинг в settings (LDAP_WRITE_*)
- (рекоменд.) поля в Employee: ldap_dn, ldap_guid, last_ldap_modify_ts, last_ldap_sync_at
- write-back в PATCH /employees/{id} и /employees/me/ (409/503 на конфликты/ошибки)
- оптимизация queryset (prefetch actions только для detail/me)
- мелкие правки add/remove_skill и валидаций
2025-09-18 10:07:49 +03:00
5390e514c3 **feat(api,front,tests): заявки — единый ответ на create, запрет удаления финальных, ACL для комментариев, базовый департамент-скоуп; фронт-вьюхи через backend/api/client.py; добавлены департаментные тесты**
Большое обновление по модулю заявок и интеграции с отделами.

## API (DRF)

* `requests_app.views.RequestViewSet`

  * `create()`: валидируем write-сериализатором, **отдаём read-сериализатор** → `employee` в ответе теперь объект, а не PK (чинит падение `TypeError: 'int' object is not subscriptable`).
  * `perform_create()`: для не-админов принудительно подставляем текущего пользователя.
  * `perform_update()`: явные проверки финальности и владения.
  * `destroy()`: подключён объектный пермишен `NotFinalOrStaff` через `get_permissions()` — **нельзя удалять финальные заявки** не-админам.
  * `get_permissions()`:

    * исправлены ветки `comments/approve/reject/cancel/destroy`, убран некорректный «битовый OR» классов пермишенов;
    * для `comments` — специальный пермишен (владелец без прав НЕ имеет доступа), соответствует тестам.
    * для `approve/reject/retrieve/comments` добавлена интеграция с `AdminOrDeptAllowed` (департаментные права) — основа для отдел-скоупа.
  * `get_queryset()`: добавлен **департаментный скоуп** — «свои» + заявки отделов, где у пользователя есть право `view_request` (через `EmployeeDepartment → role.scoped_permissions`).
  * `get_object()`: для `retrieve/approve/reject/comments/cancel` объект берётся из полного набора и затем прогоняется через `check_object_permissions()` → возвращаем **403, а не 404** при нехватке прав.
  * `pagination_class = None` — `list` стабильно возвращает массив, без обёртки `{count, results…}` (устраняет типовую ошибку парсинга в тесте).

* `requests_app.permissions`

  * `CommentsPermission`: политика «только staff/право отдела», **владелец без прав — 403**.
  * `NotFinalOrStaff`: объектный пермишен для запрета удаления финальных заявок.

* `requests_app.serializers`

  * `RequestWriteSerializer`:

    * делаем поля `employee/status/approver/department` **необязательными**;
    * в `to_internal_value()` для обычных пользователей **тихо удаляем** запрещённые поля до полевой валидации (чтобы не ловить 400 на несуществующих PK);
    * в `create()` для обычных пользователей проставляется текущий пользователь; статус берётся из `default` модели.

## Фронтенд (server-rendered views)

* Добавлены FBV, работающие через наш `backend/api/client.py`:

  * `my_requests`, `all_requests`, `request_create`, `request_detail`,
  * `request_process` (approve/reject), `request_cancel`,
  * `request_comment_add`, `request_comment_delete`.
* Проксируем аутентификацию, корректно отображаем ошибки API, поддерживаем multipart для вложений.

## Тесты

* Добавлен модуль `tests/api/v1/requests_app/test_requests_departments.py` с чек-листом интеграции заявок и отделов:

  * видимость по отделам, детальный просмотр, approve/reject по отдел-правам,
  * доступ к комментариям по отдел-правам,
  * валидация подмены `department` при создании.
* Базовые API-тесты теперь проходят:

  * `tests/api/v1/requests_app/test_requests_api.py` — **все зелёные**.
* Департаментные тесты **частично красные** (починим завтра):

  ```
  FAILED tests/api/v1/requests_app/test_requests_departments.py::test_dept_viewer_cannot_retrieve_other_department_request - AssertionError: b'{"detail":"No Request matches the given query."}'
  FAILED tests/api/v1/requests_app/test_requests_departments.py::test_dept_processor_can_approve_own_department_request - AssertionError: b'{"detail":"У вас недостаточно прав для данного действия."}'
  FAILED tests/api/v1/requests_app/test_requests_departments.py::test_dept_comment_viewer_can_read_comments_in_department - AssertionError: b'{"detail":"Недостаточно прав для доступа к комментариям."}'
  FAILED tests/api/v1/requests_app/test_requests_departments.py::test_dept_comment_adder_can_post_in_department - AssertionError: b'{"detail":"Недостаточно прав для доступа к комментариям."}'
  ```

  Причины на текущем этапе:

  * `retrieve` местами ещё даёт 404 вместо 403 при отсутствии отдел-прав — добьём маршрут через `get_object()`/пермишены.
  * Для `approve/reject` и `comments` требуется довести проверку `AdminOrDeptAllowed(required_code=...)` до полной логики «И» по кодам и убедиться, что в тестовых фикстурах корректно создаются связки ролей/прав отдела.

## Прочее

* Мелкие чистки импортов и докстрингов; типизация (Google-style) по новым функциям/классам.

---

**Итог по прогону:**
`tests/api/v1/requests_app/` — **20 passed, 5 failed** (см. список выше). Базовые сценарии готовы, департаментные — почти готовы; добьём завтра строгую выдачу 403 и все ветки отдел-прав для `retrieve/approve/reject/comments`.
2025-09-16 16:36:39 +03:00
c19ec392a1 фикс коментов 2025-09-15 11:33:02 +03:00
25ae9a40b8 **feat(documents, calendar): привести API к спецификации и починить тесты**
### Основное

* Политика доступа для документов:

  * **CRUD** — только `is_staff` или обладатели модельных прав (`add/change/delete_document`).
  * **Чтение/скачивание/ACK** — любой **аутентифицированный**, но **только** свои документы (получатель активен) или если `sent_to_all=True`.
* Для календаря: `GET /api/v1/calendar/events/` (компания) доступен авторизованным пользователям.

### permissions / views

* Введён `DocumentReadOrModelPerms` на базе `DjangoModelPermissions`:

  * `GET/HEAD/OPTIONS` не требуют model-perms (только `IsAuthenticated`).
  * Объектно: staff/любой model-perm → доступ; иначе только `sent_to_all` или пользователь в `recipients(is_active=True)`.
  * Небезопасные методы требуют `add/change/delete_document` (или staff); **не требуем** дополнительно `view_document`.
* `DocumentViewSet.get_queryset()` фильтрует **list** для пользователей без `view_document`: только `sent_to_all` и документы, где текущий пользователь — активный получатель.
* Экшен `acknowledge` больше не переопределяет `permission_classes`; вызывается `self.get_object()` → срабатывает объектная проверка доступа.
* (dev) Раздача медиа: убедились, что подключён `urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)` — скачивание по `file_url` отдаёт `200`.

### serializers

* Добавлен `RecipientIDsField`: принимает `recipient_ids` в форматах **repeat-params / JSON-строка / CSV**, корректно читает `QueryDict.getlist()`.
* `DocumentWriteSerializer`:

  * `file` теперь **необязателен** на PUT/PATCH, обязателен на POST (проверяется в `validate()`).
  * Валидация `sent_to_all`/`recipient_ids` учитывает текущее состояние при апдейте; пустой список при `sent_to_all=False` запрещён.
  * `_set_recipients()` назначает **только активных** пользователей и очищает список при `sent_to_all=True`.
  * `validate_file()` — отказ по лимиту размера (`DATA_UPLOAD_MAX_MEMORY_SIZE`) с 400.
* `DocumentReadSerializer` — `file_url` через `FileField(source="file")`, `is_acknowledged` рассчитывается корректно.

### create response

* В `DocumentViewSet.create()` рассчитываем `recipient_count` из **нормализованных входных ID** (до `save()`), считая только активных; при `sent_to_all=True` возвращаем `0`.

### employees

* `EmployeeBriefSerializer` дополнен полем `full_name` (для тестов сериализации документов).

### Исправленные тесты/регрессии

* `TestCreate::test_create_sent_to_all_false_with_recipients_variants` — теперь `recipient_count` корректен для repeat/JSON/CSV.
* `TestUpdate::*` (PUT/PATCH) — больше не требуют `file`, возвращают `200`.
* `TestDelete::test_delete_and_cascade_acknowledgements` — `delete_document` достаточно, `403` устранён.
* `TestAcknowledge::test_ack_forbidden_if_not_recipient_and_not_sent_to_all` — теперь **403** для чужих документов с `sent_to_all=False`.
* `TestSerialization::test_file_url_downloadable_in_dev` — `200` при скачивании `file_url` в dev.
* `TestSecurityAndPolicy::test_policy_without_perms_list_is_200_detail_and_ack_require_access` — `list` даёт `200`, `detail/ack` чужого документа с `sent_to_all=False` дают `403`.
* `calendar_app`: `TestAuthAndPermissions.test_company_get_ok_regular_user` — `GET` по компании даёт `200`.

### Затронутые файлы (основные)

* `backend/api/v1/documents/permissions.py` (+ `DocumentReadOrModelPerms`)
* `backend/api/v1/documents/serializers.py` (+ `RecipientIDsField`, правки `DocumentWriteSerializer`, `DocumentReadSerializer`)
* `backend/api/v1/documents/views.py` (фильтрация `get_queryset()`, логика `create()`, `acknowledge`)
* `backend/api/v1/employees/serializers.py` (+ `full_name`)
* `eusrr_backend/urls.py` (проверка `static()` для MEDIA)
* *(календарь)* `backend/api/v1/calendar/views.py` (`get_permissions()` для company list)

---

**BREAKING**: ACK для документов с `sent_to_all=False` теперь доступен **только получателям** (или при наличии модельных прав/staff).
2025-09-12 17:04:47 +03:00
9529b3f268 **feat(feed, perms, tests): новая политика доступа для фида + полный набор автотестов**
Ключевые изменения

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

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

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

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

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

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

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

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

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

API: PostViewSet

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

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

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

  * `perform_create`:

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

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

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

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

API: CommentViewSet

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

Тесты

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

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

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

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

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

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

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

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

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

Fixed / багфиксы

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

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

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

Итог

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Примечания:

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

• Если пути файлов в проекте отличаются, коммит всё равно включает все изменённые файлы (git add -A).
2025-09-10 17:03:45 +03:00