Игорь Надеин 3da3100328
All checks were successful
Deploy production / deploy (push) Successful in 11m10s
Separate salary participation and preserve historical carryover rules
2026-10-15 19:37:51 +03:00

78 lines
2.7 KiB
Python

"""Payroll balances reconstructed from configured source input lines."""
from __future__ import annotations
from collections.abc import Iterable
from decimal import Decimal
from finance.enums import ApprovalStatus, InputSource
from finance.models import PayrollBalanceAdjustment, PayrollInputLine, PayrollPeriod
from finance.payroll.components import get_payroll_components
from payroll_core.balances import carryover_contribution
def opening_balances(
period: PayrollPeriod,
employee_ids: Iterable[int],
) -> dict[int, Decimal]:
"""Return the cumulative balance from approved configured earlier lines."""
requested_ids = set(employee_ids)
if not requested_ids:
return {}
lines = list(
PayrollInputLine.objects.filter(
employee_id__in=requested_ids,
period__currency=period.currency,
period__date_to__lt=period.date_from,
status=ApprovalStatus.APPROVED,
include_in_carryover=True,
)
.exclude(
source=InputSource.API,
source_ref__startswith="payroll-carryover:",
)
.values("period_id", "employee_id", "component_code", "amount")
)
source_periods = {
source.pk: source
for source in PayrollPeriod.objects.filter(
pk__in={line["period_id"] for line in lines}
)
}
components_by_period = {
period_id: {
component.code: component
for component in get_payroll_components(source_period)
if component.include_in_carryover
}
for period_id, source_period in source_periods.items()
}
balances = {}
for line in lines:
component = components_by_period.get(line["period_id"], {}).get(
line["component_code"]
)
if component is None:
continue
balances[line["employee_id"]] = balances.get(
line["employee_id"], Decimal("0.00")
) + carryover_contribution(
component.kind,
line["amount"],
version=source_periods[line["period_id"]].balance_rules_version,
)
# Effective-period corrections enter the opening balance exactly once per
# cumulative reconstruction. They never become payroll input lines.
for adjustment in PayrollBalanceAdjustment.objects.filter(
employee_id__in=requested_ids,
effective_period__currency=period.currency,
effective_period__date_from__lte=period.date_from,
voided_at__isnull=True,
):
balances[adjustment.employee_id] = (
balances.get(adjustment.employee_id, Decimal("0.00"))
+ adjustment.contribution
)
return balances