ZuloOne
English
Sign in
Menu

10 · Costing — stock cost

Updated

Solution(2) layer. Depends on Inventory and Purchasing — and only on them.

← General specification

Purpose#

Two questions that Inventory does not answer: what price to write off at and what amount is on hand. The Stock register stores quantity by cell; value lives here and is tracked by item, because moving stock between cells of one store does not change money.

The model has no documents of its own. Movements are written by receipt postings (Purchasing) and output postings (Production); cost is written off by sale posting.

The process-level description is costing.

What is created (object map)#

Kind Name Purpose
Model Costing module model, layer Solution(2)
Dictionary CostingSettings method settings and the standard cost price type (singleton)
Register ItemCostFifo FIFO layers by item: quantity and amount
Register InventoryValue stock: quantity and value
TotalDriver CostingIssue layer write-off on issue
TotalDriver CostingValuation stock valuation
Service StandardCostService standard cost on a date (a price of the type CostingSettings.StandardPriceType)
Service SurplusCostingService inventory surplus valuation
NumberSequence CostingSettingsSeq numbering
Job MoveStandardCosts moves rows of the retired standard cost dictionary into price history
CustomReport StandardCostVariance write-offs against the standard on the date
Menu menu.json module menu
— reused — Item, Store, Stock (Inventory), Money not created here

Total: 1 dictionary, 2 registers, 2 totals drivers, 2 services, 1 job, 1 report, and 6 integration tests. No documents.

Costing has no reports, and this is not an omission in the description. An earlier revision listed five: stock valuation, cost breakdown by BOM, gross margin, procurement vs consumption, and labor allocation. None of them is built, and the mechanism of user reports over movement tables by SQL is not used for them. A list of intentions in the object map reads like an inventory and has already been read that way once.

model.json#

Solution(2) layer. Dependencies: Inventory, Purchasing.

The edge direction matters. Costing does not depend on Sales; the opposite is true: Sales declares a dependency on Costing. Therefore gross margin is calculated in Sales by GrossMarginService, not here. The reverse edge would close a cycle. An earlier revision declared dependencies on Sales, Production, and HR; none of the three is present in model.json.

Production is also not a dependency, although its postings write to these registers: access goes through runtime service resolution, without a declared edge. Otherwise Production ↔ Costing would lock each other in a cycle.

Dictionaries#

CostingSettings#

One record per tenant: valuation method, rounding, and StandardPriceType — the price type whose prices are the standard (planned) unit cost.

The standard cost is not a dictionary of its own (since Costing 2.0.0): it is a dated price in Inventory price history, selected on the document date by its [from, to) window. Empty StandardPriceType means the oldest active Base type with direction Internal.

Registers#

Register Dimensions Analytics Resources
ItemCostFifo Item — Quantity, Amount
InventoryValue — Item Qty, Value

ItemCostFifo is the layer queue: a receipt puts a layer with quantity and amount; an issue removes it by the "first in, first out" rule. The breakdown is a dimension, not an analytic, because the totals driver calculates the write-off and the driver sees only register coordinates.

InventoryValue answers "what amount is on hand", and an analytic breakdown is enough: no driver reads it.

Example#

Item Widget, FIFO method. Two receipts from Purchasing put layers, then a realization from Sales issues 120 pcs; the CostingIssue driver removes them from the oldest layer first.

Event ItemCostFifo — layers (Qty @ Amount) ItemCostFifo — total (Quantity / Amount) InventoryValue (Qty / Value)
Before scenario — 0 / 0.00 0 / 0.00
Receipt No. 1: 100 pcs at 10.00 Layer1: 100 @ 1000.00 100 / 1000.00 100 / 1000.00
Receipt No. 2: 50 pcs at 12.00 Layer1: 100 @ 1000.00 · Layer2: 50 @ 600.00 150 / 1600.00 150 / 1600.00
Realization: issue 120 pcs (CostingIssue: all Layer1 first, then 20 from Layer2) Layer1: 0 @ 0.00 · Layer2: 30 @ 360.00 30 / 360.00 30 / 360.00

