Fix historical FY sundry creditors FIFO ageing from accounting mirror
This commit is contained in:
@@ -272,11 +272,10 @@ def sundry_creditors_aging(
|
||||
At a reporting date, the unpaid closing balance is therefore represented by
|
||||
the *latest* credit-side additions, with any residual carried from opening.
|
||||
|
||||
We reconstruct that closing composition backwards from the authoritative
|
||||
ledger closing balance. This is algebraically the same FIFO result as a
|
||||
complete forward replay, but it is materially safer for an extracted mirror:
|
||||
it cannot mis-age a closing balance merely because the opening balance is a
|
||||
brought-forward aggregate rather than individual bills.
|
||||
We first reconstruct the selected FY closing balance from the mirror opening
|
||||
balance and selected-period movements, then reconstruct its unpaid composition
|
||||
backwards. This avoids using ledger_master.closing_balance for a historical FY
|
||||
because that master value can reflect a different/current reporting period.
|
||||
|
||||
Tally bill-allocation references are not yet persisted in the .act mirror, so
|
||||
this remains a transparent FIFO analysis rather than Agst Ref/New Ref matching.
|
||||
@@ -358,20 +357,36 @@ def sundry_creditors_aging(
|
||||
key = party_name.casefold()
|
||||
meta = ledger_meta.get(key) or {}
|
||||
|
||||
# Sundry Creditor liability balances in the canonical mirror are credit
|
||||
# balances (positive). Debit/zero closing balances are deliberately not
|
||||
# reported as closing creditors.
|
||||
closing_balance = max(0.0, _money(meta.get("closing_balance")))
|
||||
rows = by_ledger.get(key, [])
|
||||
|
||||
# IMPORTANT: ledger_master.closing_balance is a master-level snapshot and
|
||||
# is not reliable as the closing balance for an arbitrary historical FY.
|
||||
# Reconstruct the selected FY closing from the mirror's opening balance and
|
||||
# the selected-period creditor movements. This is the same balance control
|
||||
# used by the first mirror implementation and keeps historical FY analysis
|
||||
# tied to the voucher stream rather than the current ledger-master closing.
|
||||
opening_balance = _money(meta.get("opening_balance"))
|
||||
period_credit = round(sum(
|
||||
_money(row.get("amount")) for row in rows
|
||||
if str(row.get("dr_cr") or "").upper() == "CR"
|
||||
), 2)
|
||||
period_debit = round(sum(
|
||||
_money(row.get("amount")) for row in rows
|
||||
if str(row.get("dr_cr") or "").upper() == "DR"
|
||||
), 2)
|
||||
historical_closing = round(opening_balance + period_credit - period_debit, 2)
|
||||
|
||||
# Only credit closing balances are Sundry Creditors for this report.
|
||||
closing_balance = max(0.0, historical_closing)
|
||||
if closing_balance <= 0.009:
|
||||
continue
|
||||
|
||||
rows = by_ledger.get(key, [])
|
||||
credit_rows = [
|
||||
row for row in rows
|
||||
if str(row.get("dr_cr") or "").upper() == "CR" and _money(row.get("amount")) > 0.009
|
||||
]
|
||||
|
||||
# Reverse reconstruction of the closing balance under FIFO:
|
||||
# Reverse reconstruction of the selected FY closing balance under FIFO:
|
||||
# oldest balances are settled first, therefore the newest credits remain.
|
||||
amount_to_allocate = closing_balance
|
||||
lots_newest_first: list[dict[str, Any]] = []
|
||||
@@ -488,7 +503,7 @@ def sundry_creditors_aging(
|
||||
"age_bucket": bucket,
|
||||
"paid_subsequently": paid,
|
||||
"balance_after_follow_up": after,
|
||||
"allocation_basis": "FIFO reconstructed from Accounting Mirror closing balance",
|
||||
"allocation_basis": "FIFO from selected-FY Accounting Mirror movements",
|
||||
})
|
||||
|
||||
# Date on which the party's >180 FY-end component became fully paid.
|
||||
@@ -499,12 +514,12 @@ def sundry_creditors_aging(
|
||||
final_payment_date = max(dates)
|
||||
|
||||
reconstructed = round(within_180 + over_180, 2)
|
||||
# Maintain the mirror ledger closing balance as the authoritative control.
|
||||
# Maintain the selected-FY movement-derived closing balance as the control.
|
||||
variance = round(closing_balance - reconstructed, 2)
|
||||
if abs(variance) > 0.01:
|
||||
raise AccountingMirrorError(
|
||||
f"FIFO reconstruction does not reconcile for {party_name}: "
|
||||
f"mirror closing {closing_balance:.2f}, reconstructed {reconstructed:.2f}."
|
||||
f"selected-FY closing {closing_balance:.2f}, reconstructed {reconstructed:.2f}."
|
||||
)
|
||||
|
||||
parties.append({
|
||||
@@ -530,9 +545,9 @@ def sundry_creditors_aging(
|
||||
"parties": parties,
|
||||
"details": details,
|
||||
"allocation_basis": (
|
||||
"FIFO reconstructed from the Accounting Mirror closing balance; "
|
||||
"newest credit-side additions form the unpaid closing balance and "
|
||||
"debit-side settlements are deemed to clear the oldest balance first"
|
||||
"FIFO from selected-FY Accounting Mirror movements; the historical "
|
||||
"closing balance is reconstructed from opening balance plus FY credits "
|
||||
"less FY debits, and newest credit-side additions form the unpaid closing balance"
|
||||
),
|
||||
"current_mirror": current.get("mirror") or {},
|
||||
"follow_up_mirror": later.get("mirror") or {},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">Accounting · Audit Analysis</p>
|
||||
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Sundry Creditors – 180 Day Analysis</h1>
|
||||
<p class="mt-1 max-w-4xl text-sm text-slate-500">Analyses closing Sundry Creditors into ≤180 days and >180 days from the local Accounting Mirror. The closing balance is reconstructed on FIFO: debit-side settlements clear the oldest balances first, so the newest credit-side additions form the unpaid closing balance. Bill-reference allocations are not yet stored in the .act mirror.</p>
|
||||
<p class="mt-1 max-w-4xl text-sm text-slate-500">Analyses closing Sundry Creditors into ≤180 days and >180 days from the local Accounting Mirror. The selected-FY closing balance and ageing are reconstructed from the Accounting Mirror on FIFO: debit-side settlements clear the oldest balances first, so the newest credit-side additions form the unpaid closing balance. Bill-reference allocations are not yet stored in the .act mirror.</p>
|
||||
</div>
|
||||
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Back to Tally Accounting</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user