from decimal import Decimal from django.contrib.auth import get_user_model from rest_framework import serializers from commerce.models import SourceSystem from projects.models import ( Project, ProjectExternalBinding, ProjectMembership, ProjectResource, ProjectStage, ) class ProjectSerializer(serializers.ModelSerializer): manager_name = serializers.SerializerMethodField() can_manage = serializers.SerializerMethodField() can_view_finances = serializers.SerializerMethodField() class Meta: model = Project fields = [ "id", "name", "description", "customer", "manager", "manager_name", "status", "starts_on", "ends_on", "created_at", "updated_at", "can_manage", "can_view_finances", ] read_only_fields = ["created_at", "updated_at"] extra_kwargs = { "manager": {"queryset": get_user_model().objects.filter(is_active=True)} } def get_manager_name(self, obj) -> str: return str(obj.manager) def get_can_manage(self, obj) -> bool: from projects.permissions import can_manage return can_manage(self.context["request"].user, obj) def get_can_view_finances(self, obj) -> bool: cached = getattr(self, "_can_view_finances", None) if cached is None: from finance.payroll.access import can_access_payroll_management cached = can_access_payroll_management(self.context["request"].user) self._can_view_finances = cached return cached def validate(self, attrs): start = attrs.get( "starts_on", self.instance.starts_on if self.instance else None ) end = attrs.get("ends_on", self.instance.ends_on if self.instance else None) if start and end and end < start: raise serializers.ValidationError("Окончание не может быть раньше начала.") return attrs class MembershipSerializer(serializers.ModelSerializer): employee_name = serializers.SerializerMethodField() class Meta: model = ProjectMembership fields = [ "id", "employee", "employee_name", "role", "planned_percent", "starts_on", "ends_on", ] extra_kwargs = { "planned_percent": {"min_value": 0, "max_value": 100}, "employee": {"queryset": get_user_model().objects.filter(is_active=True)}, } def get_employee_name(self, obj): return str(obj.employee) def validate(self, attrs): if ( attrs.get("starts_on") and attrs.get("ends_on") and attrs["ends_on"] < attrs["starts_on"] ): raise serializers.ValidationError("Окончание участия раньше начала.") return attrs class StageSerializer(serializers.ModelSerializer): class Meta: model = ProjectStage fields = ["id", "name", "due_on", "done", "position"] class BindingSerializer(serializers.ModelSerializer): source_name = serializers.CharField(source="source.name", read_only=True) class Meta: model = ProjectExternalBinding fields = [ "id", "source", "source_name", "object_type", "external_id", "external_name", ] validators = [] class ResourceSerializer(serializers.ModelSerializer): class Meta: model = ProjectResource fields = ["id", "title", "kind", "url"] def validate(self, attrs): from urllib.parse import urlsplit url = urlsplit(attrs["url"]) prefix = { "task": "/tasks", "procurement": "/procurement", "document": "/documents", }[attrs["kind"]] if ( url.scheme or url.netloc or "\\" in attrs["url"] or not (url.path == prefix or url.path.startswith(prefix + "/")) ): raise serializers.ValidationError( "Укажите внутреннюю ссылку соответствующего раздела портала." ) return attrs class AllocationRowSerializer(serializers.Serializer): project = serializers.IntegerField(min_value=1, required=False, allow_null=True) activity = serializers.ChoiceField( choices=["", "department", "training", "support"], default="" ) percent = serializers.DecimalField( max_digits=5, decimal_places=2, min_value=Decimal("0.01"), max_value=100 ) class DaySerializer(serializers.Serializer): date = serializers.DateField() version = serializers.IntegerField(min_value=0) status = serializers.ChoiceField(choices=["draft", "confirmed"]) note = serializers.CharField(max_length=2000, allow_blank=True, default="") allocations = AllocationRowSerializer(many=True, max_length=50) class DayProjectSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField() class DayAllocationResponseSerializer(serializers.Serializer): project = serializers.IntegerField(allow_null=True) project_name = serializers.CharField() activity = serializers.CharField() percent = serializers.CharField() class CalendarDayResponseSerializer(serializers.Serializer): date = serializers.DateField() status = serializers.ChoiceField(choices=["draft", "confirmed"]) hours = serializers.FloatField(allow_null=True) hours_source = serializers.CharField() version = serializers.IntegerField() class DayResponseSerializer(CalendarDayResponseSerializer): attendance_changed = serializers.BooleanField() note = serializers.CharField() allocations = DayAllocationResponseSerializer(many=True) projects = DayProjectSerializer(many=True) class ReminderRequestSerializer(serializers.Serializer): snooze = serializers.BooleanField(required=False, default=False) class ReminderResponseSerializer(serializers.Serializer): date = serializers.DateField(allow_null=True) class ExternalProjectImportSerializer(serializers.Serializer): source = serializers.PrimaryKeyRelatedField(queryset=SourceSystem.objects.all()) external_id = serializers.CharField(max_length=255) name = serializers.CharField(max_length=255) description = serializers.CharField(max_length=5000, allow_blank=True, default="") customer = serializers.CharField(max_length=255, allow_blank=True, default="") manager = serializers.PrimaryKeyRelatedField( queryset=get_user_model().objects.filter(is_active=True) ) status = serializers.ChoiceField(choices=Project.Status.choices, default="planned") starts_on = serializers.DateField(required=False, allow_null=True) ends_on = serializers.DateField(required=False, allow_null=True) def validate(self, attrs): if attrs.get("starts_on") and attrs.get("ends_on"): if attrs["ends_on"] < attrs["starts_on"]: raise serializers.ValidationError( "Окончание не может быть раньше начала." ) return attrs