FIFO layers & how COGS is realized
Every unit you own carries a cost, and SKU.io tracks that cost in FIFO layers — dated, costed batches of stock. When you receive goods, a layer opens. When you ship, sell, or transfer them, layers are drawn down in a deterministic order and their cost becomes your cost of goods sold (COGS). This page explains the model: what a layer is, how positive events create them, how negative events consume them soonest-expiry-first, how COGS is realized on fulfillment, where cost comes from when no document dictates it, and why some costs are locked while others stay editable.
This is the conceptual companion to the task guides: browse & manage FIFO layers, revalue COGS, and rebuild COGS. For exact field names, statuses, and enums, see the FIFO layer fields, statuses & enums reference.
What a FIFO layer is
A FIFO layer is one batch of stock at a single point in time: a quantity of a product, in one warehouse, received on one date, at one landed cost. First-in, first-out means the oldest layer is normally consumed first, so cost flows out in the order it flowed in.
Each layer records a small, fixed set of numbers:
| Field | Meaning |
|---|---|
| Original quantity | How many units the layer opened with. |
| Fulfilled quantity | How many have been drawn down so far. |
| Available quantity | Units still open — original − fulfilled. Stored generated: the database computes it; SKU.io never writes it directly. |
| Total cost | The landed cost of the whole layer — the valuation basis. |
| Average cost | Per-unit cost — total cost / original quantity. Also stored generated. |
| Cost breakdown | The landed-cost category map (product, freight, duty, and more) that adds up to total cost. |
| Cost version | A counter bumped each time the layer is revalued, so re-costing is traceable. |
| Condition | The stock condition the layer holds (for example new versus damaged). |
| Consignment flag | Whether the layer holds supplier-owned stock — see Consignment stock. |
Available quantity and average cost are stored generated columns — SQL derives them from the base columns and they read as NULL until the row is reloaded. That's why SKU.io refreshes a layer immediately after saving it. You never edit them by hand; you change the inputs (original quantity, fulfilled quantity, total cost) and the derived values follow.
A layer's per-unit average cost multiplied by its available quantity gives the value of stock still sitting in that layer. Sum that across every open, owned layer for a product and you have its inventory valuation.
Layers are lot-aware. A layer can also carry a batch number, expiry date, and manufacture date. If a lot-tracked product is received without a batch number, SKU.io generates a recognizable fallback (an AUTO- prefix with the date and a short suffix) so the layer still supports FEFO ordering, genealogy, and recall.
How layers are created — positive inventory events
A layer is never created out of nothing. Every layer is linked to the positive inventory event that produced it, through a polymorphic pointer (a link type plus a link id). That link is what lets SKU.io trace a batch back to its origin document and decide how its cost can later be corrected.
These are the positive events that open a layer:
| Positive event | What it represents |
|---|---|
| Purchase-order receipt | Receiving stock against a purchase order (an inbound shipment receipt line). |
| Warehouse transfer receipt | Receiving the inbound side of a warehouse transfer. |
| Inventory adjustment | A positive stock adjustment — found stock, an opening balance, a correction up. |
| Stock take | A stock take item that reconciles a count upward. |
| Manufacturing / assembly output | Finished goods produced by a manufacturing order or an assembly. |
| Return receipt | A customer return received back into stock. |
| Inventory movement | A positive movement without its own document (for example a Simple Mode disassembly). |
The event class matters beyond bookkeeping: it decides which cost sources are valid for that layer. A purchase-order receipt layer, for instance, may only carry the purchase-order line cost or a manual override; a transfer receipt layer may only carry the transfer's weighted-average cost or a manual override; adjustments and stock takes accept any source because they have no document cost of their own. SKU.io enforces this whenever a layer's cost is set or revalued, and rejects an invalid combination rather than silently mis-costing the batch.
A FIFO layer can't be saved against a supplier warehouse. Channel-managed Amazon inventory (FBA, AWD, WFS) is costed in its own Amazon layer ecosystem, not the regular FIFO table — see Cost sourcing and, on the sales side, Amazon FBA, MCF & external fulfillment.
How layers are consumed — FEFO, then FIFO
A negative inventory event — a sale shipment, a transfer shipped out, a decrease adjustment, a disassembly — draws stock down from open layers. The order in which layers are picked is the single most important behavior on this page, and it depends on whether the product is expiry-tracked:
- FEFO products (lot tracking set to first-expiry-first-out) consume the soonest-to-expire layer first. Undated layers sort last, then dated layers by expiry ascending, with the usual FIFO tie-break behind that.
- Everything else consumes in pure FIFO order: oldest layer date first, then by layer id as a deterministic tie-break.
One service owns this decision for every consumption path — sales fulfillment, transfers, manufacturing, disassembly, and reversals — so the same physical layer is always chosen no matter which flow triggers the draw-down.
FEFO only selects which physical batch ships when several are open. The books still consume cost in layer order. For a non-expiry product there is no behavioral difference at all — it's plain FIFO.
How COGS is realized on fulfillment
Realizing COGS is what makes consumption more than a quantity change. When a sales-order line is fulfilled, SKU.io walks the ordered layers and, for each one it draws from:
- Locks the layer and re-checks its remaining capacity, so two concurrent fulfillments can't oversell the same batch.
- Creates a negative inventory movement stamped with that layer's id. The cost carried on that negative movement is the realized COGS — it's the layer's own cost flowing out, not a re-estimate.
- Bumps the layer's fulfilled quantity under the row lock, shrinking its available quantity.
- Writes a cost breakdown onto the sales-order line, prorated from the layer's landed-cost category map, so the sale's margin reflects freight and duty, not just the product price.
A single fulfillment can span several layers (when the first runs out mid-quantity) and several bins, producing one movement per layer-and-bin pair — each stamped with the layer it came from.
Before the walk begins, SKU.io locks every candidate layer up front in ascending id order, then walks them in consumption (FEFO/FIFO) order. Forcing all concurrent fulfillments to acquire locks in the same global order is what prevents deadlocks when many orders drain the same pool at once. This is invisible in the UI — it only guarantees correctness under load.
To see the movements a fulfillment produced, open the movement ledger; each negative sale movement links back to its source layer.
Cost sourcing hierarchy
Deterministic events carry their own cost — a purchase-order receipt uses its landed cost, a transfer receipt uses the shipment's weighted-average cost, an assembly uses the sum of its component costs. But indeterminate events have no document cost of their own: a positive adjustment or a stock-take increase just says "these units exist," not what they cost. For those, SKU.io resolves a cost from a best-available priority hierarchy, stopping at the first source that yields a usable value:
| Order | Source | When it applies |
|---|---|---|
| 1 | Oldest active Amazon FIFO layer | The warehouse is Amazon-managed (FBA/AWD), so stock lives in the Amazon layer ecosystem. |
| 2 | Oldest active (open) FIFO layer | A regular open layer exists for the product in that warehouse — the standard case. |
| 3 | Newest closed FIFO layer | No layer is open, but a fully consumed one carries a recent cost. |
| 4 | Product default cost | The product's own Default Cost (unit cost). A 0 default is treated as unset and skipped. |
| 5 | None | Nothing above yielded a value — cost resolves to 0 and the source is recorded as None. |
Recording None rather than guessing is deliberate: a 0-cost, None-source layer is visible and fixable, whereas a fabricated cost would silently distort margin. You correct these later — see revalue COGS and rebuild COGS.
The hierarchy resolves within a single warehouse. SKU.io deliberately doesn't borrow a cost from a different warehouse or from a product-wide average, because that would blend valuations that should stay separate. A few historic resolvers (weighted-average cache, any-layer, supplier pricing) are retired for exactly that reason, though old layers may still carry those source labels.
Locked versus editable cost
Not every cost should move when you rebuild valuations. SKU.io classifies each layer's cost source as either locked (document-backed, treated as actual) or editable (estimated, safe to auto-recalculate):
| Classification | Sources | Behavior |
|---|---|---|
| Locked / actual | Manual entry, purchase-order line, assembly line, transfer weighted-average, sales-order weighted-average, initial FNSKU cost, AWD | Backed by a document or an explicit user action. Not auto-recalculated on a rebuild, and counted as actual cost in confidence reporting. |
| Editable / estimated | Oldest-open FIFO, newest-closed FIFO, default unit cost, Amazon FIFO / weighted-average, None | A fallback estimate. May be auto-updated when you rebuild COGS, and counted as estimated in confidence reporting. |
This split is why a manually corrected cost survives a rebuild while a fallback estimate can be refreshed by it. It also drives cost-confidence reporting: the share of a product's stock backed by actual versus estimated cost tells you how much to trust its margin. The difference between correcting one layer and re-deriving many is covered in Correcting COGS: revalue vs rebuild vs recalculate.
Consignment stock
A layer can hold consigned — supplier-owned — stock. Consigned stock is physically in your warehouse and available to sell, and it carries a cost so that COGS can be realized normally when it ships. But because you don't own it until it sells, it's excluded from owned-inventory valuation and GL reconciliation, and excluded from a product's COGS report.
In the FIFO layers list a consigned layer is marked with a purple Consigned chip. Treat it as "cost-carrying but off-balance-sheet until sold."
The FEFO available-lots picker
Negative inventory events don't force you to accept the automatic FEFO/FIFO draw-down blindly — SKU.io can show you the open lots and let you override which ones ship. The available-lots picker powers this: on a fulfillment, a transfer ship-out, or a decrease adjustment, it lists a product's open layers in the same consumption order the back end would use, pre-filled with the FEFO suggestion.
For each lot the picker surfaces its batch number, expiry date, days to expiry, whether it's expired, available quantity, and average cost — everything you need to make or confirm a lot choice.
A few behaviors are worth knowing:
- A warehouse is required. Lots are always resolved for one product in one warehouse; the picker can't list lots without knowing which location you're drawing from.
- Days-to-expiry can be negative. A negative value means the lot is already expired — the picker flags it, and selecting an expired lot raises a warning (and, where expiry enforcement is on, blocks it).
- Undated lots sort last. Layers with no expiry date always fall to the bottom of the FEFO order, so dated stock is offered first.
- Overriding is explicit. If you pick something other than the FEFO-ideal lots, the panel labels it a non-FEFO override so the deviation is deliberate and visible. You can reset back to the FEFO suggestion at any time.
Because the picker reads the exact ordering the fulfillment engine uses, what you see is what would ship if you accepted the default — the override only changes the selection, never the costing method.
Empty and archived layers
Once a layer is fully drawn down its available quantity reaches 0. An empty layer is archivable — it stays in the ledger as history but drops out of the active layers list. A layer that still has available stock can't be archived; attempting to archive it returns a clear "not fully fulfilled" message. Manage this from browse & manage FIFO layers.
Next steps
- Browse & manage FIFO layers — find, filter, and archive layers.
- Revalue COGS — correct the cost on one or many layers.
- Rebuild COGS & the SKU COGS Health dashboard — re-derive editable costs and monitor cost confidence.
- Correcting COGS: revalue vs rebuild vs recalculate — which tool to reach for.
- The inventory movement ledger — the audit trail where realized COGS lands.
- Stock states — how available, allocated, on-hold, and non-sellable stock relate to open layers.
- FIFO layer fields, statuses & enums reference — every field, source, and status value.
- Inventory overview — the whole stock-control area.