Recompute inventory-consumption COGS and fix written-off AR (audit O6, O8)
O6: inventory consumed on jobs posts DR COGS / CR Inventory, but neither recompute
engine reflected it — so reports understated COGS / overstated inventory and a
"Recalculate Balances" wiped the effect. The COGS posting fires only for JobUsage
and Waste transaction types, which are created only at the two COGS-posting sites,
so the consumption is exactly identifiable from InventoryTransaction:
- both posting sites now record consumption at the effective (weighted-average)
unit cost so TotalCost equals the COGS posted (the recompute reads TotalCost)
- LedgerService: new section (dated rows + prior balance) crediting Inventory /
debiting COGS from JobUsage/Waste rows on items with both accounts mapped
- FinancialReportService: Trial Balance + accrual P&L include consumption COGS
This reads existing transactions, so historical data is covered with no backfill.
The Balance Sheet inventory line is intentionally left alone — it does not track
inventory purchases either (periodic), so relieving it for consumption alone would
unbalance it; tracked as O9 (inventory capitalization policy).
O8: the write-off already creates a balanced posted JournalEntry (both engines read
it via their JE-line sections). The real defect was 4 "Status != WrittenOff" filters
in FinancialReportService that excluded pre-write-off payments from AR credits and
bank debits — leaving the paid portion dangling as open AR and understating the bank.
Removed those filters; AR now nets to zero for written-off invoices and the trial
balance balances. No backfill needed.
Adds a LedgerService regression test for inventory consumption. Build clean; 293
unit tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -655,6 +655,43 @@ public class LedgerServiceTests
|
||||
Assert.Equal(150m, ledger.ClosingBalance); // debit-normal AR: 200 invoiced − 50 redeemed
|
||||
}
|
||||
|
||||
// ── Inventory consumption posts DR COGS / CR Inventory; both sides must be in the ──
|
||||
// ── recompute so a recalc reproduces them and the trial balance stays balanced (O6). ──
|
||||
|
||||
[Fact]
|
||||
public async Task GetAccountLedgerAsync_InventoryConsumption_DebitsCogsAndCreditsInventory()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
// COGS account (id 1) and Inventory asset account (id 2)
|
||||
context.Accounts.Add(new Account { Id = 1, CompanyId = 1, AccountNumber = "5100", Name = "Powder & Materials", AccountType = AccountType.CostOfGoods, AccountSubType = AccountSubType.CostOfGoodsSold, IsActive = true });
|
||||
context.Accounts.Add(new Account { Id = 2, CompanyId = 1, AccountNumber = "1200", Name = "Inventory - Powder", AccountType = AccountType.Asset, AccountSubType = AccountSubType.Inventory, IsActive = true });
|
||||
context.InventoryItems.Add(new InventoryItem
|
||||
{
|
||||
Id = 7, CompanyId = 1, Name = "White Powder", SKU = "WP-1",
|
||||
CogsAccountId = 1, InventoryAccountId = 2
|
||||
});
|
||||
context.InventoryTransactions.Add(new InventoryTransaction
|
||||
{
|
||||
Id = 1, CompanyId = 1, InventoryItemId = 7,
|
||||
TransactionType = InventoryTransactionType.JobUsage,
|
||||
Quantity = -4m, UnitCost = 10m, TotalCost = 40m,
|
||||
TransactionDate = InPeriod, Reference = "Job 100"
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var svc = CreateService(context);
|
||||
|
||||
var cogs = await svc.GetAccountLedgerAsync(1, PeriodStart, PeriodEnd);
|
||||
var cogsEntry = Assert.Single(cogs!.Entries, e => e.Source == "Inventory Usage");
|
||||
Assert.Equal(40m, cogsEntry.Debit);
|
||||
Assert.Equal(40m, cogs.ClosingBalance); // debit-normal COGS
|
||||
|
||||
var inv = await svc.GetAccountLedgerAsync(2, PeriodStart, PeriodEnd);
|
||||
var invEntry = Assert.Single(inv!.Entries, e => e.Source == "Inventory Usage");
|
||||
Assert.Equal(40m, invEntry.Credit);
|
||||
Assert.Equal(-40m, inv.ClosingBalance); // debit-normal asset relieved by a credit
|
||||
}
|
||||
|
||||
private static LedgerService CreateService(ApplicationDbContext context)
|
||||
=> new LedgerService(context, Mock.Of<ILogger<LedgerService>>());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user