Cost of the 120 pcs written off = 1000.00 (all Layer1) + 240.00 (20 of 50 at the Layer2 price, 20 × 12.00) = 1240.00. This is the amount the realization assigns to revenue as COGS (GrossMarginService), not the average 1600.00 / 150 = 10.67: FIFO writes off layers at their entry price, not at a general average.

Surplus (SurplusCostingService). Inventory count finds another 5 pcs of Widget that were not in any receipt. The surplus is valued at the current average of the open layers, 360.00 / 30 = 12.00: ItemCostFifo gets Layer3: 5 @ 60.00, and InventoryValue becomes 35 / 420.00. An item with no layers at all is valued at its standard cost on the count date; with no standard either, the layer is created at zero. Without this service the surplus would be written off at zero cost and would understate the cost of the next sale by the same amount.

Totals drivers#

CostingIssue removes layers on issue. CostingValuation calculates stock value. The driver changes the implementation of the calculation, not the contract: the valuation method is switched by a setting, and consumers do not change.

Services#

  • StandardCostService — standard cost on a date, plan-vs-actual variance.
  • SurplusCostingService — valuation of surplus found by inventory count: there is no receipt layer, so cost comes from a separate rule.

Business logic and invariants#

Cost is per item, quantity is per cell. Stock knows where the item is; InventoryValue knows how much it is worth. Moving between cells of one store does not change value and should not generate a movement here.

Value is written off only through movements. Derived registers are not written directly when a document is reposted.

Receipt value is in the entity's currency. ReceiptCostTx and ReceiptFifoTx take each line amount × PurchaseOrder.ExchangeRate (the supplier currency's rate on the receipt date; 1 when the supplier bills in the entity's own currency), so cost lots, InventoryValue and the cost of sales they feed are never in a foreign currency.

Surplus is not free. Goods found by inventory count have no layer; without SurplusCostingService, they would be written off at zero cost and understate cost of sales.

Trap that cost time. A totals driver does not see movements written by bypassing the register movement manager in an event handler: it reads document transactions. Inventory once wrote off an amount that way, and the balance fell from 10 to 7 while no rows appeared in ItemCostFifo.

Totals (ItemCostFifo, InventoryValue) · Settings (CostingSettings) · Reports (StandardCostVariance).

  • Outgoing: Inventory — item and store; Purchasing — receipt as the source of layers.
  • Incoming: Sales declares a dependency and calculates margin; Production writes to the registers through runtime resolution, without an edge.
  • Ledger: Costing writes the cost legs of stock documents into GL itself — InventoryCostGLService turns the ItemCostFifo cost a document issued or received into the document's own GL movements (write-off of GoodsIssue, StockAdjustment, StockCount; surplus; Sales cost of sales and Production WIP call it too). See Who writes the general ledger.

Objects#

Generated from workspace metadata by node tools/gen-reference.mjs; edit the metadata, not this block.

Dictionaries#

CostingSettings — Costing settings

Field Caption Type Required
CostingMethod Costing method (AVG/FIFO) String(128) No
ID ID String(50) Yes
RoundCosts Round costs Boolean No
StandardPriceType Standard cost price type → PriceType No
ValuationMethod Costing method CostingMethodKindEdt No

StandardCost — Standard cost

Dated standard unit cost of an item. Gaps are allowed; overlapping windows of the same item are not.

Field Caption Type Required
Amount Amount Money Yes
EffectiveFrom Effective from DateTime Yes
EffectiveTo Effective to DateTime No
ID Code String(50) Yes
Item Item → Item Yes

Registers#

InventoryValue — Inventory value

Resources:

Resource Caption Type Required
Qty Qty Qty —
Value Value Money —

Analytics:

Analytic Caption Type Required
InventoryValue-Item InventoryValue-Item — Yes

ItemCostFifo — Item cost (FIFO)

Dimensions:

Dimension Caption Type Required
Item Item → Item —

Resources:

Resource Caption Type Required
Amount Amount Money —
Quantity Quantity Qty —
Was this page helpful?