Credit AR for gift-certificate redemptions in balance recompute (audit O7)
ApplyGiftCertificate posts DR 2500 Gift Certificate Liability / CR AR, but the AR recompute only subtracted payments and credit-memo applications — so the redemption's 2500 debit was recomputed while its AR credit was not, leaving the Trial Balance out of balance by the total gift-certificate amount redeemed and overstating AR on the Balance Sheet. Subtract GC redemptions from AR in both recompute engines: - FinancialReportService: Balance Sheet (gcRedeemedBs) and Trial Balance (gcRedeemedTb) - LedgerService: AR section (dated rows) and ComputePriorBalanceAsync (prior balance) AR Aging was already correct (uses BalanceDue, which includes GiftCertificateRedeemed). Adds a LedgerService regression test. Build clean; 292 unit tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,64 @@ explicit `CompanyId` predicate, matching the standing rule in CLAUDE.md. ~19 loo
|
||||
`companyId` source per controller: `_tenantContext.GetCurrentCompanyId()` where available, else the
|
||||
in-scope entity's `CompanyId`, else `_userManager` current user. Build clean; 291 unit tests pass.
|
||||
|
||||
---
|
||||
|
||||
## Whole-system re-audit (2026-06-19) — NEW findings, OPEN
|
||||
|
||||
Root cause shared by all three: the GL is kept two ways — **direct postings** via
|
||||
`AccountBalanceService.Debit/CreditAsync` (authoritative `Account.CurrentBalance`) and **recomputes**
|
||||
via `LedgerService` (drives `RecalculateAllAsync`) and `FinancialReportService` (drives the reports).
|
||||
Every posting type must be mirrored in *both* recompute engines or balances diverge. Several postings
|
||||
are **not** mirrored, so: (a) "Recalculate Balances" corrupts those accounts, (b) the Balance
|
||||
Reconciliation report shows false drift, and in one case (O7) the **Trial Balance does not balance**.
|
||||
O2 (Sales Discounts 4950) was the first instance found and fixed; these are the rest.
|
||||
|
||||
### O6 — Inventory consumption COGS not in either recompute — **MEDIUM**
|
||||
- `JobsController:3019` and `InventoryController:1855` post **DR COGS / CR Inventory** when an inventory
|
||||
item with both `CogsAccountId` and `InventoryAccountId` is consumed on a job. Neither `LedgerService`
|
||||
nor `FinancialReportService` reads this (no JobUsage/InventoryTransaction COGS section).
|
||||
- **Impact:** COGS understated (profit overstated) and Inventory asset overstated on P&L/Balance Sheet
|
||||
relative to the posted `CurrentBalance`; a recalc wipes the effect; reconciliation flags drift. TB still
|
||||
*balances* (both sides omitted). Only active for items with both account mappings set.
|
||||
- **Fix direction:** add a COGS/Inventory section to both recompute engines driven by `InventoryTransaction`
|
||||
consumption rows (needs the posted cost captured on the transaction, e.g. `TotalCost`), or — better —
|
||||
post these via real `JournalEntry` lines so the JE recompute path already covers them.
|
||||
|
||||
### O7 — Gift-certificate redemptions credit AR but AR recompute omitted it — **RESOLVED**
|
||||
- `InvoicesController.ApplyGiftCertificate:3137` posts **DR 2500 GC Liability / CR AR** (no Payment row,
|
||||
no `AmountPaid` change — only `invoice.GiftCertificateRedeemed`). The **2500 debit IS** recomputed
|
||||
(GC redemptions), but the **AR credit is NOT**: AR recompute = `sum(Total) − Payments − CreditMemoApplications`
|
||||
in both `FinancialReportService` (BS line 358 / TB line 1129) and `LedgerService` (AR section).
|
||||
- **Impact:** because only one side of the entry is recomputed, the **Trial Balance is out of balance by the
|
||||
total GC redeemed**; Balance Sheet AR is overstated; recalc corrupts AR. (AR **Aging** is correct — it uses
|
||||
`BalanceDue`, which includes `GiftCertificateRedeemed`.) Active for any company that redeems GCs on invoices.
|
||||
- **Fix applied:** GC redemptions now subtracted from AR in both recompute engines — `FinancialReportService`
|
||||
Balance Sheet (`gcRedeemedBs`) and Trial Balance (`gcRedeemedTb`), and `LedgerService` AR section + prior
|
||||
balance. Mirrors the `cmApplied` treatment. Regression test
|
||||
`GetAccountLedgerAsync_AR_GiftCertificateRedemption_CreditsAccountsReceivable`. Build clean; 292 tests pass.
|
||||
|
||||
### O8 — Written-off invoices misstated in AR recompute — **MEDIUM**
|
||||
- `InvoicesController.WriteOff:1689` posts **DR Bad Debt / CR AR** for the balance due and marks the invoice
|
||||
`WrittenOff`. In the recompute, written-off invoices' `Total` is still counted as an AR **debit**
|
||||
(`arDebits` only excludes Draft/Voided), while `FinancialReportService` **excludes** their payments from AR
|
||||
credits (Status != WrittenOff filter) and neither engine models the write-off CR — so a written-off invoice
|
||||
shows its full `Total` as open AR on recompute. `LedgerService` and `FinancialReportService` also disagree
|
||||
(Ledger does not apply the WrittenOff payment filter).
|
||||
- **Impact:** AR overstated by written-off balances on recompute/reports; recalc corrupts AR; the two engines
|
||||
disagree. Active when invoices are written off.
|
||||
- **Fix direction:** treat `WrittenOff` consistently — exclude written-off invoices' `Total` from `arDebits`
|
||||
(or model the write-off CR) and align the two engines.
|
||||
|
||||
**Architectural note:** these keep recurring because reports re-derive balances from source documents in
|
||||
parallel with the live postings. The durable fix is to make **every** financial event post `JournalEntry`
|
||||
lines and drive all reports/recompute from those lines alone (single source of truth), retiring the
|
||||
per-document re-derivation. Worth considering before the ledger grows further.
|
||||
|
||||
## Status
|
||||
All audit findings **O1–O4 are resolved** on `dev`. Original audit numbering #1–3/#5/#6/#8 remains
|
||||
unrecoverable (see top). The accounting batch has **not** been merged to `master`/production yet.
|
||||
Findings **O1–O5, O7, + the read-path sweep are resolved** on `dev`. **O6 and O8 remain OPEN** — both need the
|
||||
JournalEntry approach (post a balanced JE at consumption/write-off time) plus a one-time historical backfill,
|
||||
because neither event leaves a reliably recompute-able marker (O6: no flag distinguishing COGS-posting
|
||||
reductions, and posted cost uses AverageCost while the transaction stores UnitCost; O8: the write-off's
|
||||
bad-debt account and amount aren't stored as a ledger record, so mirroring only the AR side would re-break the
|
||||
trial balance).
|
||||
Original audit numbering #1–3/#5/#6/#8 remains unrecoverable (see top). Nothing merged to `master` yet.
|
||||
|
||||
Reference in New Issue
Block a user