1058 Commits

Author SHA1 Message Date
4da93f80a0 feat(api): add Excel export endpoint for employees
Добавлен GET эндпоинт для экспорта списка сотрудников в Excel формат.

Endpoint: GET /api/v1/employees/export-excel/

Функционал:
- Экспорт всех сотрудников в формат .xlsx
- Применяются все фильтры из queryset (department, position, active и т.д.)
- Поддержка поиска и сортировки из основного API

Колонки в Excel:
- ID, Фамилия, Имя, Отчество
- Email, Телефон
- Должность, Отделы (с ролями)
- Дата рождения, Дата регистрации
- Статус: Активен, Email подтвержден
- Навыки
- Мессенджеры: Telegram, WhatsApp, WeChat

Особенности:
- Стилизованные заголовки (синий фон, белый текст)
- Автоподбор ширины колонок (максимум 50 символов)
- Закрепленная первая строка (заголовки)
- Перенос текста в ячейках
- Имя файла: employees_YYYYMMDD_HHMMSS.xlsx

Зависимости:
- openpyxl==3.1.5 (добавлена в requirements.txt)

Использование:
GET /api/v1/employees/export-excel/
GET /api/v1/employees/export-excel/?department=5
GET /api/v1/employees/export-excel/?active=true&search=Иванов
2025-12-18 15:29:40 +03:00
21b0614c69 fix(ldap): handle dismissal of employees without departments
Проблема: При увольнении сотрудника, не состоящего ни в одном отделе,
он оставался в текущей OU (например, OU=Users) вместо перемещения в OU=Dismissed.

Причина: Цикл по active_departments был пустым, remove_member() не вызывался,
перемещение в OU=Dismissed не происходило.

Решение:
- Добавлена проверка departments_processed
- Если сотрудник не состоял ни в одном отделе, выполняется явное перемещение
- Используется get_base_dn_for_employee() для определения целевой OU
- Прямой вызов _move_user_to_base() для перемещения в OU=Dismissed

Тест: test_dismissal_without_department_moves_to_dismissed
- Проверяет увольнение сотрудника без отделов
- Убеждается, что LDAP соединение создано для перемещения
- Подтверждает деактивацию в LDAP (userAccountControl=514)

Edge case покрыт: Теперь все уволенные попадают в OU=Dismissed независимо
от наличия отделов.
2025-12-18 12:32:09 +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
77e34e6e2b feat(avatar): center and enlarge face in overlay guide
Improved human silhouette overlay to better center the face and make it larger for optimal FaceID recognition.

Changes:

Face positioning:
- Moved face center from cy=120 to cy=150 (centered vertically)
- Face now occupies middle of frame instead of upper third
- Eyes align with horizontal center line at y=150
- Better balance between head and body in frame

Face size increase:
- Increased head ellipse from rx=75,ry=95 to rx=90,ry=115
- ~20% larger face area for better recognition
- More prominent facial features in final crop
- Improved face-to-frame ratio (70-80% coverage)

Body proportions updated:
- Neck: y=250 to y=285 (moved down)
- Shoulders: y=230 to y=285 (centered below face)
- Torso extends to bottom of frame
- More natural body proportions

Guide lines adjusted:
- Eye-level line: y=120 → y=150 (center of frame)
- Shoulder line: y=230 → y=285 (below center)
- Vertical center line unchanged (x=150)

UI text updates:
- Instructions emphasize face centering
- 'Расположите глаза на центральной линии'
- 'Голова занимает верхнюю половину фото'
- Changed bottom label from 'Пояс' to 'Низ кадра'

Benefits:
✓ Face more prominent in photo (better for FaceID)
✓ Centered composition (professional appearance)
✓ Balanced head-to-body ratio
✓ Easier for users to align correctly
✓ Improved recognition accuracy
2025-12-18 10:19:47 +03:00
28feac732e feat(avatar): change crop ratio to 3:4 portrait and improve overlay sync
Changed avatar cropper aspect ratio from 1:1 to 3:4 (portrait) for better FaceID recognition.
Improved human silhouette overlay synchronization with crop box during all interactions.

Key changes:

Aspect ratio:
- Changed from 1:1 (square) to 3:4 (portrait)
- Output size: 600x800px (instead of 800x800px)
- Better suited for full body positioning (head, shoulders, torso)

