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