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:
@@ -365,6 +365,13 @@ public class FinancialReportService : IFinancialReportService
|
||||
.Where(a => a.CompanyId == companyId && a.AppliedDate <= asOfEnd && a.Invoice.Status != InvoiceStatus.Voided)
|
||||
.SumAsync(a => (decimal?)a.AmountApplied) ?? 0;
|
||||
arCredits += cmAppliedBs;
|
||||
// Gift-certificate redemptions also credit AR (ApplyGiftCertificate posts DR 2500 / CR AR).
|
||||
// Mirror the posting here so AR is not overstated and the entry's two sides stay balanced.
|
||||
var gcRedeemedBs = await _context.GiftCertificateRedemptions
|
||||
.Where(r => r.CompanyId == companyId && !r.IsDeleted && r.RedeemedDate <= asOfEnd
|
||||
&& r.Invoice.Status != InvoiceStatus.Voided)
|
||||
.SumAsync(r => (decimal?)r.AmountRedeemed) ?? 0;
|
||||
arCredits += gcRedeemedBs;
|
||||
|
||||
// Customer Credits (2350): a credit memo books DR Sales Discounts / CR Customer Credits on issue,
|
||||
// then DR Customer Credits / CR AR on apply. Contra-revenue (retained earnings) = issued amount
|
||||
@@ -1132,6 +1139,13 @@ public class FinancialReportService : IFinancialReportService
|
||||
&& p.Invoice.Status != InvoiceStatus.WrittenOff)
|
||||
.SumAsync(p => (decimal?)p.Amount) ?? 0m;
|
||||
arTotalCredits += cmApplied; // credit memo applications reduce AR balance
|
||||
// Gift-certificate redemptions credit AR too (DR 2500 / CR AR). Without this the redemption's
|
||||
// 2500 debit is recomputed but its AR credit is not, leaving the trial balance out of balance.
|
||||
var gcRedeemedTb = await _context.GiftCertificateRedemptions
|
||||
.Where(r => r.CompanyId == companyId && !r.IsDeleted && r.RedeemedDate <= asOfEnd
|
||||
&& r.Invoice.Status != InvoiceStatus.Voided)
|
||||
.SumAsync(r => (decimal?)r.AmountRedeemed) ?? 0m;
|
||||
arTotalCredits += gcRedeemedTb;
|
||||
|
||||
// Cash refunds reverse the sale: revenue portion → DR Sales Returns (4960), tax portion →
|
||||
// DR Sales Tax Payable (relieves the liability), cash → CR bank (refundsByAcct below). They no
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user