Fix Customer Deposits account mislabel and Sales Discounts recalc (audit O1, O2)
O1: account 2300 has always been used by the deposit GL code as the Customer
Deposits liability (resolved by number), but it was seeded/named "Payroll
Liabilities" for tenants the AccountingDepositsGL migration's NOT EXISTS guard
skipped — so the liability was mislabeled on the balance sheet. Rename 2300 to
"Customer Deposits" (IsSystem) and move payroll to a new 2400 account:
- both seed paths (SeedDataService.Accounts, SeedData)
- EnsureSystemAccountsAsync self-heal (renames only where still default-named,
preserving user renames; ensures 2400 exists)
- migration RenameDepositsAccountAddPayroll for existing tenants
Account number 2300 is unchanged, so the deposit posting code needs no changes.
O2: LedgerService never recomputed 4950 Sales Discounts, so "Recalculate
Balances" wiped it to JE-only and the Balance Reconciliation report showed false
drift. Add a 4950 section to GetAccountLedgerAsync and ComputePriorBalanceAsync
that reproduces the actual postings (invoice discounts DR + credit-memo issuance
DR, less the unapplied remainder of voided memos CR), matching AccountBalanceService.
Adds a LedgerService regression test for 4950. Documents both fixes plus the
remaining open findings (O3, O4) in docs/ACCOUNTING_AUDIT.md so the audit is no
longer lost. Build clean; 291 unit tests pass; migration applied.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -60,8 +60,12 @@ public partial class SeedDataService
|
||||
new Account { AccountNumber = "2000", Name = "Accounts Payable", AccountType = AccountType.Liability, AccountSubType = AccountSubType.AccountsPayable, IsSystem = true, IsActive = true, Description = "Amounts owed to suppliers and vendors", CompanyId = company.Id, CreatedAt = now },
|
||||
new Account { AccountNumber = "2100", Name = "Credit Card Payable", AccountType = AccountType.Liability, AccountSubType = AccountSubType.CreditCard, IsSystem = false, IsActive = true, Description = "Business credit card balance", CompanyId = company.Id, CreatedAt = now },
|
||||
new Account { AccountNumber = "2200", Name = "Sales Tax Payable", AccountType = AccountType.Liability, AccountSubType = AccountSubType.OtherCurrentLiability, IsSystem = false, IsActive = true, Description = "Sales tax collected and owed to government", CompanyId = company.Id, CreatedAt = now },
|
||||
new Account { AccountNumber = "2300", Name = "Payroll Liabilities", AccountType = AccountType.Liability, AccountSubType = AccountSubType.OtherCurrentLiability, IsSystem = false, IsActive = true, Description = "Payroll taxes and withholdings owed", CompanyId = company.Id, CreatedAt = now },
|
||||
// 2300 is the Customer Deposits liability — credited when a deposit is taken, debited when it is
|
||||
// applied to an invoice (see DepositsController / InvoicesController, which resolve it by number).
|
||||
// IsSystem because the GL posting code depends on it existing. Payroll lives at 2400 below.
|
||||
new Account { AccountNumber = "2300", Name = "Customer Deposits", AccountType = AccountType.Liability, AccountSubType = AccountSubType.OtherCurrentLiability, IsSystem = true, IsActive = true, Description = "Deposits received from customers before an invoice is created; cleared when applied to an invoice", CompanyId = company.Id, CreatedAt = now },
|
||||
new Account { AccountNumber = "2350", Name = "Customer Credits", AccountType = AccountType.Liability, AccountSubType = AccountSubType.OtherCurrentLiability, IsSystem = true, IsActive = true, Description = "Store credit owed to customers (credit memos not yet applied)", CompanyId = company.Id, CreatedAt = now },
|
||||
new Account { AccountNumber = "2400", Name = "Payroll Liabilities", AccountType = AccountType.Liability, AccountSubType = AccountSubType.OtherCurrentLiability, IsSystem = false, IsActive = true, Description = "Payroll taxes and withholdings owed", CompanyId = company.Id, CreatedAt = now },
|
||||
new Account { AccountNumber = "2900", Name = "Business Loan", AccountType = AccountType.Liability, AccountSubType = AccountSubType.LongTermLiability, IsSystem = false, IsActive = true, Description = "Long-term equipment or business loan", CompanyId = company.Id, CreatedAt = now },
|
||||
|
||||
// ── EQUITY ────────────────────────────────────────────────────────
|
||||
@@ -191,6 +195,46 @@ public partial class SeedDataService
|
||||
added++;
|
||||
}
|
||||
|
||||
// 2300 used to be seeded as "Payroll Liabilities" but the deposit GL posting code has always
|
||||
// resolved 2300 by number and used it as the Customer Deposits liability — so the account was
|
||||
// mislabeled on the balance sheet. Rename it to "Customer Deposits" and mark it system. Only
|
||||
// touch accounts still carrying the old default name so a user's own rename is preserved.
|
||||
var legacyDepositsAcct = await _context.Set<Account>()
|
||||
.IgnoreQueryFilters()
|
||||
.FirstOrDefaultAsync(a => a.CompanyId == company.Id && a.AccountNumber == "2300"
|
||||
&& !a.IsDeleted && a.Name == "Payroll Liabilities");
|
||||
if (legacyDepositsAcct != null)
|
||||
{
|
||||
legacyDepositsAcct.Name = "Customer Deposits";
|
||||
legacyDepositsAcct.Description = "Deposits received from customers before an invoice is created; cleared when applied to an invoice";
|
||||
legacyDepositsAcct.IsSystem = true;
|
||||
legacyDepositsAcct.UpdatedAt = now;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// 2400 Payroll Liabilities — the payroll account displaced from 2300 (now Customer Deposits).
|
||||
var has2400 = await _context.Set<Account>()
|
||||
.IgnoreQueryFilters()
|
||||
.AnyAsync(a => a.CompanyId == company.Id && a.AccountNumber == "2400" && !a.IsDeleted);
|
||||
|
||||
if (!has2400)
|
||||
{
|
||||
_context.Set<Account>().Add(new Account
|
||||
{
|
||||
AccountNumber = "2400",
|
||||
Name = "Payroll Liabilities",
|
||||
AccountType = AccountType.Liability,
|
||||
AccountSubType = AccountSubType.OtherCurrentLiability,
|
||||
IsSystem = false,
|
||||
IsActive = true,
|
||||
Description = "Payroll taxes and withholdings owed",
|
||||
CompanyId = company.Id,
|
||||
CreatedAt = now
|
||||
});
|
||||
await _context.SaveChangesAsync();
|
||||
added++;
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user