Track store credit as a Customer Credits liability (GL)
Audit finding 9b: store-credit refunds and credit memos posted nothing to the GL on issue (only a CreditMemo + Customer.CreditBalance), so outstanding store credit was invisible on the balance sheet and the contra-revenue was recognized only on apply. Introduces a 2350 "Customer Credits" liability so the credit is on the books from issue to apply. Model (chosen): lifecycle-equivalent to before, plus the liability is tracked. - Issue (credit memos, goodwill, and store-credit refunds): DR Sales Discounts (4950) / CR Customer Credits (2350). - Apply: DR Customer Credits / CR AR (was DR Sales Discounts / CR AR). - Void unapplied remainder: DR Customer Credits / CR Sales Discounts. Posting updated in all 8 sites: CreditMemosController Create/Apply/Void and InvoicesController IssueCreditMemo/IssueRefund(store credit)/ApplyCredit/ VoidCreditMemo/CancelRefund. New 2350 account (seed + self-heal). Reporting moved in lockstep so the books still balance: the 4950 contra-revenue shifts from applied -> issued (active memos in full + applied portion of voided), the 2350 liability = unapplied balance on active memos, AR still credited by applications. Updated in FinancialReportService (balance sheet retained earnings, trial balance, P&L) and LedgerService (per-account + prior-balance 2350 section). Verified the balance-sheet identity for active and voided memos by hand; new ledger test covers the 2350 lifecycle. Build clean; 284 unit tests pass. Note: pre-existing quirks left untouched (out of 9b scope) — account 2300 is seeded as "Payroll Liabilities" but resolved as Customer Deposits in code, and LedgerService doesn't recompute 4950 so RecalculateBalances understates it; both predate this change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -495,6 +495,39 @@ public class LedgerService : ILedgerService
|
||||
});
|
||||
}
|
||||
|
||||
// ── 12b. Customer Credits liability (account 2350) ────────────────────
|
||||
// CR when a credit memo (incl. store-credit refund) is issued; DR when applied to an invoice.
|
||||
// Voided memos are excluded (their issue/void net to zero).
|
||||
if (account.AccountNumber == "2350")
|
||||
{
|
||||
var memosIssued = await _context.CreditMemos
|
||||
.Where(m => m.Status != CreditMemoStatus.Voided
|
||||
&& m.IssueDate >= fromDate && m.IssueDate <= toDate)
|
||||
.ToListAsync();
|
||||
foreach (var m in memosIssued)
|
||||
entries.Add(new LedgerEntryDto
|
||||
{
|
||||
Date = m.IssueDate, Reference = m.MemoNumber,
|
||||
Source = "Credit Memo", Description = "Store credit issued",
|
||||
Debit = 0, Credit = m.Amount,
|
||||
LinkController = "CreditMemos", LinkId = m.Id
|
||||
});
|
||||
|
||||
var memosApplied = await _context.CreditMemoApplications
|
||||
.Include(a => a.CreditMemo).Include(a => a.Invoice)
|
||||
.Where(a => a.CreditMemo.Status != CreditMemoStatus.Voided
|
||||
&& a.AppliedDate >= fromDate && a.AppliedDate <= toDate)
|
||||
.ToListAsync();
|
||||
foreach (var a in memosApplied)
|
||||
entries.Add(new LedgerEntryDto
|
||||
{
|
||||
Date = a.AppliedDate, Reference = a.CreditMemo?.MemoNumber ?? $"CM-{a.CreditMemoId}",
|
||||
Source = "Credit Applied", Description = $"Applied to {a.Invoice?.InvoiceNumber}",
|
||||
Debit = a.AmountApplied, Credit = 0,
|
||||
LinkController = "Invoices", LinkId = a.InvoiceId
|
||||
});
|
||||
}
|
||||
|
||||
// ── 10. Journal Entry lines touching this account ──────────────────
|
||||
var jeLines = await _context.JournalEntryLines
|
||||
.Include(l => l.JournalEntry)
|
||||
@@ -716,6 +749,17 @@ public class LedgerService : ILedgerService
|
||||
.SumAsync(d => (decimal?)d.Amount) ?? 0;
|
||||
}
|
||||
|
||||
// 12b. Customer Credits liability (account 2350)
|
||||
if (account.AccountNumber == "2350")
|
||||
{
|
||||
credits += await _context.CreditMemos
|
||||
.Where(m => m.Status != CreditMemoStatus.Voided && m.IssueDate < beforeDate)
|
||||
.SumAsync(m => (decimal?)m.Amount) ?? 0;
|
||||
debits += await _context.CreditMemoApplications
|
||||
.Where(a => a.CreditMemo.Status != CreditMemoStatus.Voided && a.AppliedDate < beforeDate)
|
||||
.SumAsync(a => (decimal?)a.AmountApplied) ?? 0;
|
||||
}
|
||||
|
||||
// 10. Posted journal entry lines touching this account (prior to period)
|
||||
debits += await _context.JournalEntryLines
|
||||
.Where(l => l.AccountId == accountId
|
||||
|
||||
Reference in New Issue
Block a user