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
@@ -619,6 +619,42 @@ public class LedgerServiceTests
Assert.Equal(-150m, ledger.ClosingBalance);
}
// ── Gift-certificate redemptions credit AR (DR 2500 / CR AR) — must appear in the AR ──
// ── recompute or the trial balance is left out of balance by the redeemed amount (O7). ──
[Fact]
public async Task GetAccountLedgerAsync_AR_GiftCertificateRedemption_CreditsAccountsReceivable()
{
await using var context = CreateContext();
SeedAccount(context, id: 1, AccountSubType.AccountsReceivable);
SeedCustomer(context, id: 1);
context.Invoices.Add(new Invoice
{
Id = 10, CompanyId = 1, InvoiceNumber = "INV-GC", CustomerId = 1,
Status = InvoiceStatus.Sent, InvoiceDate = InPeriod, Total = 200m
});
context.GiftCertificates.Add(new GiftCertificate
{
Id = 5, CompanyId = 1, CertificateCode = "GC-5", OriginalAmount = 50m, RedeemedAmount = 50m,
IssueDate = InPeriod, Status = GiftCertificateStatus.FullyRedeemed
});
context.GiftCertificateRedemptions.Add(new GiftCertificateRedemption
{
Id = 1, CompanyId = 1, GiftCertificateId = 5, InvoiceId = 10,
AmountRedeemed = 50m, RedeemedDate = InPeriod
});
await context.SaveChangesAsync();
var ledger = await CreateService(context).GetAccountLedgerAsync(1, PeriodStart, PeriodEnd);
var invoiceEntry = ledger!.Entries.Single(e => e.Source == "Invoice");
var gcEntry = ledger.Entries.Single(e => e.Source == "Gift Certificate");
Assert.Equal(200m, invoiceEntry.Debit);
Assert.Equal(50m, gcEntry.Credit);
Assert.Equal(0m, gcEntry.Debit);
Assert.Equal(150m, ledger.ClosingBalance); // debit-normal AR: 200 invoiced 50 redeemed
}
private static LedgerService CreateService(ApplicationDbContext context)
=> new LedgerService(context, Mock.Of<ILogger<LedgerService>>());