Corp/backend/api/v1/finance/admin_serializers.py

988 lines
33 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Serializers for the native payroll administration workspace.
Workflow fields, hashes, source references and idempotency keys intentionally
never form part of a writable/read payload here. State transitions are owned
by :mod:`finance.payroll.services`.
"""
from __future__ import annotations
from decimal import Decimal
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError as DjangoValidationError
from rest_framework import serializers
from api.v1.employees.serializers import EmployeeBriefSerializer
from finance.enums import (
ApprovalStatus,
InputSource,
PayrollCarryoverDirection,
PayrollCarryoverStatus,
)
from finance.models import (
EmployeePayRate,
PayrollCarryover,
PayrollInputLine,
PayrollPeriod,
PayrollRun,
PayrollWorkRecord,
)
from finance.payroll.components import (
PAYROLL_COMPONENT_CODE_CHOICES,
)
from finance.payroll.work_norm import calculate_period_target_points
Employee = get_user_model()
def _validation_detail(exc: DjangoValidationError):
if hasattr(exc, "message_dict"):
return exc.message_dict
return {"non_field_errors": exc.messages}
class PayrollEmployeeMiniSerializer(serializers.ModelSerializer):
display_name = serializers.SerializerMethodField()
position = serializers.SerializerMethodField()
department = serializers.SerializerMethodField()
class Meta:
model = Employee
fields = ["id", "display_name", "position", "department"]
read_only_fields = fields
def get_display_name(self, obj) -> str:
return obj.get_full_name().strip() or f"Сотрудник #{obj.pk}"
def get_position(self, obj) -> str | None:
position = getattr(obj, "position", None)
return position.name if position is not None else None
def get_department(self, obj) -> str | None:
prefetched = getattr(obj, "_prefetched_objects_cache", {}).get(
"departments_links"
)
if prefetched is None:
link = (
obj.departments_links.filter(is_active=True)
.select_related("department")
.order_by("department__name", "id")
.first()
)
else:
link = next(
(
item
for item in sorted(
prefetched,
key=lambda item: (item.department.name, item.pk),
)
if item.is_active
),
None,
)
return link.department.name if link is not None else None
class PayrollUserRefSerializer(serializers.ModelSerializer):
display_name = serializers.SerializerMethodField()
class Meta:
model = Employee
fields = ["id", "display_name"]
read_only_fields = fields
def get_display_name(self, obj) -> str:
return obj.get_full_name().strip() or f"Сотрудник #{obj.pk}"
class PayrollCellCommentSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
author = EmployeeBriefSerializer(read_only=True)
text = serializers.CharField(read_only=True)
created_at = serializers.DateTimeField(read_only=True)
class PayrollCellCommentWriteSerializer(serializers.Serializer):
text = serializers.CharField(max_length=10000, trim_whitespace=True)
class PayrollComponentSerializer(serializers.Serializer):
id = serializers.CharField(read_only=True)
code = serializers.CharField(read_only=True)
name = serializers.CharField(read_only=True)
kind = serializers.CharField(read_only=True)
kind_label = serializers.CharField(source="get_kind_display", read_only=True)
requires_reason = serializers.BooleanField(read_only=True)
is_active = serializers.BooleanField(read_only=True)
display_order = serializers.IntegerField(read_only=True)
class PayrollPeriodSerializer(serializers.ModelSerializer):
status_label = serializers.CharField(source="get_status_display", read_only=True)
current_run_id = serializers.IntegerField(read_only=True, allow_null=True)
created_by_id = serializers.IntegerField(read_only=True)
class Meta:
model = PayrollPeriod
fields = [
"id",
"code",
"name",
"date_from",
"date_to",
"pay_date",
"currency",
"status",
"status_label",
"current_run_id",
"lock_version",
"created_by_id",
"created_at",
"updated_at",
]
read_only_fields = fields
class PayrollCarryoverPeriodSerializer(serializers.ModelSerializer):
class Meta:
model = PayrollPeriod
fields = ["id", "code", "name", "date_from", "date_to", "currency"]
read_only_fields = fields
class PayrollCarryoverSerializer(serializers.ModelSerializer):
employee = PayrollEmployeeMiniSerializer(read_only=True)
source_period = PayrollCarryoverPeriodSerializer(read_only=True)
target_period = PayrollCarryoverPeriodSerializer(read_only=True)
direction_label = serializers.CharField(
source="get_direction_display",
read_only=True,
)
status_label = serializers.CharField(source="get_status_display", read_only=True)
created_by = PayrollUserRefSerializer(read_only=True)
generated_input_line_id = serializers.IntegerField(read_only=True, allow_null=True)
can_cancel = serializers.SerializerMethodField()
class Meta:
model = PayrollCarryover
fields = [
"id",
"source_period",
"target_period",
"employee",
"direction",
"direction_label",
"amount",
"reason",
"status",
"status_label",
"generated_input_line_id",
"created_by",
"created_at",
"transferred_at",
"cancelled_at",
"can_cancel",
]
read_only_fields = fields
def get_can_cancel(self, obj) -> bool:
return obj.status != PayrollCarryoverStatus.CANCELLED and (
obj.generated_input_line_id is None
or obj.generated_input_line.status == ApprovalStatus.DRAFT
)
class PayrollCarryoverWriteSerializer(serializers.Serializer):
employee_id = serializers.PrimaryKeyRelatedField(
source="employee",
queryset=Employee.objects.all(),
)
direction = serializers.ChoiceField(choices=PayrollCarryoverDirection.choices)
amount = serializers.DecimalField(
max_digits=19,
decimal_places=2,
min_value=Decimal("0.01"),
)
reason = serializers.CharField(allow_blank=False, trim_whitespace=True)
class PayrollPeriodWriteSerializer(serializers.ModelSerializer):
expected_lock_version = serializers.IntegerField(
min_value=0,
required=False,
write_only=True,
)
class Meta:
model = PayrollPeriod
fields = [
"code",
"name",
"date_from",
"date_to",
"pay_date",
"currency",
"expected_lock_version",
]
def validate(self, attrs):
if self.instance is not None and "expected_lock_version" not in attrs:
raise serializers.ValidationError(
{"expected_lock_version": "Укажите открытую версию периода."}
)
return attrs
class DraftWriteSerializerMixin:
"""Create manual drafts and update them through model optimistic locking."""
expected_lock_version = serializers.IntegerField(
min_value=0,
required=False,
write_only=True,
)
def validate(self, attrs):
attrs = super().validate(attrs)
if self.instance is not None and "expected_lock_version" not in attrs:
raise serializers.ValidationError(
{"expected_lock_version": "Укажите открытую версию черновика."}
)
return attrs
def create(self, validated_data):
validated_data.pop("expected_lock_version", None)
instance = self.Meta.model(
**validated_data,
status=ApprovalStatus.DRAFT,
source=InputSource.MANUAL,
created_by=self.context["actor"],
)
try:
instance.full_clean()
except DjangoValidationError as exc:
raise serializers.ValidationError(_validation_detail(exc)) from exc
instance.save(force_insert=True)
return instance
def update(self, instance, validated_data):
expected_lock_version = validated_data.pop("expected_lock_version")
for field_name, value in validated_data.items():
setattr(instance, field_name, value)
instance._expected_lock_version = expected_lock_version
try:
instance.full_clean()
instance.save()
except DjangoValidationError as exc:
raise serializers.ValidationError(_validation_detail(exc)) from exc
return instance
class EmployeePayRateSerializer(serializers.ModelSerializer):
employee = PayrollEmployeeMiniSerializer(read_only=True)
employee_id = serializers.IntegerField(read_only=True)
status_label = serializers.CharField(source="get_status_display", read_only=True)
created_by_id = serializers.IntegerField(read_only=True)
approved_by_id = serializers.IntegerField(read_only=True, allow_null=True)
voided_by_id = serializers.IntegerField(read_only=True, allow_null=True)
replaces_id = serializers.IntegerField(read_only=True, allow_null=True)
created_by = PayrollUserRefSerializer(read_only=True)
approved_by = PayrollUserRefSerializer(read_only=True)
class Meta:
model = EmployeePayRate
fields = [
"id",
"employee_id",
"employee",
"rate_code",
"amount",
"point_rate",
"currency",
"effective_from",
"revision",
"status",
"status_label",
"lock_version",
"replaces_id",
"reason",
"source",
"created_by_id",
"created_by",
"created_at",
"updated_at",
"approved_by_id",
"approved_by",
"approved_at",
"voided_by_id",
"voided_at",
]
read_only_fields = fields
class EmployeePayRateWriteSerializer(
DraftWriteSerializerMixin,
serializers.ModelSerializer,
):
expected_lock_version = DraftWriteSerializerMixin.expected_lock_version
employee_id = serializers.PrimaryKeyRelatedField(
source="employee",
queryset=Employee.objects.all(),
)
class Meta:
model = EmployeePayRate
fields = [
"employee_id",
"rate_code",
"amount",
"point_rate",
"currency",
"effective_from",
"reason",
"expected_lock_version",
]
class EmployeePayRateRevisionSerializer(serializers.Serializer):
amount = serializers.DecimalField(max_digits=19, decimal_places=4, required=False)
point_rate = serializers.DecimalField(
max_digits=19,
decimal_places=4,
min_value=0,
required=False,
allow_null=True,
)
currency = serializers.CharField(max_length=3, required=False)
reason = serializers.CharField(allow_blank=False, trim_whitespace=True)
class BulkPointRateCommandSerializer(serializers.Serializer):
MODE_FIXED = "fixed"
MODE_IN_NORM = "in_norm"
employee_ids = serializers.ListField(
child=serializers.IntegerField(min_value=1),
allow_empty=False,
)
mode = serializers.ChoiceField(
choices=(MODE_FIXED, MODE_IN_NORM),
required=False,
)
point_rate = serializers.DecimalField(
max_digits=19,
decimal_places=4,
min_value=0,
required=False,
allow_null=True,
)
reason = serializers.CharField(allow_blank=False, trim_whitespace=True)
def validate_employee_ids(self, value):
return list(dict.fromkeys(value))
def validate(self, attrs):
attrs = super().validate(attrs)
mode = attrs.get("mode")
if mode is None:
mode = (
self.MODE_FIXED
if attrs.get("point_rate") is not None
else self.MODE_IN_NORM
)
attrs["mode"] = mode
if mode == self.MODE_FIXED and attrs.get("point_rate") is None:
raise serializers.ValidationError(
{
"point_rate": "Укажите фиксированную цену или выберите автоматический расчёт."
}
)
if mode == self.MODE_IN_NORM:
attrs["point_rate"] = None
return attrs
class BulkPayRateCommandSerializer(serializers.Serializer):
employee_ids = serializers.ListField(
child=serializers.IntegerField(min_value=1),
allow_empty=False,
)
amount = serializers.DecimalField(
max_digits=19,
decimal_places=4,
min_value=Decimal("0.0001"),
)
effective_from = serializers.DateField()
reason = serializers.CharField(allow_blank=False, trim_whitespace=True)
def validate_employee_ids(self, value):
return list(dict.fromkeys(value))
class BulkTargetPointsCommandSerializer(serializers.Serializer):
MODE_FIXED = "fixed"
MODE_AUTOMATIC = "automatic"
employee_ids = serializers.ListField(
child=serializers.IntegerField(min_value=1),
allow_empty=False,
)
mode = serializers.ChoiceField(choices=(MODE_FIXED, MODE_AUTOMATIC))
target_points = serializers.DecimalField(
max_digits=19,
decimal_places=4,
min_value=Decimal("0.0001"),
required=False,
allow_null=True,
)
reason = serializers.CharField(allow_blank=False, trim_whitespace=True)
def validate_employee_ids(self, value):
return list(dict.fromkeys(value))
def validate(self, attrs):
attrs = super().validate(attrs)
if attrs["mode"] == self.MODE_FIXED and attrs.get("target_points") is None:
raise serializers.ValidationError(
{"target_points": "Укажите норму баллов."}
)
if attrs["mode"] == self.MODE_AUTOMATIC:
attrs["target_points"] = None
return attrs
class PayrollWorkRecordSerializer(serializers.ModelSerializer):
employee = PayrollEmployeeMiniSerializer(read_only=True)
employee_id = serializers.IntegerField(read_only=True)
period_id = serializers.IntegerField(read_only=True)
status_label = serializers.CharField(source="get_status_display", read_only=True)
created_by_id = serializers.IntegerField(read_only=True)
approved_by_id = serializers.IntegerField(read_only=True, allow_null=True)
voided_by_id = serializers.IntegerField(read_only=True, allow_null=True)
replaces_id = serializers.IntegerField(read_only=True, allow_null=True)
created_by = PayrollUserRefSerializer(read_only=True)
approved_by = PayrollUserRefSerializer(read_only=True)
class Meta:
model = PayrollWorkRecord
fields = [
"id",
"period_id",
"employee_id",
"employee",
"target_points",
"target_points_overridden",
"actual_points",
"expected_point_amount",
"expected_gross",
"expected_recalculated_gross",
"expected_payable",
"revision",
"status",
"status_label",
"lock_version",
"replaces_id",
"reason",
"source",
"created_by_id",
"created_by",
"created_at",
"updated_at",
"approved_by_id",
"approved_by",
"approved_at",
"voided_by_id",
"voided_at",
]
read_only_fields = fields
class PayrollWorkRecordWriteSerializer(
DraftWriteSerializerMixin,
serializers.ModelSerializer,
):
expected_lock_version = DraftWriteSerializerMixin.expected_lock_version
period_id = serializers.PrimaryKeyRelatedField(
source="period",
queryset=PayrollPeriod.objects.all(),
)
employee_id = serializers.PrimaryKeyRelatedField(
source="employee",
queryset=Employee.objects.all(),
)
target_points = serializers.DecimalField(
max_digits=19,
decimal_places=4,
min_value=Decimal("0.0001"),
required=False,
allow_null=True,
)
class Meta:
model = PayrollWorkRecord
fields = [
"period_id",
"employee_id",
"target_points",
"actual_points",
"expected_point_amount",
"expected_gross",
"expected_recalculated_gross",
"expected_payable",
"reason",
"expected_lock_version",
]
def validate(self, attrs):
attrs = super().validate(attrs)
target_was_sent = "target_points" in attrs
should_calculate = (self.instance is None and not target_was_sent) or attrs.get(
"target_points"
) is None
if should_calculate:
period = attrs.get("period") or self.instance.period
employee = attrs.get("employee") or self.instance.employee
target_points, _, _ = calculate_period_target_points(
period,
employee=employee,
)
attrs["target_points"] = target_points
attrs["target_points_overridden"] = False
elif target_was_sent:
attrs["target_points_overridden"] = True
return attrs
class PayrollWorkRecordRevisionSerializer(serializers.Serializer):
target_points = serializers.DecimalField(
max_digits=19,
decimal_places=4,
min_value=0,
required=False,
)
actual_points = serializers.DecimalField(
max_digits=19,
decimal_places=4,
min_value=0,
required=False,
)
expected_point_amount = serializers.DecimalField(
max_digits=19,
decimal_places=2,
allow_null=True,
required=False,
)
expected_gross = serializers.DecimalField(
max_digits=19,
decimal_places=2,
allow_null=True,
required=False,
)
expected_recalculated_gross = serializers.DecimalField(
max_digits=19,
decimal_places=2,
allow_null=True,
required=False,
)
expected_payable = serializers.DecimalField(
max_digits=19,
decimal_places=2,
allow_null=True,
required=False,
)
reason = serializers.CharField(allow_blank=False, trim_whitespace=True)
class PayrollInputLineSerializer(serializers.ModelSerializer):
employee = PayrollEmployeeMiniSerializer(read_only=True)
employee_id = serializers.IntegerField(read_only=True)
period_id = serializers.IntegerField(read_only=True)
component = serializers.SerializerMethodField()
component_code = serializers.CharField(read_only=True)
component_id = serializers.CharField(source="component_code", read_only=True)
relates_to_period_id = serializers.IntegerField(read_only=True, allow_null=True)
relates_to_period = PayrollCarryoverPeriodSerializer(read_only=True)
is_automatic_carryover = serializers.SerializerMethodField()
status_label = serializers.CharField(source="get_status_display", read_only=True)
created_by_id = serializers.IntegerField(read_only=True)
approved_by_id = serializers.IntegerField(read_only=True, allow_null=True)
voided_by_id = serializers.IntegerField(read_only=True, allow_null=True)
created_by = PayrollUserRefSerializer(read_only=True)
approved_by = PayrollUserRefSerializer(read_only=True)
class Meta:
model = PayrollInputLine
fields = [
"id",
"period_id",
"employee_id",
"employee",
"component_code",
"component_id",
"component",
"amount",
"relates_to_period_id",
"relates_to_period",
"is_automatic_carryover",
"include_in_carryover",
"reason",
"status",
"status_label",
"lock_version",
"source",
"created_by_id",
"created_by",
"created_at",
"approved_by_id",
"approved_by",
"approved_at",
"voided_by_id",
"voided_at",
]
read_only_fields = fields
def get_is_automatic_carryover(self, obj) -> bool:
return obj.source == InputSource.API and obj.source_ref.startswith(
"payroll-carryover:"
)
def get_component(self, obj) -> dict:
return PayrollComponentSerializer(obj.component).data
class PayrollCarryoverInclusionSerializer(serializers.Serializer):
included = serializers.BooleanField()
class PayrollInputLineWriteSerializer(
DraftWriteSerializerMixin,
serializers.ModelSerializer,
):
expected_lock_version = DraftWriteSerializerMixin.expected_lock_version
period_id = serializers.PrimaryKeyRelatedField(
source="period",
queryset=PayrollPeriod.objects.all(),
)
employee_id = serializers.PrimaryKeyRelatedField(
source="employee",
queryset=Employee.objects.all(),
)
component_code = serializers.ChoiceField(
choices=PAYROLL_COMPONENT_CODE_CHOICES,
required=False,
)
component_id = serializers.ChoiceField(
source="component_code",
choices=PAYROLL_COMPONENT_CODE_CHOICES,
required=False,
write_only=True,
)
relates_to_period_id = serializers.PrimaryKeyRelatedField(
source="relates_to_period",
queryset=PayrollPeriod.objects.all(),
allow_null=True,
required=False,
)
class Meta:
model = PayrollInputLine
fields = [
"period_id",
"employee_id",
"component_code",
"component_id",
"amount",
"relates_to_period_id",
"reason",
"expected_lock_version",
]
def validate(self, attrs):
if not attrs.get("component_code"):
raise serializers.ValidationError(
{"component_code": "Обязательное поле."}
)
return attrs
class ApprovalCommandSerializer(serializers.Serializer):
expected_lock_version = serializers.IntegerField(min_value=0)
class ClearComponentCellCommandSerializer(serializers.Serializer):
employee_id = serializers.IntegerField(min_value=1)
component_code = serializers.ChoiceField(
choices=PAYROLL_COMPONENT_CODE_CHOICES,
required=False,
)
component_id = serializers.ChoiceField(
source="component_code",
choices=PAYROLL_COMPONENT_CODE_CHOICES,
required=False,
write_only=True,
)
def validate(self, attrs):
if not attrs.get("component_code"):
raise serializers.ValidationError({"component_code": "Обязательное поле."})
return attrs
class SetComponentCellCommandSerializer(ClearComponentCellCommandSerializer):
amount = serializers.DecimalField(
max_digits=19,
decimal_places=2,
min_value=Decimal("0.01"),
)
class PayrollPaymentConfirmationCommandSerializer(serializers.Serializer):
confirmed = serializers.BooleanField()
class CopyPayrollInputLinesCommandSerializer(serializers.Serializer):
source_period_id = serializers.IntegerField(min_value=1)
component_codes = serializers.ListField(
child=serializers.ChoiceField(choices=PAYROLL_COMPONENT_CODE_CHOICES),
allow_empty=False,
required=False,
)
component_ids = serializers.ListField(
source="component_codes",
child=serializers.ChoiceField(choices=PAYROLL_COMPONENT_CODE_CHOICES),
allow_empty=False,
required=False,
write_only=True,
)
employee_scope = serializers.ChoiceField(
choices=("all", "active", "selected"),
)
existing_mode = serializers.ChoiceField(choices=("skip", "replace"))
employee_ids = serializers.ListField(
child=serializers.IntegerField(min_value=1),
required=False,
default=list,
)
expected_period_lock_version = serializers.IntegerField(min_value=0)
idempotency_key = serializers.UUIDField()
remember_configuration = serializers.BooleanField(default=False)
auto_apply_on_period_creation = serializers.BooleanField(default=False)
def validate(self, attrs):
if not attrs.get("component_codes"):
raise serializers.ValidationError(
{"component_codes": "Выберите хотя бы один вид операции."}
)
attrs["component_codes"] = list(dict.fromkeys(attrs["component_codes"]))
attrs["employee_ids"] = list(dict.fromkeys(attrs["employee_ids"]))
if attrs["employee_scope"] == "selected" and not attrs["employee_ids"]:
raise serializers.ValidationError(
{"employee_ids": "Выберите хотя бы одного сотрудника."}
)
if attrs["employee_scope"] != "selected":
attrs["employee_ids"] = []
if attrs["auto_apply_on_period_creation"] and not attrs["remember_configuration"]:
raise serializers.ValidationError(
{
"auto_apply_on_period_creation": (
"Для автоприменения сначала сохраните конфигурацию."
)
}
)
return attrs
class CalculatePayrollCommandSerializer(serializers.Serializer):
expected_lock_version = serializers.IntegerField(min_value=0)
recalculation_reason = serializers.CharField(
allow_blank=True,
required=False,
default="",
trim_whitespace=True,
)
idempotency_key = serializers.UUIDField(required=False, allow_null=True)
class AttendanceWorkImportCommandSerializer(serializers.Serializer):
mode = serializers.ChoiceField(
choices=("missing_only", "replace_existing"),
)
preview_token = serializers.CharField(min_length=64, max_length=64)
expected_period_lock_version = serializers.IntegerField(min_value=0)
reason = serializers.CharField(
allow_blank=True,
required=False,
default="",
trim_whitespace=True,
)
def validate(self, attrs):
if attrs["mode"] == "replace_existing" and not attrs["reason"]:
raise serializers.ValidationError(
{"reason": "Для пересчёта существующих записей укажите причину."}
)
return attrs
class WorkbookWorkPreviewSerializer(serializers.Serializer):
file = serializers.FileField()
def validate_file(self, value):
if not value.name.lower().endswith(".xlsx"):
raise serializers.ValidationError("Выберите файл Excel в формате .xlsx.")
if value.size > 10 * 1024 * 1024:
raise serializers.ValidationError("Excel-файл должен быть не больше 10 МБ.")
return value
class WorkbookWorkImportSerializer(WorkbookWorkPreviewSerializer):
mode = serializers.ChoiceField(choices=("skip_existing", "replace_existing"))
mappings = serializers.CharField(required=False, default="{}")
expected_file_hash = serializers.RegexField(regex=r"^[0-9a-f]{64}$")
expected_period_lock_version = serializers.IntegerField(min_value=0)
class ReturnPayrollCommandSerializer(serializers.Serializer):
reason = serializers.CharField(allow_blank=False, trim_whitespace=True)
class PayrollRunSerializer(serializers.ModelSerializer):
period_id = serializers.IntegerField(read_only=True)
status_label = serializers.CharField(source="get_status_display", read_only=True)
requested_by_id = serializers.IntegerField(read_only=True)
approved_by_id = serializers.IntegerField(read_only=True, allow_null=True)
published_by_id = serializers.IntegerField(read_only=True, allow_null=True)
supersedes_id = serializers.IntegerField(read_only=True, allow_null=True)
requested_by = PayrollUserRefSerializer(read_only=True)
approved_by = PayrollUserRefSerializer(read_only=True)
published_by = PayrollUserRefSerializer(read_only=True)
class Meta:
model = PayrollRun
fields = [
"id",
"period_id",
"revision",
"status",
"status_label",
"supersedes_id",
"recalculation_reason",
"employee_count",
"gross_total",
"deduction_total",
"payable_total",
"requested_by_id",
"requested_by",
"requested_at",
"approved_by_id",
"approved_by",
"approved_at",
"published_by_id",
"published_by",
"published_at",
]
read_only_fields = fields
def to_representation(self, instance):
data = super().to_representation(instance)
if not self.context.get("include_amounts", False):
for field_name in ("gross_total", "deduction_total", "payable_total"):
data[field_name] = None
return data
class PayrollAccessSettingsWriteSerializer(serializers.Serializer):
management_user_ids = serializers.ListField(
child=serializers.IntegerField(min_value=1),
)
class PayrollAccessSettingsSerializer(serializers.Serializer):
restricted = serializers.BooleanField(read_only=True)
management_user_ids = serializers.ListField(
child=serializers.IntegerField(),
read_only=True,
)
management_user_count = serializers.IntegerField(read_only=True)
candidates = EmployeeBriefSerializer(many=True, read_only=True)
class PayrollAdminWorkspaceSerializer(serializers.Serializer):
permissions = serializers.DictField(
child=serializers.BooleanField(),
read_only=True,
)
access_settings = serializers.JSONField(read_only=True)
employees = PayrollEmployeeMiniSerializer(many=True, read_only=True)
rate_employees = PayrollEmployeeMiniSerializer(many=True, read_only=True)
components = PayrollComponentSerializer(many=True, read_only=True)
periods = PayrollPeriodSerializer(many=True, read_only=True)
selected_period = PayrollPeriodSerializer(read_only=True, allow_null=True)
readiness = serializers.JSONField(read_only=True)
current_run = PayrollRunSerializer(read_only=True, allow_null=True)
runs = PayrollRunSerializer(many=True, read_only=True)
summary = serializers.JSONField(read_only=True, allow_null=True)
pending_approvals = serializers.JSONField(read_only=True)
carryovers = serializers.JSONField(read_only=True)
class PayrollPeriodTableSerializer(serializers.Serializer):
period_id = serializers.IntegerField(read_only=True)
period_status = serializers.CharField(read_only=True)
currency = serializers.CharField(read_only=True)
calculation_rules = serializers.JSONField(read_only=True)
run = serializers.JSONField(read_only=True, allow_null=True)
component_columns = serializers.ListField(
child=serializers.JSONField(),
read_only=True,
)
cell_comments = serializers.ListField(
child=serializers.JSONField(),
read_only=True,
)
carryovers = serializers.ListField(
child=serializers.JSONField(),
read_only=True,
)
rows = serializers.ListField(child=serializers.JSONField(), read_only=True)
summary = serializers.JSONField(read_only=True)
class PayrollPaymentConfirmationSerializer(serializers.Serializer):
statement_id = serializers.IntegerField(read_only=True)
employee_id = serializers.IntegerField(read_only=True)
confirmed = serializers.BooleanField(read_only=True)
confirmed_at = serializers.DateTimeField(read_only=True, allow_null=True)
confirmed_by = PayrollUserRefSerializer(read_only=True, allow_null=True)
class PayrollCarryoverLedgerSerializer(serializers.Serializer):
period_id = serializers.IntegerField(read_only=True)
currency = serializers.CharField(read_only=True)
summary = serializers.JSONField(read_only=True)
entries = serializers.ListField(child=serializers.JSONField(), read_only=True)
class PayrollPeriodDraftApprovalSerializer(serializers.Serializer):
period_id = serializers.IntegerField(read_only=True)
summary = serializers.JSONField(read_only=True)
class PayrollCompleteWorkflowSerializer(serializers.Serializer):
period = PayrollPeriodSerializer(read_only=True)
run = PayrollRunSerializer(read_only=True)
completed_steps = serializers.ListField(
child=serializers.CharField(),
read_only=True,
)
class PayrollOperationResultSerializer(serializers.Serializer):
"""Именованный контракт для составных payroll-команд с JSON-сводкой."""
summary = serializers.JSONField(read_only=True, required=False)
code = serializers.CharField(read_only=True, required=False)
message = serializers.CharField(read_only=True, required=False)