852 lines
29 KiB
Python
852 lines
29 KiB
Python
from django.contrib.auth import get_user_model
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.utils import timezone
|
|
from drf_spectacular.utils import extend_schema_field
|
|
from rest_framework import serializers
|
|
|
|
from attendance.models import AttendanceRecord
|
|
from api.v1.documents.serializers import DocumentReadSerializer
|
|
from api.v1.employees.serializers import EmployeeBriefSerializer
|
|
from communications.models import Message
|
|
from communications.comments_helpers import get_comment_count
|
|
from communications.serialization import serialize_message
|
|
from communications.utils import user_can_access_chat
|
|
from documents.models import Document
|
|
from employees.models import EmployeeDepartment
|
|
from employees.services.personnel_state import resolve_employee_personnel_state
|
|
from feed.models import Post
|
|
from guests.models import Guest, GuestVisit
|
|
from procurement.models import ProcurementRequest
|
|
from requests_app.models import Request as EmployeeRequest
|
|
from schedule.models import Event
|
|
from api.v1.schedule.serializers import EventSerializer
|
|
from tasks.access import (
|
|
user_can_access_calendar_event,
|
|
user_can_access_document,
|
|
user_can_access_employee,
|
|
user_can_access_employee_request,
|
|
user_can_access_feed_post,
|
|
user_can_access_guest,
|
|
user_can_access_guest_visit,
|
|
user_can_access_attendance_record,
|
|
user_can_access_procurement_request,
|
|
)
|
|
from tasks.models import (
|
|
TaskCover,
|
|
TaskCoverType,
|
|
TaskLinkedObject,
|
|
TaskLinkedObjectKind,
|
|
)
|
|
from .resources import TaskAttachmentSerializer, TaskExternalLinkSerializer
|
|
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class TaskLinkedObjectSerializer(serializers.ModelSerializer):
|
|
"""Shared access and navigation contract for linked task resources."""
|
|
|
|
created_by = EmployeeBriefSerializer(read_only=True)
|
|
can_open = serializers.SerializerMethodField()
|
|
object_url = serializers.SerializerMethodField()
|
|
linked_kind = None
|
|
|
|
def _linked_source(self, obj):
|
|
if obj.kind != self.linked_kind:
|
|
return None
|
|
return getattr(obj, "content_object", None)
|
|
|
|
def _can_open_linked_object(self, obj, access_checker):
|
|
source = self._linked_source(obj)
|
|
request = self.context.get("request")
|
|
user = getattr(request, "user", None)
|
|
return bool(source is not None and user and access_checker(user, source))
|
|
|
|
def _linked_object_url(self, obj, url_builder):
|
|
source = self._linked_source(obj)
|
|
if source is None or not self.get_can_open(obj):
|
|
return None
|
|
return url_builder(source)
|
|
|
|
|
|
class TaskLinkedPostSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.POST
|
|
post = serializers.SerializerMethodField()
|
|
post_id = serializers.IntegerField(source="object_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"post_id",
|
|
"post",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_post(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.POST:
|
|
return None
|
|
|
|
post = getattr(obj, "content_object", None)
|
|
if post is None:
|
|
return None
|
|
|
|
request = self.context.get("request")
|
|
from api.v1.utils import build_media_url
|
|
|
|
return {
|
|
"id": post.id,
|
|
"type": post.type,
|
|
"department": post.department_id,
|
|
"department_id": post.department_id,
|
|
"department_name": getattr(post.department, "name", None),
|
|
"title": post.title,
|
|
"body": post.body,
|
|
"image": build_media_url(post.image, request),
|
|
"attachment": build_media_url(post.attachment, request),
|
|
"created_at": post.created_at,
|
|
"pinned": post.pinned_global or post.pinned_department,
|
|
"pinned_global": post.pinned_global,
|
|
"pinned_department": post.pinned_department,
|
|
"likes_count": post.likes_count,
|
|
"comments_count": get_comment_count(post),
|
|
"author_id": post.author_id,
|
|
"author": EmployeeBriefSerializer(post.author).data,
|
|
}
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(obj, user_can_access_feed_post)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(obj, lambda source: f"/?post={source.id}")
|
|
|
|
|
|
def get_post_content_type():
|
|
return ContentType.objects.get_for_model(Post)
|
|
|
|
|
|
class TaskLinkedMessageSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.MESSAGE
|
|
message = serializers.SerializerMethodField()
|
|
message_id = serializers.IntegerField(source="object_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"message_id",
|
|
"message",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_message(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.MESSAGE:
|
|
return None
|
|
|
|
message = getattr(obj, "content_object", None)
|
|
if message is None:
|
|
return None
|
|
|
|
return serialize_message(message, include_linked_tasks=False)
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(
|
|
obj, lambda user, source: user_can_access_chat(source.chat, user)
|
|
)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(
|
|
obj, lambda source: f"/messages/{source.chat_id}?message={source.id}"
|
|
)
|
|
|
|
|
|
def get_message_content_type():
|
|
return ContentType.objects.get_for_model(Message)
|
|
|
|
|
|
class TaskLinkedCalendarEventSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.CALENDAR_EVENT
|
|
event = serializers.SerializerMethodField()
|
|
event_id = serializers.IntegerField(source="object_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"event_id",
|
|
"event",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_event(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.CALENDAR_EVENT:
|
|
return None
|
|
|
|
event = getattr(obj, "content_object", None)
|
|
if event is None:
|
|
return None
|
|
|
|
return EventSerializer(event, context=self.context).data
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(obj, user_can_access_calendar_event)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(
|
|
obj,
|
|
lambda source: f"/calendar?calendar={source.calendar_id}&event={source.id}",
|
|
)
|
|
|
|
|
|
def get_calendar_event_content_type():
|
|
return ContentType.objects.get_for_model(Event)
|
|
|
|
|
|
class TaskLinkedDocumentSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.DOCUMENT
|
|
document = serializers.SerializerMethodField()
|
|
document_id = serializers.IntegerField(source="object_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"document_id",
|
|
"document",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_document(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.DOCUMENT:
|
|
return None
|
|
|
|
document = getattr(obj, "content_object", None)
|
|
if document is None:
|
|
return None
|
|
|
|
if not self.get_can_open(obj):
|
|
return {
|
|
"id": document.id,
|
|
"title": document.title,
|
|
"description": document.description,
|
|
"is_regulation": document.is_regulation,
|
|
"folder_path": document.folder_path,
|
|
"file_name": (
|
|
getattr(document.file, "name", None) if document.file else None
|
|
),
|
|
}
|
|
|
|
return DocumentReadSerializer(document, context=self.context).data
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(obj, user_can_access_document)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(
|
|
obj, lambda source: f"/documents?document={source.id}"
|
|
)
|
|
|
|
|
|
def get_document_content_type():
|
|
return ContentType.objects.get_for_model(Document)
|
|
|
|
|
|
class TaskLinkedRequestSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.REQUEST
|
|
request = serializers.SerializerMethodField()
|
|
request_id = serializers.IntegerField(source="object_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"request_id",
|
|
"request",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_request(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.REQUEST:
|
|
return None
|
|
|
|
request_obj = getattr(obj, "content_object", None)
|
|
if request_obj is None:
|
|
return None
|
|
|
|
return {
|
|
"id": request_obj.id,
|
|
"title": request_obj.title,
|
|
"display_title": request_obj.display_title,
|
|
"type": request_obj.type,
|
|
"type_display": request_obj.get_type_display(),
|
|
"status": request_obj.status,
|
|
"status_display": request_obj.get_status_display(),
|
|
"comment": request_obj.comment,
|
|
"date_from": request_obj.date_from,
|
|
"date_to": request_obj.date_to,
|
|
"employee": EmployeeBriefSerializer(request_obj.employee).data,
|
|
"created_at": request_obj.created_at,
|
|
"updated_at": request_obj.updated_at,
|
|
}
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(obj, user_can_access_employee_request)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(
|
|
obj, lambda source: f"/requests?request={source.id}"
|
|
)
|
|
|
|
|
|
def get_request_content_type():
|
|
return ContentType.objects.get_for_model(EmployeeRequest)
|
|
|
|
|
|
class TaskLinkedProcurementRequestSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.PROCUREMENT_REQUEST
|
|
procurement_request = serializers.SerializerMethodField()
|
|
procurement_request_id = serializers.IntegerField(
|
|
source="object_id", read_only=True
|
|
)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"procurement_request_id",
|
|
"procurement_request",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_procurement_request(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.PROCUREMENT_REQUEST:
|
|
return None
|
|
|
|
procurement_request = getattr(obj, "content_object", None)
|
|
if procurement_request is None:
|
|
return None
|
|
|
|
return {
|
|
"id": procurement_request.id,
|
|
"title": procurement_request.title,
|
|
"description": procurement_request.description,
|
|
"status": procurement_request.status,
|
|
"status_display": procurement_request.get_status_display(),
|
|
"urgency": procurement_request.urgency,
|
|
"urgency_display": procurement_request.get_urgency_display(),
|
|
"fulfillment_status": procurement_request.fulfillment_status,
|
|
"fulfillment_status_display": procurement_request.get_fulfillment_status_display(),
|
|
"department_id": procurement_request.department_id,
|
|
"department_name": (
|
|
procurement_request.department.name
|
|
if procurement_request.department_id
|
|
else None
|
|
),
|
|
"processing_department_id": procurement_request.processing_department_id,
|
|
"processing_department_name": (
|
|
procurement_request.processing_department.name
|
|
if procurement_request.processing_department_id
|
|
else None
|
|
),
|
|
"requestor": EmployeeBriefSerializer(procurement_request.requestor).data,
|
|
"executor": (
|
|
EmployeeBriefSerializer(procurement_request.executor).data
|
|
if procurement_request.executor_id
|
|
else None
|
|
),
|
|
"total_cost": procurement_request.total_cost,
|
|
"items_count": procurement_request.items_count,
|
|
"created_at": procurement_request.created_at,
|
|
"updated_at": procurement_request.updated_at,
|
|
}
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(obj, user_can_access_procurement_request)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(
|
|
obj, lambda source: f"/procurement?request={source.id}"
|
|
)
|
|
|
|
|
|
def get_procurement_request_content_type():
|
|
return ContentType.objects.get_for_model(ProcurementRequest)
|
|
|
|
|
|
class TaskLinkedEmployeeSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.EMPLOYEE
|
|
employee = serializers.SerializerMethodField()
|
|
employee_id = serializers.IntegerField(source="object_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"employee_id",
|
|
"employee",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_employee(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.EMPLOYEE:
|
|
return None
|
|
|
|
employee = getattr(obj, "content_object", None)
|
|
if employee is None:
|
|
return None
|
|
|
|
departments = []
|
|
links = getattr(employee, "dept_links", None)
|
|
if links is None:
|
|
links = EmployeeDepartment.objects.select_related(
|
|
"department",
|
|
"role",
|
|
).filter(employee=employee, is_active=True)
|
|
departments = [
|
|
{
|
|
"id": link.department_id,
|
|
"name": link.department.name if link.department_id else None,
|
|
"role_id": link.role_id,
|
|
"role_name": link.role.name if link.role_id else None,
|
|
"is_head": (
|
|
link.department.head_id == employee.id
|
|
if link.department_id
|
|
else False
|
|
),
|
|
}
|
|
for link in links
|
|
]
|
|
personnel_state = resolve_employee_personnel_state(
|
|
employee,
|
|
timezone.localdate(),
|
|
)
|
|
|
|
return {
|
|
**EmployeeBriefSerializer(employee).data,
|
|
"position": (
|
|
{
|
|
"id": employee.position_id,
|
|
"name": employee.position.name,
|
|
}
|
|
if employee.position_id
|
|
else None
|
|
),
|
|
"departments": departments,
|
|
"is_active": employee.is_active,
|
|
"personnel_state": {
|
|
"status": personnel_state.status,
|
|
"label": personnel_state.label or "Работает",
|
|
"action_id": personnel_state.action_id,
|
|
"date_from": personnel_state.date_from,
|
|
"date_to": personnel_state.date_to,
|
|
"expects_attendance": personnel_state.expects_attendance,
|
|
},
|
|
}
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(obj, user_can_access_employee)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(obj, lambda source: f"/users/{source.id}")
|
|
|
|
|
|
def get_employee_content_type():
|
|
return ContentType.objects.get_for_model(User)
|
|
|
|
|
|
def _guest_payload(guest):
|
|
return {
|
|
"id": guest.id,
|
|
"full_name": guest.full_name,
|
|
"last_name": guest.last_name,
|
|
"first_name": guest.first_name,
|
|
"patronymic": guest.patronymic,
|
|
"birth_date": guest.birth_date,
|
|
"phone": guest.phone,
|
|
"email": guest.email,
|
|
"avatar": guest.avatar.url if getattr(guest, "avatar", None) else None,
|
|
"organization": guest.organization,
|
|
"position": guest.position,
|
|
"is_active": guest.is_active,
|
|
"is_blacklisted": guest.is_blacklisted,
|
|
"ldap_username": guest.ldap_username,
|
|
"ldap_upn": guest.ldap_upn,
|
|
"created_at": guest.created_at,
|
|
"updated_at": guest.updated_at,
|
|
}
|
|
|
|
|
|
class TaskLinkedGuestSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.GUEST
|
|
guest = serializers.SerializerMethodField()
|
|
guest_id = serializers.IntegerField(source="object_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"guest_id",
|
|
"guest",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_guest(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.GUEST:
|
|
return None
|
|
|
|
guest = getattr(obj, "content_object", None)
|
|
if guest is None:
|
|
return None
|
|
return _guest_payload(guest)
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(obj, user_can_access_guest)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(obj, lambda source: f"/guests?guest={source.id}")
|
|
|
|
|
|
def get_guest_content_type():
|
|
return ContentType.objects.get_for_model(Guest)
|
|
|
|
|
|
class TaskLinkedGuestVisitSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.GUEST_VISIT
|
|
guest_visit = serializers.SerializerMethodField()
|
|
guest_visit_id = serializers.IntegerField(source="object_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"guest_visit_id",
|
|
"guest_visit",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_guest_visit(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.GUEST_VISIT:
|
|
return None
|
|
|
|
visit = getattr(obj, "content_object", None)
|
|
if visit is None:
|
|
return None
|
|
|
|
return {
|
|
"id": visit.id,
|
|
"guest": _guest_payload(visit.guest),
|
|
"inviter": EmployeeBriefSerializer(visit.inviter).data,
|
|
"inviter_snapshot_name": visit.inviter_snapshot_name,
|
|
"inviter_snapshot_email": visit.inviter_snapshot_email,
|
|
"purpose": visit.purpose,
|
|
"visit_comment": visit.visit_comment,
|
|
"status": visit.status,
|
|
"status_display": visit.get_status_display(),
|
|
"access_starts_at": visit.access_starts_at,
|
|
"access_expires_at": visit.access_expires_at,
|
|
"all_day": visit.all_day,
|
|
"unlimited": visit.unlimited,
|
|
"submitted_at": visit.submitted_at,
|
|
"is_active_now": visit.is_active_now,
|
|
"is_expired": visit.is_expired,
|
|
"created_at": visit.created_at,
|
|
"updated_at": visit.updated_at,
|
|
}
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(obj, user_can_access_guest_visit)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(obj, lambda source: f"/guests?visit={source.id}")
|
|
|
|
|
|
def get_guest_visit_content_type():
|
|
return ContentType.objects.get_for_model(GuestVisit)
|
|
|
|
|
|
class TaskLinkedAttendanceRecordSerializer(TaskLinkedObjectSerializer):
|
|
linked_kind = TaskLinkedObjectKind.ATTENDANCE_RECORD
|
|
attendance_record = serializers.SerializerMethodField()
|
|
attendance_record_id = serializers.IntegerField(source="object_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskLinkedObject
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"attendance_record_id",
|
|
"attendance_record",
|
|
"can_open",
|
|
"object_url",
|
|
"created_by",
|
|
"created_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
def get_attendance_record(self, obj):
|
|
if obj.kind != TaskLinkedObjectKind.ATTENDANCE_RECORD:
|
|
return None
|
|
|
|
record = getattr(obj, "content_object", None)
|
|
if record is None:
|
|
return None
|
|
|
|
return {
|
|
"id": record.id,
|
|
"employee_id": record.employee_id,
|
|
"employee": EmployeeBriefSerializer(record.employee).data,
|
|
"date": record.date,
|
|
"display_name": record.display_name,
|
|
"arrival_time": record.arrival_time,
|
|
"departure_time": record.departure_time,
|
|
"work_hours": record.work_hours,
|
|
"expected_hours": record.expected_hours,
|
|
"is_workday": record.is_workday,
|
|
"effective_is_workday": record.effective_is_workday,
|
|
"is_late": record.is_late,
|
|
"late_minutes": record.late_minutes,
|
|
"is_early_leave": record.is_early_leave,
|
|
"early_leave_minutes": record.early_leave_minutes,
|
|
"is_underwork": record.is_underwork,
|
|
"underwork_hours": record.underwork_hours,
|
|
"is_overtime": record.is_overtime,
|
|
"overtime_hours": record.overtime_hours,
|
|
"is_absent": record.is_absent,
|
|
"statuses": record.statuses,
|
|
"employee_issues": record.employee_issues,
|
|
"technical_issues": record.technical_issues,
|
|
"personnel_status": record.personnel_status,
|
|
"personnel_status_label": record.personnel_status_label,
|
|
"comments_count": get_comment_count(record),
|
|
"updated_at": record.updated_at,
|
|
}
|
|
|
|
@extend_schema_field(serializers.BooleanField())
|
|
def get_can_open(self, obj):
|
|
return self._can_open_linked_object(obj, user_can_access_attendance_record)
|
|
|
|
@extend_schema_field(serializers.CharField(allow_null=True))
|
|
def get_object_url(self, obj):
|
|
return self._linked_object_url(
|
|
obj, lambda source: f"/attendance?record={source.id}"
|
|
)
|
|
|
|
|
|
def get_attendance_record_content_type():
|
|
return ContentType.objects.get_for_model(AttendanceRecord)
|
|
|
|
|
|
def _linked_object_cover_payload(link, context):
|
|
source = getattr(link, "content_object", None)
|
|
kind = link.kind
|
|
title = link.get_kind_display()
|
|
description = ""
|
|
image_url = None
|
|
|
|
if source is not None:
|
|
if kind == TaskLinkedObjectKind.POST:
|
|
title = source.title or "Новость"
|
|
description = source.body or ""
|
|
if source.image:
|
|
from api.v1.utils import build_media_url
|
|
|
|
image_url = build_media_url(source.image, context.get("request"))
|
|
elif kind == TaskLinkedObjectKind.MESSAGE:
|
|
title = source.author.get_full_name() or source.author.email or "Сообщение"
|
|
description = source.content or ""
|
|
elif kind == TaskLinkedObjectKind.CALENDAR_EVENT:
|
|
title = getattr(source, "title", None) or "Календарное событие"
|
|
description = getattr(source, "description", "") or ""
|
|
elif kind == TaskLinkedObjectKind.DOCUMENT:
|
|
title = source.title or "Документ"
|
|
description = source.description or ""
|
|
elif kind == TaskLinkedObjectKind.REQUEST:
|
|
title = source.display_title or source.title or "Заявление"
|
|
description = source.comment or ""
|
|
elif kind == TaskLinkedObjectKind.PROCUREMENT_REQUEST:
|
|
title = source.title or "Заявка на закупку"
|
|
description = source.description or ""
|
|
elif kind == TaskLinkedObjectKind.EMPLOYEE:
|
|
title = source.get_full_name() or source.email or "Сотрудник"
|
|
description = getattr(source, "position_name", "") or ""
|
|
elif kind == TaskLinkedObjectKind.GUEST:
|
|
title = source.full_name or "Гость"
|
|
description = source.organization or ""
|
|
elif kind == TaskLinkedObjectKind.GUEST_VISIT:
|
|
title = source.guest.full_name or "Гостевой визит"
|
|
description = source.purpose or ""
|
|
elif kind == TaskLinkedObjectKind.ATTENDANCE_RECORD:
|
|
title = source.display_name or "Запись посещаемости"
|
|
description = str(source.date)
|
|
|
|
return {
|
|
"id": link.id,
|
|
"kind": kind,
|
|
"kind_display": link.get_kind_display(),
|
|
"object_id": link.object_id,
|
|
"title": str(title),
|
|
"description": str(description),
|
|
"image_url": image_url,
|
|
}
|
|
|
|
|
|
class TaskCoverSerializer(serializers.ModelSerializer):
|
|
kind_display = serializers.CharField(source="get_kind_display", read_only=True)
|
|
attachment = serializers.SerializerMethodField()
|
|
checklist = serializers.SerializerMethodField()
|
|
comment = serializers.SerializerMethodField()
|
|
external_link = serializers.SerializerMethodField()
|
|
linked_object = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = TaskCover
|
|
fields = [
|
|
"id",
|
|
"kind",
|
|
"kind_display",
|
|
"attachment",
|
|
"checklist",
|
|
"comment",
|
|
"external_link",
|
|
"linked_object",
|
|
"updated_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
@extend_schema_field(serializers.DictField(allow_null=True))
|
|
def get_attachment(self, obj):
|
|
if obj.kind != TaskCoverType.ATTACHMENT or obj.attachment is None:
|
|
return None
|
|
return TaskAttachmentSerializer(
|
|
obj.attachment,
|
|
context=self.context,
|
|
).data
|
|
|
|
@extend_schema_field(serializers.DictField(allow_null=True))
|
|
def get_checklist(self, obj):
|
|
if obj.kind != TaskCoverType.CHECKLIST:
|
|
return None
|
|
items = list(obj.task.checklist_items.all())
|
|
return {
|
|
"total": len(items),
|
|
"completed": sum(item.is_completed for item in items),
|
|
"items": [
|
|
{
|
|
"id": item.id,
|
|
"title": item.title,
|
|
"is_completed": item.is_completed,
|
|
}
|
|
for item in items[:6]
|
|
],
|
|
}
|
|
|
|
@extend_schema_field(serializers.DictField(allow_null=True))
|
|
def get_comment(self, obj):
|
|
message = obj.comment
|
|
if obj.kind != TaskCoverType.COMMENT or message is None or message.is_deleted:
|
|
return None
|
|
attachments = [
|
|
attachment
|
|
for attachment in message.task_attachments.all()
|
|
if attachment.task_id == obj.task_id
|
|
]
|
|
return {
|
|
"id": message.id,
|
|
"author": EmployeeBriefSerializer(message.author).data,
|
|
"text": message.content,
|
|
"created_at": message.created_at,
|
|
"attachments": TaskAttachmentSerializer(
|
|
attachments,
|
|
many=True,
|
|
context=self.context,
|
|
).data,
|
|
}
|
|
|
|
@extend_schema_field(serializers.DictField(allow_null=True))
|
|
def get_external_link(self, obj):
|
|
if obj.kind != TaskCoverType.EXTERNAL_LINK or obj.external_link is None:
|
|
return None
|
|
return TaskExternalLinkSerializer(
|
|
obj.external_link,
|
|
context=self.context,
|
|
).data
|
|
|
|
@extend_schema_field(serializers.DictField(allow_null=True))
|
|
def get_linked_object(self, obj):
|
|
if obj.kind != TaskCoverType.LINKED_OBJECT or obj.linked_object is None:
|
|
return None
|
|
return _linked_object_cover_payload(obj.linked_object, self.context)
|