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:
2026-06-19 21:08:16 -04:00
parent 08a5cd39d4
commit 91ed19c2b1
4 changed files with 136 additions and 2 deletions
@@ -350,6 +350,27 @@ public class LedgerService : ILedgerService
LinkId = cm.InvoiceId
});
// Gift-certificate redemptions reduce open AR (CREDIT) — ApplyGiftCertificate posts DR 2500 / CR AR.
var arGcRedemptions = await _context.GiftCertificateRedemptions
.Include(r => r.Invoice)
.Include(r => r.GiftCertificate)
.Where(r => !r.IsDeleted && r.RedeemedDate >= fromDate && r.RedeemedDate <= toDate
&& r.Invoice.Status != InvoiceStatus.Voided)
.ToListAsync();
foreach (var r in arGcRedemptions)
entries.Add(new LedgerEntryDto
{
Date = r.RedeemedDate,
Reference = r.GiftCertificate?.CertificateCode ?? $"GC-{r.GiftCertificateId}",
Source = "Gift Certificate",
Description = $"GC redeemed on {r.Invoice?.InvoiceNumber}",
Debit = 0,
Credit = r.AmountRedeemed,
LinkController = "Invoices",
LinkId = r.InvoiceId
});
// NOTE: cash refunds no longer touch AR. Under the "reverse the sale" model they debit
// Sales Returns + Sales Tax Payable and credit the bank (see section 5b above).
}
@@ -751,6 +772,11 @@ public class LedgerService : ILedgerService
.Where(a => a.AppliedDate < beforeDate && a.Invoice.Status != InvoiceStatus.Voided)
.SumAsync(a => (decimal?)a.AmountApplied) ?? 0;
// Gift-certificate redemptions credit AR (DR 2500 / CR AR), same as in GetAccountLedgerAsync.
credits += await _context.GiftCertificateRedemptions
.Where(r => !r.IsDeleted && r.RedeemedDate < beforeDate && r.Invoice.Status != InvoiceStatus.Voided)
.SumAsync(r => (decimal?)r.AmountRedeemed) ?? 0;
// NOTE: cash refunds no longer debit AR — they reverse the sale (Sales Returns + Sales Tax),
// handled in section 5b above.
}