Skip to main content

Quantities and adjustments

This page is the arithmetic. Getting it wrong is the single most likely way for a Goods Received integration to report the wrong stock, so it is worth reading before writing any consumer.

The four quantities

QuantityMeansWhere it comes from
ExpectedWhat the delivery note said would arriveThe shipper, via the delivery note
AdjustedThe signed difference between what arrived and what was expectedThe receiving store
ApprovedWhat the store acceptedEntered by staff (or derived from reason-code adjustments); validated by the service
DeltaThe change in approved since this line was last publishedCalculated, only on published events

The first three are related by one rule, and the service enforces it:

approved = expected + adjusted

Adjusted is signed. A shortage is negative, a surplus is positive:

SituationExpectedAdjustedApproved
Everything arrived10010
Three missing or damaged10−37
Two more than the note said10+212
Nothing usable arrived10−100

Approved quantity is what staff enter, by counter, keyboard or by scanning items as they are checked. What the service enforces is that the difference is explained: if the approved quantity does not match the expected quantity, reason codes accounting for exactly the difference must be supplied with it.

The relationship works in both directions, and a receiving app can offer either. Enter an approved quantity of 7 against an expected 10 and a −3 difference has to be explained; or record the reasons first — 3 damaged — and the approved quantity follows as 7. Same three numbers, same rule, whichever end staff start from.

Reason codes explain the adjustment

Every non-zero adjustment must be broken down into one or more reason codes, each carrying its own signed quantity. Their sum must equal the adjustment:

adjusted = Σ reason code quantities

A line short by five units, three damaged in transit and two never loaded:

Reason codeQuantity
damaged−3
not-delivered−2
Adjusted−5

With an expected quantity of 20, the approved quantity is 15.

Reason codes are signed the same way as the adjustment, so an over-delivery uses positive quantities — a code such as surplus or over-delivery at +2.

Each reason code carries its own sign in configuration, so a code is not free to be used either way. A code marked as negative-by-default is offered for shortages, one not so marked for surpluses, and one with no marking for both. A store configured only with negative codes therefore cannot record an over-delivery at all — see Configuration.

A reason code can also be used only once per line. To split a difference you use two different codes, not the same code twice.

🛑 Reason codes must exist before they can be used. They are configured per store, and an adjustment quoting a code that is not configured for that store is rejected outright. If staff cannot save a correction, this is almost always why — see Configuration.

⚠ A non-zero adjustment without reason codes is rejected, and so is a set of reason codes that does not add up to the adjustment. Both are deliberate: an unexplained difference is not something the service will record.

Delta versus level

Published events carry both an absolute figure and a change, and they mean different things:

FieldKindMeaning
expectedQuantityLevelWhat the note said. Does not change between events for a line
approvedQuantityLevelThe accepted quantity as it now stands
deltaQuantityDeltaThe change in approved quantity since this line was last published
reasonCodesLevelThe line's complete current set of reason codes — not what changed

🛑 deltaQuantity is a change but reasonCodes is a total. They are not two views of the same thing. A consumer that accumulates reason code quantities across events the way it accumulates deltaQuantity will over-count every correction. Replace a line's reason codes with each event's set; add up only deltaQuantity.

Worked example: a correction

A line expects 10 units. Three arrive damaged, so the store records an adjustment of −3 against damaged and approves the line:

{
"lineId": "1",
"expectedQuantity": 10,
"approvedQuantity": 7,
"deltaQuantity": 7,
"reasonCodes": [{ "id": "damaged", "quantity": -3 }]
}

deltaQuantity equals approvedQuantity here because nothing had been published for this line before — the whole approved quantity is new information.

The next morning one more unit is found damaged. The store changes the line's adjustment to −4 in total and approves again:

{
"lineId": "1",
"expectedQuantity": 10,
"approvedQuantity": 6,
"deltaQuantity": -1,
"reasonCodes": [{ "id": "damaged", "quantity": -4 }]
}

Read the second event carefully:

  • approvedQuantity is 6 — the new total, not the change.
  • deltaQuantity is −1 — one fewer unit than last time.
  • reasonCodes is −4 damaged — the running total for the line, not the extra −1.

A stock consumer applies deltaQuantity (−1) to what it already holds. A consumer that rebuilds state from scratch uses approvedQuantity (6). A consumer reporting on causes of loss uses reasonCodes (−4) and overwrites whatever it had for that line.

Summing across the two events: 7 + (−1) = 6, which matches approvedQuantity. That identity always holds, and it is the cheapest way to check a consumer is correct.

Precision

Quantities are numbers, not integers. Items sold by weight or length arrive in fractional quantities, and a delivery of 12.5 kg is expected, approved and adjusted as 12.5. Do not round on the way in, and do not assume a quantity fits an integer field.

Prices are per unit, in the currency named on the line. A line's unitPrice may be absent when the source system did not supply one — that means unpriced, not free.

Serial numbers, batches and dates

A line can carry a batch number, a best-before date, an expiration date and a list of serial numbers. These describe what was expected, and the service passes them through to consumers unchanged; they are not reduced or split when the approved quantity is lower than expected.

⚠ So a line expecting 10 serialised units and approving 7 still carries all 10 serial numbers. Do not infer which individual units were accepted from the serial number list — the service does not record that, and treating the list as the accepted set will overstate what is in stock.