Corp/backend/finance/payroll/balances.py

64 lines
2.0 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 PayrollInputLine, PayrollPeriod
from finance.payroll.components import get_payroll_components
CARRYOVER_DEBIT_KINDS = frozenset({"adjustment_debit", "deduction", "payment"})
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
direction = 1 if component.kind in CARRYOVER_DEBIT_KINDS else -1
balances[line["employee_id"]] = balances.get(
line["employee_id"], Decimal("0.00")
) + (line["amount"] * direction)
return balances