Assign_try skips moves in staging state — expected behaviour?

Hi everyone, I’ve been away from Tryton development for a while and recently got back into it, so apologies if this is already known .

We ran into the following situation:

  1. An outgoing shipment is in waiting state.

    assign_try is called (manually or via cron).

  2. Stock is insufficient → some moves end up in staging state instead of assigned.

  3. New stock arrives later (purchase receipt, inventory correction, etc.).

  4. assign_cron fires again — but staging moves are silently skipped:

# stock/move.py
for move in moves:
    if move.state != 'draft':
        if move.state == 'staging':
            success = False
            continue   # ← staging is never re-evaluated

The shipment stays unassigned indefinitely. The cron was designed to recover shipments when stock becomes available, but staging blocks this recovery path entirely.

Questions:

  • Should assign_try at the shipment level draft its stuck staging moves before delegating to

    Move.assign_try? That would close the loop.

  • Or is staging intentionally a human-intervention state, and the cron is not meant to recover it?

We worked around it with a local override of ShipmentOut.assign_try that drafts staging moves first.

Thanks!

Stock moves are set to staging state only when they depend on other event to be activated. For example when using supply on sale, the stock move of such sale line is set to staging until it is supplied by the corresponding purchase line (through purchase request).

Thanks for the clarification, @ced - i know the design intent and is clear.

I understand that staging is intentional: the move is waiting for a specific supply event (e.g. the corresponding purchase line) to activate it.

In practice, though, we see a workflow that diverges from that model: a buyer receives the purchase request as a suggestion, but then places a different purchase order - perhaps for a larger quantity to get better prices, a different supplier, or simply as part of a consolidated order. When that stock arrives, the warehouse is replenished and assign_cron fires - but the staging moves are never re-evaluated, so shipments stay stuck indefinitely.

No buyer realistically wants to go back and manually review all open purchase requests for a product after they’ve already placed an order. The mental model for them is: “I ordered stock, it arrived, shipments should go out.” And the guy in the warehouse does not care if the shipment stays in staging since he does not know all details about purchases.

Our local workaround: if stock is available, the move is assigned and the now-redundant draft request is cleaned up; if not, the move stays in draft and the request is preserved. Non-draft requests (already purchased/in exception) are left untouched.

I think this is reasonable to keep as a custom override rather than a core change, since the core behaviour is correct within its own model. Just sharing the use case in case it’s useful context for others who run into the same situation. Am I missing something?

No as long as the line is considered as supplied (which means that the linked purchase is processing or done).

Then do not use “supply on sale” if you do not want it.

You are right. To add some context on why we ended up using supply_on_sale this way: our catalogue has around 80,000 products, and maintaining individual order points for each is not practical. supply_on_sale was the only built-in mechanism to automatically trigger purchase requests based on actual demand, without per-product configuration overhead. Now I remember my decision - getting older each year :smiley:

So our use case is not true make-to-order (one purchase per sale line, strict 1:1 coupling) - it is closer to demand-triggered replenishment at scale: sales generate purchase suggestions, buyers consolidate and order independently, and stock should flow to shipments once it arrives. This is not the purpose if supply_on_sale - you are right.

I understand that supply_on_sale was not designed for this, and the staging behaviour makes perfect sense in the strict make-to-order model. But it might be worth noting that there is a gap: no built-in mechanism scales to large catalogues for “buy what you sell” replenishment without tight coupling. That gap is what pushed us toward supply_on_sale as a practical approximation.

We will keep the local override for now as a workaround and rethink the whole problem - thanks for clarification. Should ask more often.

You do not need to maintain an order point per product if you purchase once you have not enough stock. There is a virtual default order point for each purchasable product with minimal stock 0.

It is true that the stock supply tasks may be slow on large product sets.
But it is not that bad. For example on a database with 5K products, it takes only 21s to supply the all stock. It may vary depending on the number of moves and closing period. But it should scale pretty linearly.

1 Like