# Cost layers

**Stance:** Cost **layers** are the durable accounting projection of **inbound** economic substance. They are **linked** to movements, **consumed** by outbound movements, and **never** silently rewritten.

## Valuation scope

Layers are maintained per **valuation key** (tenant-defined minimum):

- Typically: `item_id` + `warehouse_id` (+ `lot_id` if lot control).
- Optional dimensions: cost center, project, owner—must not break quantity conservation.

All quantities in layers sum to **on-hand** for that scope (subject to same timing as operational snapshot).

---

## Layer creation (on `stock_in`)

Created when an **inbound** movement posts (receipt, transfer in, production output, positive adjustment):

| Field (conceptual) | Purpose |
| --- | --- |
| `layer_id` | Immutable identifier |
| `source_movement_id` | **Strong link** to the movement that created the layer |
| `quantity_received` | Original units in layer |
| `quantity_remaining` | Decremented by issues; zero means exhausted |
| `unit_cost` | In functional currency per policy (includes allocated landed cost when modeled) |
| `cost_model` | `fifo` / `average` context flags |
| `opened_at` | Movement commit time (ordering with tie-breaker) |

**FIFO:** Each qualifying inbound creates **one or more** new layers (split only when policy requires distinct costs for same receipt line).

**Moving average:** Inbound does not always create a persistent “layer” row equal to FIFO; it may update a **running snapshot**—still **append** an **audit row** or movement-linked cost event so replay is possible (`cost-models.md`).

---

## Layer consumption (on `stock_out`)

On **outbound** (fulfillment, transfer out, consumption, negative adjustment):

1. Select layers per **FIFO order** (or apply average per `cost-models.md`).
2. For each slice: decrement `quantity_remaining`; compute **extended cost** = quantity × `unit_cost` with policy rounding.
3. Persist **consumption links**: `(outbound_movement_id, layer_id, quantity, extended_cost)`.

**Movement integrity:** Outbound quantity is fixed by operations; valuation **allocates** cost across layers to match that quantity. If insufficient layers (negative stock policy off), **block** outbound at operational layer—not “borrow” cost from nowhere.

---

## Linkage to movements

| Movement type | Layer effect |
| --- | --- |
| Receipt | Creates layer(s) |
| Transfer in | Creates layer(s) sourced from transfer; unit cost from source site layers at ship time (policy) |
| Fulfillment / issue | Consumes layers; produces **cost lines** on movement |
| Transfer out | Consumes at source; informs destination inbound cost |
| Adjustment + | Creates layer |
| Adjustment − | Consumes layers |

Every layer row points **up** to its creating movement; every consumption row points **out** to the issuing movement and **in** to the layer—full **bipartite** traceability.

---

## Immutability

- **Past** `unit_cost` on a created layer is immutable.
- Corrections use **new** movements (or reversal movements—`valuation-reversals.md`), not UPDATE-in-place on historical layer quantities except via **controlled rebuild** jobs that **replace** projection tables from source movements as a whole (`valuation-pipeline.md`).

---

## Related documents

- `valuation-pipeline.md` — calculation timing and replay  
- `valuation-reversals.md` — returns restore layers  
