All checks were successful
Deploy production / deploy (push) Successful in 11m10s
78 lines
2.7 KiB
Python
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
|