from decimal import Decimal from drf_spectacular.utils import extend_schema_field from rest_framework import serializers from finance.models import ( PayrollStatement, PayrollStatementAcknowledgement, PayrollStatementPaymentConfirmation, PayrollStatementLine, PayrollDailyWorkEntry, PayrollWorkRecord, ) class PayrollStatementLineSerializer(serializers.ModelSerializer): is_automatic_carryover = serializers.SerializerMethodField() class Meta: model = PayrollStatementLine fields = [ "code", "label", "kind", "amount", "source_period_from", "source_period_to", "is_retro", "is_automatic_carryover", "calculated", ] def get_is_automatic_carryover(self, obj) -> bool: return obj.source_ref.startswith("api:payroll-carryover:") or obj.source_ref.startswith( "payroll-carryover:" ) class PayrollStatementAcknowledgementSerializer(serializers.ModelSerializer): class Meta: model = PayrollStatementAcknowledgement fields = ["viewed_at", "acknowledged_at", "disputed_at"] class PayrollConfirmedBySerializer(serializers.Serializer): id = serializers.IntegerField() display_name = serializers.CharField() class PayrollStatementPaymentConfirmationSerializer(serializers.ModelSerializer): confirmed_by = serializers.SerializerMethodField() class Meta: model = PayrollStatementPaymentConfirmation fields = ["confirmed_at", "confirmed_by"] read_only_fields = fields @extend_schema_field(PayrollConfirmedBySerializer) def get_confirmed_by(self, obj) -> dict[str, object]: user = obj.confirmed_by return { "id": user.pk, "display_name": user.get_full_name().strip() or f"Сотрудник #{user.pk}", } class PayrollPeriodPayloadSerializer(serializers.Serializer): code = serializers.CharField() name = serializers.CharField() date_from = serializers.DateField() date_to = serializers.DateField() pay_date = serializers.DateField(allow_null=True) class OwnPayrollPaymentConfirmationWriteSerializer(serializers.Serializer): confirmed = serializers.BooleanField() def period_payload(statement): period = statement.run.period return { "code": period.code, "name": period.name, "date_from": period.date_from, "date_to": period.date_to, "pay_date": period.pay_date, } def acknowledgement_payload(statement): try: acknowledgement = statement.acknowledgement except PayrollStatementAcknowledgement.DoesNotExist: return None return PayrollStatementAcknowledgementSerializer(acknowledgement).data def payment_confirmation_payload(statement): try: confirmation = statement.payment_confirmation except PayrollStatementPaymentConfirmation.DoesNotExist: return None return PayrollStatementPaymentConfirmationSerializer(confirmation).data class OwnPayrollStatementSummarySerializer(serializers.ModelSerializer): """List metadata only; detailed amounts/lines require the audited endpoint.""" period = serializers.SerializerMethodField() revision = serializers.IntegerField(source="run.revision", read_only=True) published_at = serializers.DateTimeField( source="run.published_at", read_only=True, ) acknowledgement = serializers.SerializerMethodField() payment_confirmation = serializers.SerializerMethodField() can_confirm_payment = serializers.SerializerMethodField() class Meta: model = PayrollStatement fields = [ "public_id", "period", "revision", "published_at", "currency", "payable", "acknowledgement", "payment_confirmation", "can_confirm_payment", ] read_only_fields = fields @extend_schema_field(PayrollPeriodPayloadSerializer) def get_period(self, obj) -> dict[str, object]: return period_payload(obj) @extend_schema_field( PayrollStatementAcknowledgementSerializer(allow_null=True) ) def get_acknowledgement(self, obj) -> dict[str, object] | None: return acknowledgement_payload(obj) @extend_schema_field( PayrollStatementPaymentConfirmationSerializer(allow_null=True) ) def get_payment_confirmation(self, obj) -> dict[str, object] | None: return payment_confirmation_payload(obj) def get_can_confirm_payment(self, obj) -> bool: return obj.run.period.status == "closed" class OwnPayrollStatementSerializer(serializers.ModelSerializer): period = serializers.SerializerMethodField() revision = serializers.IntegerField(source="run.revision", read_only=True) published_at = serializers.DateTimeField( source="run.published_at", read_only=True, ) lines = PayrollStatementLineSerializer(many=True, read_only=True) acknowledgement = serializers.SerializerMethodField() payment_confirmation = serializers.SerializerMethodField() can_confirm_payment = serializers.SerializerMethodField() class Meta: model = PayrollStatement fields = [ "public_id", "period", "revision", "published_at", "currency", "point_delta", "gross_before_adjustments", "adjustment_total", "gross_total", "deduction_total", "net_pay", "payment_total", "opening_balance", "closing_balance", "payable", "lines", "acknowledgement", "payment_confirmation", "can_confirm_payment", ] read_only_fields = fields @extend_schema_field(PayrollPeriodPayloadSerializer) def get_period(self, obj) -> dict[str, object]: return period_payload(obj) @extend_schema_field( PayrollStatementAcknowledgementSerializer(allow_null=True) ) def get_acknowledgement(self, obj) -> dict[str, object] | None: return acknowledgement_payload(obj) @extend_schema_field( PayrollStatementPaymentConfirmationSerializer(allow_null=True) ) def get_payment_confirmation(self, obj) -> dict[str, object] | None: return payment_confirmation_payload(obj) def get_can_confirm_payment(self, obj) -> bool: return obj.run.period.status == "closed" class OwnPayrollWorkRecordSerializer(serializers.ModelSerializer): status_label = serializers.CharField(source="get_status_display", read_only=True) class Meta: model = PayrollWorkRecord fields = [ "id", "period_id", "target_points", "target_points_overridden", "actual_points", "revision", "status", "status_label", "lock_version", "replaces_id", "reason", "source", "created_at", "updated_at", "approved_at", ] read_only_fields = fields class OwnPayrollDailyWorkEntrySerializer(serializers.ModelSerializer): class Meta: model = PayrollDailyWorkEntry fields = [ "id", "period_id", "work_date", "target_points", "actual_points", "note", "lock_version", "created_at", "updated_at", ] read_only_fields = fields class OwnPayrollDailyWorkEntryWriteSerializer(serializers.Serializer): period_id = serializers.IntegerField(min_value=1) work_date = serializers.DateField() actual_points = serializers.DecimalField( max_digits=19, decimal_places=4, min_value=Decimal("0"), ) note = serializers.CharField( allow_blank=True, required=False, trim_whitespace=True, ) expected_lock_version = serializers.IntegerField( min_value=0, required=False, )