158 lines
4.9 KiB
Python

from django.contrib.contenttypes.models import ContentType
from django.db.models import Count, IntegerField, OuterRef, Subquery, Value
from django.db.models.functions import Coalesce
from django.utils import timezone
from rest_framework.exceptions import PermissionDenied
from attendance.models import AttendanceRecord
from communications.models import Message
from employees.models import Employee
def _can_access_employee(user, employee_id) -> bool:
try:
normalized_employee_id = int(employee_id)
except (TypeError, ValueError):
return False
return bool(
user
and user.is_authenticated
and (
user.is_staff or user.is_superuser or normalized_employee_id == int(user.id)
)
)
def _check_employee_access(user, employee_id) -> None:
if not _can_access_employee(user, employee_id):
raise PermissionDenied(
"You don't have permission to access this attendance data"
)
def _records_visible_to(user):
queryset = AttendanceRecord.objects.all()
if user.is_staff or user.is_superuser:
return queryset
return queryset.filter(employee_id=user.id)
def _day_event_records_visible_to(user):
"""Allow payroll viewers to inspect evidence used by point projections."""
queryset = AttendanceRecord.objects.all()
if user.is_staff or user.is_superuser or user.has_perm("finance.view_all_payroll"):
return queryset
return queryset.filter(employee_id=user.id)
def _check_staff_access(user) -> None:
if not (user and user.is_authenticated and (user.is_staff or user.is_superuser)):
raise PermissionDenied("You don't have permission to manage attendance")
def _has_attendance_pass(record: AttendanceRecord) -> bool:
return bool(
record.arrival_time
or record.departure_time
or (record.work_hours is not None and record.work_hours > 0)
)
def _has_absence_marker(record: AttendanceRecord) -> bool:
values = [
record.statuses,
record.employee_issues,
]
return bool(
record.is_absent
or any(
"absent" in normalized
or "absence" in normalized
or "отсутств" in normalized
for items in values
for normalized in [str(item).strip().lower() for item in (items or [])]
)
)
def _employee_attendance_aliases(record: AttendanceRecord) -> list[str]:
return [
str(alias).strip()
for alias in (getattr(record.employee, "attendance_aliases", None) or [])
if str(alias).strip()
]
def _attendance_employee_summary(employee: Employee) -> dict[str, str | int]:
full_name = f"{employee.last_name or ''} {employee.first_name or ''}".strip()
if getattr(employee, "patronymic", ""):
full_name = f"{full_name} {employee.patronymic}".strip()
return {
"id": employee.id,
"name": full_name or employee.email or f"Сотрудник #{employee.id}",
"email": employee.email or "",
}
def _comments_count_annotation():
record_ct = ContentType.objects.get_for_model(AttendanceRecord)
comments_subquery = (
Message.objects.filter(
chat__type="comments",
chat__context_content_type=record_ct,
chat__context_object_id=OuterRef("pk"),
is_deleted=False,
)
.values("chat")
.annotate(count=Count("id"))
.values("count")
)
return {
"comments_count": Coalesce(
Subquery(comments_subquery, output_field=IntegerField()),
Value(0),
)
}
def _comment_author_name(author) -> str:
if not author:
return "Сотрудник"
first_name = getattr(author, "first_name", "") or ""
last_name = getattr(author, "last_name", "") or ""
full_name = f"{last_name} {first_name}".strip()
return full_name or getattr(author, "email", "") or "Сотрудник"
def _attendance_record_comments_map(
record_ids: list[int],
) -> dict[int, list[dict[str, str]]]:
if not record_ids:
return {}
record_ct = ContentType.objects.get_for_model(AttendanceRecord)
messages = (
Message.objects.filter(
chat__type="comments",
chat__context_content_type=record_ct,
chat__context_object_id__in=record_ids,
is_deleted=False,
)
.select_related("author", "chat")
.order_by("created_at", "id")
)
result: dict[int, list[dict[str, str]]] = {}
for message in messages:
record_id = int(message.chat.context_object_id)
result.setdefault(record_id, []).append(
{
"author": _comment_author_name(message.author),
"text": message.content,
"created_at": timezone.localtime(message.created_at).strftime(
"%d.%m.%Y %H:%M"
),
}
)
return result