Cost sales first-in-first-out per branch instead of at an average
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>
This commit is contained in:
@@ -100,12 +100,12 @@ describe("F-03 report period validation", () => {
|
||||
});
|
||||
|
||||
describe("F-07 cost of goods sold", () => {
|
||||
it("costs only the units sold, at the average buying price, and reports purchases separately", async () => {
|
||||
it("costs only the units sold, first-in-first-out, and reports purchases separately", async () => {
|
||||
const t = await makeTenant();
|
||||
const buy = (quantity: number, unitCost: number) =>
|
||||
purchases.POST(req("POST", "/x", { as: t.managerA, body: { supplierName: "Kiwanja", productId: t.water20, quantity, unitCost } }));
|
||||
await buy(20, 10);
|
||||
await buy(10, 13); // average (200 + 130) / 30 = 11
|
||||
await buy(10, 13);
|
||||
await cash.POST(
|
||||
req("POST", "/x", {
|
||||
as: t.cashierA,
|
||||
@@ -116,9 +116,9 @@ describe("F-07 cost of goods sold", () => {
|
||||
expect(body).toMatchObject({
|
||||
revenue: 660, // 3 x 200 + 1 x 60
|
||||
unitsSold: 4,
|
||||
costOfGoodsSold: 33, // 3 x 11; the 5L was never purchased
|
||||
costOfGoodsSold: 30, // 3 x 10 from the oldest purchase; the 5L was never purchased
|
||||
unitsWithoutCost: 1,
|
||||
grossProfit: 627,
|
||||
grossProfit: 630,
|
||||
stockPurchases: 330,
|
||||
cashSurplus: 330,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user