0178

FIFO Cost of a Shipment

fifo
inventory-costing
list
rounding

FIFO Cost of a Shipment

When an item's Costing Method is FIFO, Business Central answers one question on every outbound posting: what did the units you just shipped originally cost? Each receipt creates a cost layer — a quantity at a unit cost — and each shipment consumes those layers oldest-first, which is why two identical shipments can carry different costs. That layer bookkeeping is the heart of the inventory costing engine, and you are going to build it in miniature.

Requirements

Create a codeunit named "FIFO Cost Calculator" with one public procedure:

procedure ComputeShipmentCosts(Quantities: List of [Decimal]; UnitCosts: List of [Decimal]): List of [Decimal]

The two lists describe one item's ledger in strict chronological order: entry i pairs Quantities.Get(i) with UnitCosts.Get(i).

What you may rely on — the tests never violate this:

  • Both lists always have the same length, and no quantity is ever zero.
  • A positive quantity is a receipt of that many units at the entry's unit cost. Unit costs of receipts are always > 0.
  • A negative quantity is a shipment of Abs(quantity) units; the entry's unit cost is always 0 and carries no information.
  • Quantities and unit costs have at most three decimal places — units can be fractional (kilograms ship in tenths).

What the returned list must guarantee — all of it is graded:

  1. Exactly one amount per shipment entry, in chronological order — receipts produce no output. A ledger with no shipments returns an empty list.
  2. Each amount is the cost of the goods shipped by that entry, as a positive number: the shipment consumes the item's cost layers strictly oldest-first. A shipment may span several layers, and a layer partially consumed by one shipment keeps only its remaining quantity for the next.
  3. A receipt posted after a shipment joins the back of the queue: it can only ever be consumed by shipments that come later in the ledger.
  4. Each shipment's cost is rounded to the nearest cent, ties away from zero — and the rounding happens exactly once per shipment, on the exact accumulated cost. Rounding each layer's piece separately produces a different (wrong) answer on some ledgers.
  5. If a shipment asks for more units than are on hand at that point in the ledger, the whole call must fail with an error message that contains the phrase Insufficient inventory (matched exactly, including capitalization). A receipt further down the ledger does not excuse the shortfall — inventory that has not arrived yet cannot ship.

What the tests check

The tests call ComputeShipmentCosts on fixed ledgers that cover each rule — a single layer, a shipment spanning two layers (where LIFO and average costing give different answers), a layer served across two shipments, a layer exhausted exactly at its boundary, a receipt arriving between shipments, a shipment that drains inventory to exactly zero and succeeds (followed by a restock and another shipment), fractional quantities whose per-layer pieces round differently than the total, a cost landing exactly on a half cent (where ties-to-even rounds the wrong way), a receipts-only ledger, and the two over-shipment error cases — plus one randomized 24-entry ledger whose expected costs are recomputed independently inside the test, so hardcoding the examples fails. All cost comparisons are exact to the cent.

Learn More

Hint 1
Each receipt is a cost layer: a remaining quantity and a unit cost. Shipping is not arithmetic on running totals — it is consuming those layers, oldest first, and a layer half-eaten by one shipment must stay half-eaten for the next. The starter's running average has no layers at all, which is exactly why it drifts.
Hint 2
Process the entries strictly in ledger order and keep a running on-hand quantity. A shipment may only draw on layers received before it; the moment it asks for more than is on hand, raise the error — a receipt further down the ledger changes nothing, because that inventory has not arrived yet.
Hint 3
One shape that works: two parallel lists holding each layer's remaining quantity and unit cost, plus an index pointing at the oldest layer with anything left. For each shipment, loop: take the smaller of what the layer still holds and what the shipment still needs, add take x unit cost to an exact running cost, shrink the layer, and advance the index past emptied layers. Round the accumulated cost to 0.01 once, only when the shipment is fully served.
ALBusiness Central 28.5
Press Compile to check your code compiles — Submit runs the tests.
The code editor is desktop-only
Open this problem on a computer to write and run code. Reading the description, tests and discussion works fine here.