SVG overlay improvements:
- Updated viewBox from 400x400 to 300x400 (3:4 proportions)
- Redesigned human silhouette for portrait format
- Added torso outline up to waist level
- Additional guide line for shoulders
- Better proportions for face/head/shoulders alignment

Synchronization enhancements:
- Added cropstart, cropmove, cropend event handlers
- Overlay now follows crop box during all interactions:
  ✓ Moving the crop box
  ✓ Resizing the crop box
  ✓ Zooming the image
  ✓ Initial positioning
- Real-time position updates using getCropBoxData()

UI/UX updates:
- Updated instructions: 'Совместите тело с зелёным контуром'
- Face should occupy upper third of frame
- Portrait format 3:4 mentioned in requirements
- Changed label from 'Плечи' to 'Пояс' (waist)
- Templates updated with new size requirements (600x800px)

Technical details:
- minCropBoxHeight: 266px (200 * 4/3 for aspect ratio)
- preserveAspectRatio: none (for proper stretching)
- Event handlers ensure overlay follows crop box smoothly
- No performance impact from additional event listeners

Benefits:
✓ Better for FaceID (full body context)
✓ Standard portrait photo format
✓ More professional appearance
✓ Consistent with ID photo standards
✓ Improved overlay visibility and accuracy
2025-12-18 10:15:00 +03:00
c3a82378ff feat(avatar): add human silhouette overlay to avatar cropper
Added visual guide with human body contour overlay to help users properly position their face for FaceID.

Features:
- SVG overlay with human silhouette (head, neck, shoulders)
- Green dashed outline (70-80% face coverage guidance)
- Guide lines (vertical center, horizontal eye level)
- Text labels ('Верх головы', 'Плечи')
- Toggle checkbox to show/hide overlay
- Real-time overlay positioning synchronized with crop box

Technical implementation:
- SVG viewBox 400x400 with preserveAspectRatio
- Dynamic positioning using getCropBoxData()
- Event handlers: ready, crop, zoom
- Overlay updates on cropper interactions
- Pointer-events: none for non-intrusive UI

UI/UX improvements:
- Clear visual guidance for face positioning
- Non-intrusive overlay (can be toggled off)
- Color: #00ff00 (green) for high visibility
- Opacity: 0.8 for overlay, 0.4 for guide lines
- Auto-shown by default, user can disable

Benefits:
✓ Reduces user errors in face positioning
✓ Ensures consistent photo quality for FaceID
✓ Visual feedback for proper head/shoulders alignment
✓ Improves user experience and reduces support requests
2025-12-18 10:03:34 +03:00
53f1eb8096 feat(avatar): implement avatar cropper with FaceID requirements
Implemented avatar cropping component for FaceID system integration.

Features:
- Image cropper using Cropper.js library
- FaceID requirements validation (70-80% face coverage, 600x600px min)
- Square crop area (1:1 aspect ratio)
- Output: 800x800px JPEG at 92% quality
- Real-time size indicator
- Integrated in 2 places: registration + profile editor

Components:
- avatarCropperHandler.js: Main cropper module with AvatarCropper class
- Modal UI with Bootstrap 5
- FaceID instructions and guidelines
- File validation (size, format, dimensions)

Integration:
- auth/register.html: Registration form with cropper
- employees/employee_form_personal.html: Profile editor with cropper
- employees/employee_form_scripts.html: Scripts updated

Technical details:
- CDN: Cropper.js v1.6.1
- Min input size: 600x600px
- Max file size: 10 MB
- Formats: JPG, PNG, GIF
- Output format: JPEG (optimized for face recognition)

Validation:
✓ File format validation
✓ File size validation (10 MB max)
✓ Image dimensions validation (600x600px min)
✓ Real-time crop size indicator
✓ Error handling with user-friendly messages

