Skip to main content

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:

FieldMeaning
Original quantityHow many units the layer opened with.
Fulfilled quantityHow many have been drawn down so far.
Available quantityUnits still open — original − fulfilled. Stored generated: the database computes it; SKU.io never writes it directly.
Total costThe landed cost of the whole layer — the valuation basis.
Average costPer-unit cost — total cost / original quantity. Also stored generated.
Cost breakdownThe landed-cost category map (product, freight, duty, and more) that adds up to total cost.
Cost versionA counter bumped each time the layer is revalued, so re-costing is traceable.
ConditionThe stock condition the layer holds (for example new versus damaged).
Consignment flagWhether the layer holds supplier-owned stock — see Consignment stock.
Available and average cost are computed by the database

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 eventWhat it represents
Purchase-order receiptReceiving stock against a purchase order (an inbound shipment receipt line).
Warehouse transfer receiptReceiving the inbound side of a warehouse transfer.
Inventory adjustmentA positive stock adjustment — found stock, an opening balance, a correction up.
Stock takeA stock take item that reconciles a count upward.
Manufacturing / assembly outputFinished goods produced by a manufacturing order or an assembly.
Return receiptA customer return received back into stock.
Inventory movementA 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.

Layers open in owned warehouses only

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 changes which layer, not the valuation method

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:

  1. Locks the layer and re-checks its remaining capacity, so two concurrent fulfillments can't oversell the same batch.
  2. 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.
  3. Bumps the layer's fulfilled quantity under the row lock, shrinking its available quantity.
  4. 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.

Deadlock-safe ordering

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:

OrderSourceWhen it applies
1Oldest active Amazon FIFO layerThe warehouse is Amazon-managed (FBA/AWD), so stock lives in the Amazon layer ecosystem.
2Oldest active (open) FIFO layerA regular open layer exists for the product in that warehouse — the standard case.
3Newest closed FIFO layerNo layer is open, but a fully consumed one carries a recent cost.
4Product default costThe product's own Default Cost (unit cost). A 0 default is treated as unset and skipped.
5NoneNothing 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.

Warehouse-scoped, never mixed

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):

ClassificationSourcesBehavior
Locked / actualManual entry, purchase-order line, assembly line, transfer weighted-average, sales-order weighted-average, initial FNSKU cost, AWDBacked by a document or an explicit user action. Not auto-recalculated on a rebuild, and counted as actual cost in confidence reporting.
Editable / estimatedOldest-open FIFO, newest-closed FIFO, default unit cost, Amazon FIFO / weighted-average, NoneA 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

Last verified: