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
|
At a reporting date, the unpaid closing balance is therefore represented by
|
||||||
the *latest* credit-side additions, with any residual carried from opening.
|
the *latest* credit-side additions, with any residual carried from opening.
|
||||||
|
|
||||||
We reconstruct that closing composition backwards from the authoritative
|
We first reconstruct the selected FY closing balance from the mirror opening
|
||||||
ledger closing balance. This is algebraically the same FIFO result as a
|
balance and selected-period movements, then reconstruct its unpaid composition
|
||||||
complete forward replay, but it is materially safer for an extracted mirror:
|
backwards. This avoids using ledger_master.closing_balance for a historical FY
|
||||||
it cannot mis-age a closing balance merely because the opening balance is a
|
because that master value can reflect a different/current reporting period.
|
||||||
brought-forward aggregate rather than individual bills.
|
|
||||||
|
|
||||||
Tally bill-allocation references are not yet persisted in the .act mirror, so
|
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.
|
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()
|
key = party_name.casefold()
|
||||||
meta = ledger_meta.get(key) or {}
|
meta = ledger_meta.get(key) or {}
|
||||||
|
|
||||||
# Sundry Creditor liability balances in the canonical mirror are credit
|
rows = by_ledger.get(key, [])
|
||||||
# balances (positive). Debit/zero closing balances are deliberately not
|
|
||||||
# reported as closing creditors.
|
# IMPORTANT: ledger_master.closing_balance is a master-level snapshot and
|
||||||
closing_balance = max(0.0, _money(meta.get("closing_balance")))
|
# 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:
|
if closing_balance <= 0.009:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rows = by_ledger.get(key, [])
|
|
||||||
credit_rows = [
|
credit_rows = [
|
||||||
row for row in rows
|
row for row in rows
|
||||||
if str(row.get("dr_cr") or "").upper() == "CR" and _money(row.get("amount")) > 0.009
|
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.
|
# oldest balances are settled first, therefore the newest credits remain.
|
||||||
amount_to_allocate = closing_balance
|
amount_to_allocate = closing_balance
|
||||||
lots_newest_first: list[dict[str, Any]] = []
|
lots_newest_first: list[dict[str, Any]] = []
|
||||||
@@ -488,7 +503,7 @@ def sundry_creditors_aging(
|
|||||||
"age_bucket": bucket,
|
"age_bucket": bucket,
|
||||||
"paid_subsequently": paid,
|
"paid_subsequently": paid,
|
||||||
"balance_after_follow_up": after,
|
"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.
|
# 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)
|
final_payment_date = max(dates)
|
||||||
|
|
||||||
reconstructed = round(within_180 + over_180, 2)
|
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)
|
variance = round(closing_balance - reconstructed, 2)
|
||||||
if abs(variance) > 0.01:
|
if abs(variance) > 0.01:
|
||||||
raise AccountingMirrorError(
|
raise AccountingMirrorError(
|
||||||
f"FIFO reconstruction does not reconcile for {party_name}: "
|
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({
|
parties.append({
|
||||||
@@ -530,9 +545,9 @@ def sundry_creditors_aging(
|
|||||||
"parties": parties,
|
"parties": parties,
|
||||||
"details": details,
|
"details": details,
|
||||||
"allocation_basis": (
|
"allocation_basis": (
|
||||||
"FIFO reconstructed from the Accounting Mirror closing balance; "
|
"FIFO from selected-FY Accounting Mirror movements; the historical "
|
||||||
"newest credit-side additions form the unpaid closing balance and "
|
"closing balance is reconstructed from opening balance plus FY credits "
|
||||||
"debit-side settlements are deemed to clear the oldest balance first"
|
"less FY debits, and newest credit-side additions form the unpaid closing balance"
|
||||||
),
|
),
|
||||||
"current_mirror": current.get("mirror") or {},
|
"current_mirror": current.get("mirror") or {},
|
||||||
"follow_up_mirror": later.get("mirror") or {},
|
"follow_up_mirror": later.get("mirror") or {},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">Accounting · Audit Analysis</p>
|
<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>
|
<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>
|
</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>
|
<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>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user