Cost sales first-in-first-out per branch instead of at an average #7

Merged
denis_ireri merged 1 commits from fix/fifo-cost-of-goods into main 2026-10-12 09:54:13 +00:00
Contributor

Cost of goods sold used each product's average buying price across the whole business. Ten jerrycans bought at KES 100 and ten at KES 140, with 15 sold, cost KES 1,800 instead of the KES 1,700 actually paid for those units. A branch that bought dearer was costed like one that bought cheaper, and stock moved between branches lost its cost.

What changes

src/lib/server/costing.ts replays the stock ledger in order, keeping cost layers per branch and product:

  • Purchases add stock at their unit cost.
  • Transfers carry the sending branch's cost layers to the receiving branch.
  • Opening stock and upward counts are valued at the product's purchase price at the time (or its first purchase price, if recorded before any purchase).
  • Sales take the oldest layers first; that is cost of goods sold.
  • Stock written off by counts or adjustments is reported separately as Stock losses and is now deducted in net profit (before, it did not appear anywhere in the report).
  • A sale recorded before its stock (stock gone negative, e.g. an offline sale before the purchase was entered) is costed by the next stock to arrive at that branch, even if that is after the report period. Until then it counts as "units with no purchase cost on record".
  • Sales recorded before the stock ledger existed are counted as uncosted.

The Reports page and CSV show the method ("FIFO per branch") and a new Stock losses card; the net profit card says it deducts stock losses.

Each report replays the business's whole stock history. That is fast at pilot volumes; a business with years of history would want stored cost layers (noted in docs/PILOT_READINESS.md).

Testing

  • npm test: 83 tests pass, 7 new in tests/costing.test.ts:
    • oldest stock costed first: 10x10 + 5x20 = 200 (the average gave 225);
    • each branch costed at what it paid, with branch filtering;
    • a transfer carries the sending branch's cost;
    • a short sale costed by the stock that arrives next;
    • written-off stock reported as a loss and deducted in net profit;
    • opening stock valued at the purchase price;
    • a period's sales costed against stock left over from earlier periods.
  • One expectation in tests/followups.test.ts changed from 33 (average) to 30 (oldest stock first).
  • Production build passes.
  • Browser: buying 10 at 100 and 10 at 140, selling 15 and counting one leaking jerrycan out showed cost of goods sold KES 1,700, stock losses KES 140 and net profit KES 1,160.

No migration or deploy step.

🤖 Generated with Claude Code

Cost of goods sold used each product's average buying price across the whole business. Ten jerrycans bought at KES 100 and ten at KES 140, with 15 sold, cost KES 1,800 instead of the KES 1,700 actually paid for those units. A branch that bought dearer was costed like one that bought cheaper, and stock moved between branches lost its cost. ## What changes `src/lib/server/costing.ts` replays the stock ledger in order, keeping cost layers per branch and product: - **Purchases** add stock at their unit cost. - **Transfers** carry the sending branch's cost layers to the receiving branch. - **Opening stock and upward counts** are valued at the product's purchase price at the time (or its first purchase price, if recorded before any purchase). - **Sales** take the oldest layers first; that is cost of goods sold. - **Stock written off** by counts or adjustments is reported separately as **Stock losses** and is now **deducted in net profit** (before, it did not appear anywhere in the report). - **A sale recorded before its stock** (stock gone negative, e.g. an offline sale before the purchase was entered) is costed by the next stock to arrive at that branch, even if that is after the report period. Until then it counts as "units with no purchase cost on record". - Sales recorded before the stock ledger existed are counted as uncosted. The Reports page and CSV show the method ("FIFO per branch") and a new Stock losses card; the net profit card says it deducts stock losses. Each report replays the business's whole stock history. That is fast at pilot volumes; a business with years of history would want stored cost layers (noted in `docs/PILOT_READINESS.md`). ## Testing - `npm test`: 83 tests pass, 7 new in `tests/costing.test.ts`: - oldest stock costed first: 10x10 + 5x20 = 200 (the average gave 225); - each branch costed at what it paid, with branch filtering; - a transfer carries the sending branch's cost; - a short sale costed by the stock that arrives next; - written-off stock reported as a loss and deducted in net profit; - opening stock valued at the purchase price; - a period's sales costed against stock left over from earlier periods. - One expectation in `tests/followups.test.ts` changed from 33 (average) to 30 (oldest stock first). - Production build passes. - Browser: buying 10 at 100 and 10 at 140, selling 15 and counting one leaking jerrycan out showed cost of goods sold KES 1,700, stock losses KES 140 and net profit KES 1,160. No migration or deploy step. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
denis_ireri added 1 commit 2026-10-12 09:52:48 +00:00
Cost of goods sold used each product's average buying price across the
whole business. Ten jerrycans bought at 100 and ten at 140, with 15 sold,
cost 1,800 instead of the 1,700 actually paid for those units; a branch
that bought dearer was costed like one that bought cheaper; and units
moved between branches lost their cost.

src/lib/server/costing.ts replays the stock ledger in order with cost
layers per branch and product:
- purchases add stock at their unit cost; transfers carry the sending
  branch's layers to the receiving branch; opening stock and upward counts
  are valued at the product's purchase price at the time;
- sales take the oldest layers first, and that is cost of goods sold;
- stock written off by counts or adjustments is a separate "stock losses"
  figure, now deducted in net profit;
- a sale recorded before its stock (stock gone negative) is costed by the
  next stock to arrive at that branch, even after the period ended; until
  then it is reported as units with no purchase cost on record.

The report shows the method and a Stock losses card. Sales recorded
before the stock ledger existed are counted as uncosted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
denis_ireri merged commit 418ad36b02 into main 2026-10-12 09:54:13 +00:00
denis_ireri deleted branch fix/fifo-cost-of-goods 2026-10-12 09:54:20 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Ezekiel_Nyangweso/ERP#7