UI/UX:
- Modal window with instructions
- Live preview of cropped image
- Size indicator (green/red based on requirements)
- Clear FaceID guidelines for users
- Responsive layout
2025-12-18 09:56:50 +03:00
9d47fc0c5c feat: add employee dismissal UI and filter inactive employees
- Added dismissal form on employee profile page with permissions
- Shows warning when dismissing (LDAP deactivation notice)
- Inactive employees filtered by default in employee list (active=true)
- Team wheel now shows only active employees
- Auto-submit filter form on status change
- Added redirect to set active=true by default on first visit
2025-12-17 09:14:34 +03:00
1c9aa9284e feat: sync employee dismissal/activation with LDAP
- Added LDAP synchronization in EmployeeActionViewSet._apply_effects()
- Dismissal sets userAccountControl=514 (disabled) in LDAP
- Other actions set userAccountControl=512 (enabled)
- Graceful error handling: DB succeeds even if LDAP fails
- Works correctly when LDAP is disabled
- Added 5 comprehensive tests for LDAP sync behavior
2025-12-17 08:15:35 +03:00
992dc6a13f feat: maximize LDAP avatar quality (384px, progressive JPEG, 4:4:4)
- Увеличено разрешение: 120px -> 384px (+900% пикселей)
- Повышено качество JPEG: 50-95 -> 75-98
- Добавлен progressive JPEG для лучшего сжатия
- Включён subsampling 4:4:4 (без потери цветности)
- Добавлена обработка EXIF ориентации
- Добавлено сохранение пропорций (thumbnail вместо resize)
- Обработка прозрачности PNG

Улучшения в:
- employees/ldap/utils/image_utils.py
- employees/ldap/services/user_service.py
- employees/ldap/sync_service.py

Добавлены тестовые скрипты:
- test_ldap_simple.py (быстрый тест)
- test_avatar_simple.py (тест системного сжатия)
- TEST_IMAGE_SCRIPTS.md (документация)
2025-12-16 14:55:58 +03:00
15f3b7f567 fix: preserve EXIF orientation when compressing images 2025-12-16 13:37:35 +03:00
6a77f4fb13 - 2025-12-15 12:11:24 +03:00
070dbc267e style: unify table styles in equipment_detail
- Replaced Bootstrap table with proc-table-header/proc-request-item
- Now maintenance history and transfer logs match request items style
- Added responsive behavior with d-lg-none labels
- Better visual consistency across procurement module
2025-12-15 10:12:42 +03:00
c7bc99398f refactor: switch equipment_detail from CSR to SSR
- Updated equipment_detail view to load data from DB (SSR)
- Added select_related and prefetch_related for optimization
- Implemented can_manage permission check in view
- Removed loadEquipmentDetail() JavaScript API call
- Added breadcrumb navigation
- Kept action handlers (maintenance, transfer, write-off)
- Added department loading for transfer modal
- Now consistent with request_detail SSR approach
2025-12-15 09:54:31 +03:00
04e46d98d4 - 2025-12-15 09:49:41 +03:00
aeab6a1127 fix: display total_cost instead of estimated_cost in request list
- Changed from estimated_cost (single field) to total_cost (calculated sum)
- Now correctly shows sum of all items in the request
2025-12-10 17:08:09 +03:00
978ed9a819 fix: aligned ProcurementItem permissions with ProcurementRequest logic
- Added comprehensive permission checks in perform_create()
- Created _check_item_edit_permission() helper method
- Updated perform_update() and perform_destroy() to use helper
- Permission logic now matches CanManageProcurementRequest:
  * Admins/Staff can edit items in DRAFT requests
  * Users with change_procurementrequest permission
  * Request author can edit their own requests
  * Department head can edit requests from their department
