Understand the order lifecycle

The three order statuses

An order can be confirmed, unpaid and half-shipped at the same time. Why that needs three independent statuses, and how to read them.

A buyer phones your Innendienst and asks whether their order is done. There are three correct answers, and they can disagree with each other. This is the single concept that makes B2B order management different, and the one that causes the most confusion when people arrive from a consumer shop.

Why one status is not enough

In a B2C shop the order is simple enough to collapse into one line: paid, then packed, then shipped, then delivered. Payment happens first, everything ships at once, and a single word describes the whole thing.

B2B breaks that in three places at once:

Payment comes last. Kauf auf Rechnung (30 days net, sometimes 60) is still the default in German B2B. The goods leave before a cent arrives. An order that has to be paid before it can ship describes almost none of your business.

Deliveries are partial. You have 6 of the 10 fittings on the shelf and the rest arrives from the manufacturer on Thursday. The buyer needs the 6 now. One order, two deliveries, two delivery notes.

Approval is a stage of its own. The order may exist and be legitimate while still waiting for someone at the buyer's company, or yours, to release it.

Squeeze that into one status field and you get values like partially_shipped_unpaid_awaiting_approval, which is three statuses spelled as one word. Every combination has to be invented in advance, and the first case nobody thought of has no value to put in the field.

The three dimensions

An order therefore carries three independent status fields, plus one flag.

DimensionValuesWho moves it
statuspendingplacedin_fulfillmentcompleted, or cancelledThe lifecycle actions: placing, shipping, completing, cancelling
payment_statusopen, pending, authorized, paid, partially_paid, refunded, failedThe Payments app, from outside
fulfillment_statusunfulfilled, partial, fulfilledNobody. It is derived from the position quantities
on_holdtrue / false, with a hold_reasonAn operator, or a credit check

The three are independent. Any lifecycle state can pair with any payment state and any fulfillment state, and most of the pairings are ordinary business rather than an error.

fulfillment_status is the odd one out: you never set it. It is read off the positions. When every position's shipped quantity covers its ordered quantity, the order is fulfilled; when some has shipped, partial; when nothing has, unfulfilled. Cancelled quantities count as settled, which is why cancelling the remaining 4 of 10 on a part-shipped order completes it rather than leaving it open forever.

Reading a real order

Take a typical order: ten hydraulic fittings, article 4711-A, bought on invoice by a customer with 30 days net. Six are in stock.

What happensstatuspayment_statusfulfillment_status
Buyer submits the cartplacedopenunfulfilled
Your ERP acknowledges itplacedopenunfulfilled
You ship 6 of 10in_fulfillmentopenpartial
Invoice for the partial delivery is paidin_fulfillmentpartially_paidpartial
The remaining 4 arrive and shipcompletedpartially_paidfulfilled
The second invoice is paidcompletedpaidfulfilled

Read row three again: in_fulfillment / open / partial. Confirmed, unpaid, and half delivered, all at once, and entirely correct. There is no single word for that row, which is the whole argument for three fields.

Look at row five as well. The order reaches completed while it is still not paid. Whether that is right for you is a decision, and you make it in the setting described under When an order closes below.

Which combinations are normal, and which are wrong

CombinationVerdict
placed / open / unfulfilledNormal. A new invoice order.
in_fulfillment / open / partialNormal. The everyday B2B state.
placed / paid / unfulfilledNormal: prepayment, or a card payment captured up front.
completed / open / fulfilledNormal on invoice terms. Chase the payment, not the order.
cancelled / paid / unfulfilledNeeds work. Money was taken for goods that will not ship; a refund is owed.
in_fulfillment / failed / partialNeeds work. Goods left the building on a payment that did not go through.
placed / anything / partialImpossible. A shipment moves the lifecycle to in_fulfillment.

The last row is the useful one for troubleshooting: if you ever see a part-shipped order still sitting at placed, the shipment was not recorded through the platform. Someone shipped from the warehouse system and never told the order.

What moves each one

status is moved by the actions on the order detail page: Acknowledge, Create shipment, Cancel positions, Cancel order, Complete.

payment_status is fed from outside. The Payments app owns the money: a payment runs its own lifecycle (created → authorized → captured → refunded) and reports the result onto the order. You can also set it by hand with Update payment, which is what you do for a bank transfer that arrived on your statement rather than through a payment provider. See Payment methods.

fulfillment_status moves only when position quantities move, through a shipment, a cancellation or a return.

on_hold is set with Put on hold and lifted with Release hold. What it blocks is a tenant-wide decision: shipping only; shipping and cancellation, which is the right setting while a credit check is running, because you do not want the order cancelled out from under the check either; or nothing, which makes the flag a visible warning and nothing more.

When an order closes

completed does not mean the same thing at every company, so it is configurable. There are three positions:

SettingAn order completes whenChoose it if
Full shipmentEvery position has shippedYou treat delivery as the end of your responsibility. The default.
Full shipment and paymentEverything has shipped and payment_status is paidYou consider an unpaid order an open order, which is common where Innendienst chases payment
ManuallyNever on its own; someone presses CompleteYour close is a judgement call, or your orders are services that never ship

The manual option is also the honest end of an order that will never have a shipment at all: a service, an installation, a licence.

Where you see all three

On the order detail page the three appear as badges in the header, side by side, with the lifecycle drawn as a flow on the Overview tab. On Order Management › Orders they are three separate columns and three separate filters, plus a filter for On hold.

That is what makes a real worklist possible. "Orders on hold", "orders awaiting approval" and "open returns" are each one filter, and each is a different queue worked by a different person. A single status field would force all three into one list.

Do not build reports on status alone. Revenue at risk is a payment_status question, warehouse backlog is a fulfillment_status question, and neither is answered by counting orders in in_fulfillment. A dashboard that reads one field and calls it "order status" will be quietly wrong about both.

Next