Corp/backend/api/v1/procurement/permissions.py
Игорь Надеин 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

516 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Права доступа для модуля закупок.
"""
from rest_framework import permissions
from api.v1.permissions import has_dept_perm
from employees.constants import DeptPerm
from employees.models import Department, EmployeeDepartment
class IsDepartmentHead(permissions.BasePermission):
"""Проверка, что пользователь - руководитель отдела."""
def has_permission(self, request, view):
if not request.user or not request.user.is_authenticated:
return False
if request.user.is_superuser or request.user.is_staff:
return True
# Проверяем, является ли пользователь руководителем
# какого-либо отдела
return request.user.led_departments.exists()
class IsFinanceManager(permissions.BasePermission):
"""Проверка, что пользователь - финансовый менеджер."""
def has_permission(self, request, view):
if not request.user or not request.user.is_authenticated:
return False
if request.user.is_superuser or request.user.is_staff:
return True
# Проверяем наличие права на управление бюджетами
return request.user.has_perm('procurement.change_budget')
class IsDirector(permissions.BasePermission):
"""Проверка, что пользователь - директор."""
def has_permission(self, request, view):
if not request.user or not request.user.is_authenticated:
return False
# Директор = суперпользователь или есть специальное право
return (
request.user.is_superuser or
request.user.has_perm('procurement.approve_procurementrequest')
)
class CanCreateProcurementRequest(permissions.BasePermission):
"""Проверка права на создание заявки на закупку."""
def has_permission(self, request, view):
from employees.models import Employee
if not request.user or not request.user.is_authenticated:
return False
if request.method in permissions.SAFE_METHODS:
return True
if request.method == 'POST':
# Любой авторизованный сотрудник может создать заявку
return isinstance(request.user, Employee)
return True
class CanEditOwnProcurementRequest(permissions.BasePermission):
"""Проверка права на редактирование своей заявки."""
def has_object_permission(self, request, view, obj):
if not request.user or not request.user.is_authenticated:
return False
# Чтение доступно всем
if request.method in permissions.SAFE_METHODS:
return True
# Суперпользователь может все
if request.user.is_superuser:
return True
# Редактировать можно только свою заявку в статусе DRAFT
if request.method in ['PUT', 'PATCH', 'DELETE']:
return (
obj.requestor == request.user and
obj.is_editable
)
return False
class CanApproveProcurementRequest(permissions.BasePermission):
"""Проверка права на согласование заявки."""
def has_object_permission(self, request, view, obj):
if not request.user or not request.user.is_authenticated:
return False
if request.user.is_superuser:
return True
# Получаем требуемые роли для согласования
required_roles = obj.get_required_approvals()
# Проверяем, имеет ли пользователь одну из требуемых ролей
from procurement.constants import ApprovalRole
if ApprovalRole.DEPARTMENT_HEAD in required_roles:
# Руководитель отдела заявителя
if obj.department.head == request.user:
return True
if ApprovalRole.FINANCE_MANAGER in required_roles:
# Финансовый менеджер
if request.user.has_perm('procurement.change_budget'):
return True
if ApprovalRole.DIRECTOR in required_roles:
# Директор
if request.user.has_perm(
'procurement.approve_procurementrequest'
):
return True
return False
class CanManageProcurementRequest(permissions.BasePermission):
"""Комплексная проверка прав на заявки закупок.
Логика доступа:
- Просмотр (SAFE_METHODS): любой аутентифицированный (фильтрация в queryset)
- Создание:
- admin/staff/superuser → любой отдел
- модельные права (add_procurementrequest) → любой отдел
- сотрудник отдела → только свой отдел
- без отдела → запрещено
- Изменение:
- admin/staff/superuser → любая заявка в DRAFT
- модельные права (change_procurementrequest) → любая заявка в DRAFT
- автор заявки → своя заявка в DRAFT
- начальник отдела → заявки своего отдела в DRAFT
- Удаление:
- admin/staff/superuser → любая заявка
- модельные права (delete_procurementrequest) → любая заявка
- автор заявки → своя заявка в DRAFT
- начальник отдела → заявки своего отдела в DRAFT
"""
def _is_admin(self, user) -> bool:
"""Проверяет, является ли пользователь админом."""
return (
getattr(user, 'is_superuser', False) or
getattr(user, 'is_staff', False)
)
def _has_model_perm(self, user, action: str) -> bool:
"""Проверяет модельные права на заявки."""
perm_map = {
'create': 'procurement.add_procurementrequest',
'update': 'procurement.change_procurementrequest',
'partial_update': 'procurement.change_procurementrequest',
'destroy': 'procurement.delete_procurementrequest',
}
perm = perm_map.get(action)
return perm and user.has_perm(perm)
def _get_user_departments(self, user) -> list[int]:
"""Возвращает список ID отделов, где пользователь состоит."""
return list(
EmployeeDepartment.objects.filter(
employee_id=user.id,
is_active=True
).values_list('department_id', flat=True)
)
def _get_dept_id_from_request(self, request) -> int | None:
"""Извлекает ID отдела из данных запроса."""
dept_id = request.data.get('department')
if dept_id is not None:
try:
return int(dept_id)
except (ValueError, TypeError):
pass
return None
def _is_dept_head(self, user, dept_id: int) -> bool:
"""Проверяет, является ли пользователь начальником отдела."""
return Department.objects.filter(id=dept_id, head_id=user.id).exists()
def has_permission(self, request, view) -> bool:
"""Проверка доступа на уровне запроса."""
user = request.user
if not user or not user.is_authenticated:
return False
# Чтение доступно всем аутентифицированным
if request.method in permissions.SAFE_METHODS:
return True
# Админы могут всё
if self._is_admin(user):
return True
action = getattr(view, 'action', None)
# Модельные права дают полный доступ
if self._has_model_perm(user, action):
return True
# Для создания проверяем отдел
if action == 'create':
dept_id = self._get_dept_id_from_request(request)
if dept_id is None:
return False
# Пользователь должен состоять в указанном отделе
user_depts = self._get_user_departments(user)
if not user_depts:
# Пользователь без отдела не может создавать
return False
if dept_id not in user_depts:
# Нельзя создавать заявку в чужом отделе
return False
return True
# Для update/delete проверка на уровне объекта
if action in ('update', 'partial_update', 'destroy'):
return True
return True
def has_object_permission(self, request, view, obj) -> bool:
"""Проверка доступа к конкретному объекту."""
user = request.user
if not user or not user.is_authenticated:
return False
# Чтение доступно всем
if request.method in permissions.SAFE_METHODS:
return True
# Админы могут всё
if self._is_admin(user):
return True
action = getattr(view, 'action', None)
# Модельные права
if self._has_model_perm(user, action):
return True
dept_id = obj.department_id
# Submit (отправка на согласование) - только владелец
if action == 'submit':
return obj.requestor == user
# Cancel (отмена заявки) - только владелец
if action == 'cancel':
return obj.requestor == user
# Start_work (взять в работу) - любой авторизованный
if action == 'start_work':
return True
# Complete (завершить) - только исполнитель
if action == 'complete':
return obj.executor == user
# Удаление
if action == 'destroy':
# Автор может удалить свою заявку в DRAFT
if obj.requestor == user and obj.is_editable:
return True
# Начальник отдела может удалить заявку отдела в DRAFT
if self._is_dept_head(user, dept_id) and obj.is_editable:
return True
return False
# Изменение
if action in ('update', 'partial_update'):
# Заявка должна быть редактируемой (DRAFT)
if not obj.is_editable:
return False
# Автор может редактировать свою заявку
if obj.requestor == user:
return True
# Начальник отдела может редактировать заявки отдела
if self._is_dept_head(user, dept_id):
return True
return False
return False
class CanManageEquipment(permissions.BasePermission):
"""Проверка права на управление оборудованием.
Логика доступа:
- Просмотр (SAFE_METHODS): любой аутентифицированный
- Создание:
- admin/staff/superuser → ✅
- модельные права (add_equipment) → ✅
- начальник отдела (department из request.data) → ✅
- скоуп-право MANAGE_EQUIPMENT в отделе → ✅
- Изменение:
- admin/staff/superuser → ✅
- модельные права (change_equipment) → ✅
- начальник отдела оборудования → ✅
- скоуп-право MANAGE_EQUIPMENT в отделе → ✅
- ответственный за оборудование → ✅
- Удаление:
- admin/staff/superuser → ✅
- модельные права (delete_equipment) → ✅
- начальник отдела оборудования → ✅
- НЕ уполномоченный, НЕ ответственный
"""
def _is_admin(self, user) -> bool:
"""Проверяет, является ли пользователь админом."""
return (
getattr(user, 'is_superuser', False) or
getattr(user, 'is_staff', False)
)
def _has_model_perm(self, user, action: str) -> bool:
"""Проверяет модельные права на оборудование."""
perm_map = {
'create': 'procurement.add_equipment',
'update': 'procurement.change_equipment',
'partial_update': 'procurement.change_equipment',
'destroy': 'procurement.delete_equipment',
}
perm = perm_map.get(action)
return perm and user.has_perm(perm)
def _get_dept_id_from_request(self, request) -> int | None:
"""Извлекает ID отдела из данных запроса."""
dept_id = request.data.get('department')
if dept_id is not None:
try:
return int(dept_id)
except (ValueError, TypeError):
pass
return None
def _is_dept_head(self, user, dept_id: int) -> bool:
"""Проверяет, является ли пользователь начальником отдела."""
return Department.objects.filter(id=dept_id, head_id=user.id).exists()
def _has_scoped_perm(self, user, dept_id: int) -> bool:
"""Проверяет наличие скоуп-права MANAGE_EQUIPMENT в отделе."""
return has_dept_perm(user, dept_id, DeptPerm.MANAGE_EQUIPMENT)
def _get_user_departments(self, user) -> list[int]:
"""Возвращает список ID отделов, где пользователь состоит."""
return list(
EmployeeDepartment.objects.filter(
employee_id=user.id,
is_active=True
).values_list('department_id', flat=True)
)
def has_permission(self, request, view) -> bool:
"""Проверка доступа на уровне запроса."""
user = request.user
if not user or not user.is_authenticated:
return False
# Чтение доступно всем аутентифицированным
if request.method in permissions.SAFE_METHODS:
return True
# Админы могут всё
if self._is_admin(user):
return True
action = getattr(view, 'action', None)
# Модельные права дают полный доступ
if self._has_model_perm(user, action):
return True
# Для создания нужен отдел из запроса
if action == 'create':
dept_id = self._get_dept_id_from_request(request)
if dept_id is None:
return False
# Начальник отдела может создавать
if self._is_dept_head(user, dept_id):
return True
# Проверяем скоуп-право в отделе
if self._has_scoped_perm(user, dept_id):
return True
return False
# Для update/delete проверка будет на уровне объекта
if action in ('update', 'partial_update', 'destroy'):
# Пропускаем на has_object_permission
return True
return False
def has_object_permission(self, request, view, obj) -> bool:
"""Проверка доступа к конкретному объекту."""
user = request.user
if not user or not user.is_authenticated:
return False
# Чтение доступно всем
if request.method in permissions.SAFE_METHODS:
return True
# Админы могут всё
if self._is_admin(user):
return True
action = getattr(view, 'action', None)
# Модельные права
if self._has_model_perm(user, action):
return True
dept_id = obj.department_id
# Начальник отдела оборудования
if self._is_dept_head(user, dept_id):
return True
# Удаление — только админ, модельные права или начальник
if action == 'destroy':
return False
# Изменение: скоуп-право или ответственный
if action in ('update', 'partial_update'):
# Скоуп-право в отделе
if self._has_scoped_perm(user, dept_id):
return True
# Ответственный за оборудование
if obj.responsible_person_id == user.id:
return True
return False
class CanManageBudget(permissions.BasePermission):
"""Проверка права на управление бюджетами."""
def has_permission(self, request, view):
if not request.user or not request.user.is_authenticated:
return False
# Чтение доступно руководителям отделов
if request.method in permissions.SAFE_METHODS:
return (
request.user.is_staff or
request.user.headed_departments.exists()
)
# Создание/изменение - только финансовые менеджеры
return (
request.user.is_staff or
request.user.has_perm('procurement.change_budget')
)
class CanManageSupplier(permissions.BasePermission):
"""Проверка права на управление поставщиками."""
def has_permission(self, request, view):
if not request.user or not request.user.is_authenticated:
return False
# Чтение доступно всем сотрудникам
if request.method in permissions.SAFE_METHODS:
return True
# Создание/изменение - только для staff
return (
request.user.is_staff or
request.user.has_perm('procurement.add_supplier')
)
class IsResponsibleForEquipment(permissions.BasePermission):
"""Проверка, что пользователь - ответственный за оборудование."""
def has_object_permission(self, request, view, obj):
if not request.user or not request.user.is_authenticated:
return False
return (
request.user.is_staff or
obj.responsible_person == request.user
)