"""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