- Fixes 403 Forbidden error when creating/editing/deleting items
2025-12-10 17:02:40 +03:00
2e92c4d7a0 fix: исправлена загрузка отделов и обработка пагинированного ответа
- API departments возвращает объект с пагинацией {results: [...]}, а не массив
- Добавлена универсальная обработка как массива, так и объекта с results
- Добавлены проверки существования DOM элементов перед обращением
- Исправлена ошибка при редактировании заявки
2025-12-10 16:53:09 +03:00
be9d02bc43 fix: улучшена обработка ошибок загрузки отделов
- Добавлено детальное логирование всех этапов загрузки
- Добавлена проверка существования элемента select
- Улучшена обработка ошибок с понятными сообщениями
- Добавлены alert'ы для уведомления пользователя об ошибках
2025-12-10 16:51:23 +03:00
065a8851a1 feat: реализована полная функциональность создания и редактирования заявок
- Добавлены модальные окна для всех действий на странице деталей
  * Добавление позиции (#addItemModal)
  * Одобрение заявки (#approveModal)
  * Отклонение заявки (#rejectModal)
- Реализованы JavaScript обработчики для всех кнопок
  * deleteItem() - удаление позиции
  * saveItemBtn - сохранение новой позиции
  * confirmApproveBtn - одобрение с комментарием
  * confirmRejectBtn - отклонение с обязательным комментарием
- Исправлены API endpoints (items вместо request-items)
- Доработана форма создания/редактирования заявки
  * Удалены несуществующие поля (desired_date, estimated_cost)
  * Добавлен выбор отдела из API
  * Реализована AJAX отправка формы
  * Поддержка создания черновика и отправки на согласование
  * Поддержка редактирования существующих заявок (PATCH)
- Добавлен URL и view для редактирования заявок
- Кнопка "Редактировать" теперь ведёт на страницу редактирования
2025-12-10 16:46:47 +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
427f5ed22f feat(procurement): frontend templates + API-based rendering; update models, serializers, views and tests 2025-12-10 14:09:49 +03:00
537e6c4127 feat(procurement): массовое создание бюджетов
Добавлены возможности:

1. Django Admin Actions:
   - 'Создать бюджеты на следующий квартал для всех отделов'
   - 'Скопировать выбранные бюджеты в следующий квартал'

2. Management Command:
   python manage.py create_next_quarter_budgets

   Опции:
   --year YEAR --quarter Q  - конкретный период
   --dry-run                - показать без создания
   --default-amount 100000  - сумма по умолчанию

Логика:
- Копирует allocated_amount из текущего квартала
- Если нет текущего бюджета - использует значение по умолчанию (0)
- Пропускает уже существующие бюджеты
- Сбрасывает spent_amount в 0
2025-12-10 07:25:07 +03:00
10e9c5afa7 fix: исправить format_html в utilization_badge
Ошибка была в том, что нельзя использовать f-string формат {:.1f}
в format_html, нужно сначала преобразовать в строку
2025-12-10 07:18:04 +03:00
46cb319903 fix: исправить ошибки в админке Budget
- Убрать remaining_amount и utilization_percentage из list_display (это @property)
- Добавить методы get_remaining_amount и get_utilization_percentage
- Защитить от None в allocated_amount
- Исправить prefetch_related в views_frontend
2025-12-10 07:16:00 +03:00
ccbc8c5452 feat(procurement): полный жизненный цикл заявок
Добавлены endpoints:
- start_work: APPROVED → IN_PROGRESS
- complete: IN_PROGRESS → COMPLETED
- cancel: отмена заявки с любого статуса кроме завершённых
- create_equipment: создание оборудования из позиции завершённой заявки

Frontend:
- Модальные окна approve/reject/cancel
- POST handler для создания заявок
- Django формы для заявок

Тесты: 19 новых тестов lifecycle, всего 136 тестов procurement passed
2025-12-09 16:46:21 +03:00
8b8974e3ef feat: refactor equipment API permissions
- Add DeptPerm.MANAGE_EQUIPMENT scoped permission constant
- Create migration 0035 for new DepartmentPermission
- Refactor CanManageEquipment permission class with:
  * Admin/staff/superuser → full access
  * Model permissions → full access (add/change/delete_equipment)
  * Department head → own department, can choose responsible
  * Scoped permission → own department, responsible forced to head
  * Regular members → no access
- Update EquipmentListSerializer with:
  * Permission level detection (full/dept_head/scoped/None)
  * Auto-fill department and responsible based on permission level
  * Validation that responsible is in same department
- Add create_options endpoint returning:
  * allowed_departments, can_choose_department, can_choose_responsible
  * default_responsible, permission_level
- Add bulk equipment creation support with auto-generated inventory numbers
- Implement 45 comprehensive permission tests (all passing)
- Fix is_active=True in test make_user() fixture

Tests: 45/45 passing ✅
2025-12-09 16:14:30 +03:00
3d1a415bee fix: исправление загрузки категорий в модале управления
- Исправлена обработка ответа API (поддержка как массива, так и объекта с results)
- API возвращает объект с пагинацией, а не просто массив
- Теперь корректно извлекается массив категорий из data.results
- Исправлена ошибка 'categories.forEach is not a function'
2025-12-09 13:26:36 +03:00
4a7dbf7ce2 feat: добавление UI для управления категориями оборудования
- Добавлена кнопка 'Управление категориями' на странице оборудования
- Создан модал с двумя секциями: добавление и список категорий
- Форма создания категории: название, иконка Bootstrap Icons, описание
- Список существующих категорий с возможностью удаления
- JavaScript функции для работы с API категорий
- Автоматическое обновление списка после создания/удаления
- Защита от удаления категорий с привязанным оборудованием
- Доступно только для staff пользователей (can_manage)
2025-12-09 13:24:55 +03:00
272b02377f feat: добавление позиций при создании заявки и исправление списка отделов
- Добавлен список departments в контекст request_list view
- Реализовано динамическое добавление позиций (items) прямо в модале создания заявки
- Кнопка 'Добавить позицию' для добавления товаров/услуг
- Каждая позиция содержит: название, описание, количество, ед.изм., цену
- Позиции можно удалять кнопкой X
- JavaScript собирает все позиции и отправляет в API вместе с заявкой
- Поддержка как создания с позициями, так и без них
- Реализовано на обеих страницах: dashboard и request_list
- Все 72 теста проходят успешно
2025-12-09 13:21:14 +03:00
41eda0e7ac fix: исправление создания заявок - добавлено обязательное поле department
- Сделано поле 'items' необязательным в ProcurementRequestCreateSerializer
- Добавлено поле 'department' в форму создания заявки (dashboard и request_list)
- Обновлен JavaScript для передачи department в API
- Добавлен список departments в контекст dashboard view
- Теперь можно создать заявку без позиций (items), добавить их потом
- Все 72 теста проходят успешно
2025-12-09 13:11:28 +03:00
c94de51c31 feat: добавление управления оборудованием через UI
- Добавлена кнопка 'Добавить оборудование' на странице списка
- Создан модал для добавления нового оборудования
- Создан модал для редактирования существующего оборудования
- Добавлена кнопка редактирования в dropdown меню каждой карточки
- Реализованы JavaScript обработчики для создания/редактирования через API
- Добавлен импорт User в views_frontend.py для передачи списка пользователей
- Все поля модели Equipment доступны для редактирования (включая статус, отдел)
2025-12-09 13:04:56 +03:00
805d536a47 fix: улучшение кнопки создания заявки и исправление модала
- Изменен текст кнопки на 'Создать заявку' на всех страницах
- Добавлен параметр create_as_card=True на странице списка заявок
- Перемещен модал внутрь блока content в dashboard.html (исправление ошибки Bootstrap)
2025-12-09 12:59:09 +03:00
64af0d273e fix: improve procurement request creation UX
- Change dashboard create button to use modal instead of URL redirect
- Add create_as_card and create_modal parameters to page_header
- Replace form POST submission with AJAX API calls
- Add proper form validation and error handling
- Redirect to request detail page after successful creation
- Remove unused create_url from dashboard context
2025-12-09 12:45:47 +03:00
d04b5e38c2 feat: add procurement integration with notifications and real-time
Stage 7 - Integration:
- Create notification types migration for procurement
- Fix signals to use pre_save for status tracking
- Add broadcast_request_update function for WebSocket events
- Add procurement_update handler in UserConsumer
- Add 6 integration tests for signals and notifications
- Update README with integration details

All 72 tests passing (66 base + 6 integration).
2025-12-09 12:23:02 +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
3cd161d83c feat: add equipment management with QR codes and transfers
Stage 5 (Inventory) implementation:
- Add EquipmentTransferLog model for tracking equipment movements
- Add EquipmentViewSet actions:
  - transfer: move equipment between departments/users
  - write_off: decommission equipment
  - add_maintenance: record maintenance activities
  - qr_code: generate QR code for equipment
  - transfer_history: view equipment transfer history
- Use existing QRCodeGenerator and InventoryNumberGenerator services
- Fix EquipmentStatus constants usage (IN_USE, RETIRED)
- Add 14 new tests for inventory functionality

Test results: 66/66 PASSED
- 20/20 Model tests
- 12/12 API tests
- 9/9 Workflow tests
- 11/11 Budget tests
- 14/14 Inventory tests (NEW)
2025-12-09 10:42:07 +03:00
69402567e3 feat(procurement): add budget control and alerts
Budget Model Enhancements:
- Add reserved_amount property (sum of pending requests)
- Add available_amount property (remaining - reserved)
- Add _quarter_months() helper method

API Endpoints:
- Add /budgets/my-department/ endpoint for user's department budget
- Add /stats/overview/ for procurement statistics
- Add /stats/by-department/ for department-level statistics
- Register ProcurementStatsViewSet in urls.py

Budget Alerts:
- Add _check_budget_alert() method in ProcurementRequestViewSet
- Send notifications when budget < 20% (low) or < 10% (critical)
- Alerts sent to department head via NotificationService

Serializers:
- Add reserved_amount, available_amount to BudgetSerializer
- Add BudgetDetailSerializer with pending_requests list

Permissions:
- Fix led_departments → headed_departments in BudgetViewSet
- Allow my_department endpoint for all authenticated users

Tests:
- Add test_budget.py with 11 new tests
- TestBudgetReservedAmount (3 tests)
- TestBudgetMyDepartmentEndpoint (3 tests)
- TestProcurementStatsEndpoints (4 tests)
- TestBudgetAlerts (1 test)

All 52 procurement tests passing:
✅ 20/20 Model tests
✅ 12/12 API tests
✅ 9/9 Workflow tests
✅ 11/11 Budget tests (NEW)
2025-12-09 10:19:24 +03:00
f0e619e6fb fix: Extend ProcurementRequest queryset to include approvers
- Added Q(approvals__approver=user) filter to ProcurementRequestViewSet.get_queryset()
- Ensures users can see requests where they are designated approvers
- Added .distinct() to prevent duplicate results from approval JOIN
- Updated test_cannot_approve_without_permission to accept 404 (more secure)
- Cleaned up debug statements from test_workflow.py

This fixes the 404 errors in multi-level approval workflow:
- test_multi_level_approval: PASSED ✅
- test_cannot_approve_without_permission: PASSED ✅

All 41 procurement tests now passing: 20 models + 12 API + 9 workflow
2025-12-09 10:00:28 +03:00
814d383c15 fix(procurement): fix workflow tests - add estimated_cost and budget year
- Add estimated_cost to all test ProcurementRequests
- Fix budget year from 2024 Q1 to 2025 Q4
- Fix finance manager lookup to check user_permissions too
- Remove debug print from test_submit_request_creates_approvals

Result: 39/41 tests PASSED (was 38/41)
Remaining: 2 tests with 404 on /approve/ endpoint
2025-12-09 09:50:24 +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
d6e7e4ae08 refactor(procurement): replace Celery with NotificationService
BREAKING CHANGE: Removed Celery tasks in favor of existing notification system

Changes:
- ❌ Deleted tasks.py (261 lines) - Celery tasks removed
- ✅ Updated views.py - replaced .delay() calls with NotificationService
- ✅ Updated README.md - documented NotificationService integration
- ✅ Updated PROCUREMENT_IMPLEMENTATION_PLAN.md - architectural decision

Architecture Decision:
- Use NotificationService instead of Celery for notifications
- Supports Web (WebSocket), Email (HTML), Telegram channels
- Simpler deployment (no worker processes needed)
- Sufficient for 50-200 employees, <10 requests/day
- 0.5-2s email delay is acceptable for rare operations

Benefits:
- -2 production processes (no celery worker/beat)
- Easier debugging (sync execution, same logs)
- Already tested and used across all modules
- No DevOps overhead

Net result: -164 lines of code, simpler architecture
2025-12-09 09:19:58 +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
5ad51550c0 fix: add permission checks for create buttons in employee module templates
Changes:
1. employees_list.html - 'Create Profile' button now only visible to staff/superuser
   API requires staff/superuser for POST /employees/ (explicit check in create method)

2. department_list.html - Simplified permission check for 'Create Department' button
   Changed from: can_create_departments OR perms.employees.add_department OR is_staff
   To: is_staff OR is_superuser OR perms.employees.add_department
   API uses AdminOrActionOrModelPerms which checks employees.add_department

3. skill_list.html - 'Add Skill' button now requires authentication
   API allows any authenticated user to create skills (IsAuthenticated only)

Before:
- Department creation check was redundant with can_create_departments variable
- Skill creation had no check

After:
- All create buttons properly check permissions matching API requirements
- Consistent with feed and documents modules permission patterns
2025-12-08 14:24:40 +03:00
57b2d856a1 docs: document final solution for position permissions issue
Root cause identified:
- Template checked non-existent permissions (publish_company_post, publish_department_post)
- Django only creates 4 default permissions: add, change, delete, view
- User actually HAD feed.add_post permission from Position→Group chain

Solution:
- Changed template check to use feed.add_post (real permission)
- Confirmed PositionRoleBackend works correctly (13/13 tests passed)
- User can now see 'Create Post' button

Production diagnostic confirmed user has permissions through position:
Position 'СММ специалист' → Group 'Дизайн СММ Реклама' → Permissions include feed.add_post
2025-12-08 14:17:06 +03:00
7388f596a6 fix: correct permission check in feed_list.html template
Changed from non-existent permissions:
  - perms.feed.publish_company_post
  - perms.feed.publish_department_post

To actual Django model permission:
  - perms.feed.add_post

User diagnosis confirmed:
- User has Position 'СММ специалист'
- Position linked to Group 'Дизайн СММ Реклама'
- Group has permissions: add_post, change_post, delete_post, view_post
- PositionRoleBackend correctly grants these permissions

The 'Create Post' button will now appear for users with feed.add_post permission through their position groups.
2025-12-08 14:14:58 +03:00
78fc34287b docs: add production debugging guide for position permissions
Added comprehensive troubleshooting section:
- Django shell diagnostic script
- SQL queries to check database state
- Permission cache clearing instructions
- Template debugging
- Common issues and solutions
- Complete test results table (13/13 PASSED)

All tests confirmed PositionRoleBackend logic is correct.
If buttons still don't appear, issue is likely:
1. Permission caching (needs re-login)
2. LDAP group sync not working
3. Database missing Position→Groups→Permissions links
2025-12-08 14:06:01 +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
b9e24dd841 test: add comprehensive position permissions tests
Created pytest test suite to verify permission inheritance through positions:

Test Coverage:
1. User without position has no permissions (PASS)
2. User with position gets all group permissions (needs fix)
3. Position change updates permissions dynamically
4. Remove position removes all permissions
5. Multiple users with same position get same permissions
6. Staff users keep all permissions regardless
7. Multiple groups' permissions are additive
8. get_all_permissions() includes position perms
9. Position without groups grants no permissions (PASS)
10. Inactive users have no permissions
11. Direct user permissions + position permissions combine
12. Feed/Documents integration tests

Current Results:
- 3 tests PASSED (basic cases)
- 10 tests need fixing (fixture scope issues)

Purpose:
These tests will validate that PositionRoleBackend correctly
grants permissions from Position.groups to users, which is
needed for features like:
- Show 'Create Post' button for users with publishing rights
- Show 'Create Document' button for users with document rights
- Consistent permission checks across frontend and backend

Next Steps:
1. Fix IntegrityError in permission creation (use filter first)
2. Make fixtures work across test classes
3. Run tests to verify PositionRoleBackend implementation
4. If tests fail, fix the backend implementation
2025-12-08 13:54:28 +03:00
b6f1e09ba5 fix: prevent double form submission in employee edit form
Problem:
- When editing employee profile, got duplicate PATCH requests
- One request returned 502, another 200 OK
- Error appeared in console but operation succeeded
- Classic double-submit race condition

Root Cause:
initEmployeeForm() added new addEventListener('submit') on every call.
If function was called twice (e.g., on tab navigation or re-render),
two event handlers would fire simultaneously, sending parallel requests.

Solution:
1. Check if form already initialized via data-attribute
2. Set form.dataset.employeeFormInitialized = 'true' flag
3. Skip re-initialization if already initialized
4. Return API object for already initialized forms

This ensures only ONE submit handler per form, preventing:
- Duplicate PATCH requests
- 502 errors from race conditions
- Confusing UX with errors that don't break functionality

Now form submits cleanly with single request.
2025-12-08 13:37:20 +03:00