MOQ Trees Are Business Logic
Why nested minimum-order structures should be treated as explicit decision logic, not as an awkward data shape hiding inside preprocessing code.
MOQ Trees Are Business Logic
Minimum order quantities look boring until they dominate the decision.
A buyer does not simply choose 137 units of an item because the forecast says 137 units would be nice. The supplier may require a minimum order across a vendor. A product family may have its own threshold. A case pack may force increments of 12. A promotion bundle may open one part of the tree but not another. A warehouse may accept pallets but not loose eaches. A vendor may offer a price break only when a group-level threshold is reached.
This is not a minor data problem. It is business logic.
When MOQ logic is treated as preprocessing, planning systems become fragile. The optimizer receives a set of supposedly feasible quantities, but nobody can explain how they were generated. Runtime explodes because the same nested structure is rebuilt thousands of times. Edge cases appear when a vendor changes a pack size or adds a new product group. A planner asks why the model bought one more slow-moving SKU, and the answer is buried in a tree traversal function nobody wants to touch.
MOQ trees deserve to be modeled as first-class decision objects.
The simple version is not the real version
The textbook version of an MOQ is simple:
[ x_i = 0 \quad \text{or} \quad x_i \geq M_i ]
That is useful for teaching binary activation. It is not what many supply chains actually face.
Real MOQ structures are often nested. A vendor may require a total dollar minimum. Within that vendor, a product family may require a minimum unit count. Within the family, each SKU may have case-pack increments. Some groups share thresholds. Some thresholds are optional. Some thresholds only activate when the buyer chooses to order anything from the group.
A more realistic structure looks like a tree. The root is the vendor order event. Below it are product groups, brands, temperature zones, factories, container groups, or buying desks. Below those are SKUs. Each node may have activation rules, minimums, maximums, increments, costs, or eligibility constraints.
The tree is not just a representation. It defines which decisions are legal.
Start with the order event
A clean MOQ model starts with the order event, not the SKU.
The SKU-level question is, “how many units should I buy?” The event-level question is, “if I open this vendor order, which parts of the vendor tree must also open, and what increments become available?”
That distinction matters. If a vendor-level minimum is binding, the model may buy an extra SKU with weak economics because the order event is already open. If the order event is not open, that same SKU may be a terrible buy. The marginal value of a unit depends on the rest of the tree.
This is why naive item-level ranking fails. Sorting SKUs by standalone expected profit ignores the activation cost and shared thresholds created by the MOQ tree. The best local SKU may not be feasible. The best feasible bundle may include some SKUs that look mediocre in isolation.
Decision variables
A practical MILP formulation might include quantity variables and activation variables:
[ x_i \geq 0 \quad \text{units ordered for SKU } i ]
[ y_n \in {0,1} \quad \text{whether tree node } n \text{ is active} ]
For each SKU, pack increments can be represented with integer variables:
[ x_i = p_i z_i ]
where (p_i) is the pack size and (z_i) is an integer number of packs.
Activation logic links child nodes to parent nodes:
[ y_{child} \leq y_{parent} ]
Minimums can be written at any node:
[ \sum_{i \in subtree(n)} a_i x_i \geq M_n y_n ]
Maximums and capacity rules are similar:
[ \sum_{i \in subtree(n)} b_i x_i \leq U_n y_n ]
The exact formulation depends on the business rule. The point is that the tree structure should be explicit. It should not be hidden in a function that returns a mysterious list of allowed orders.
Strong formulation matters
MOQ models are prone to weak relaxations. A loose Big-M can let the LP relaxation partially open a vendor, partially satisfy a group minimum, and spread fractional quantities across many items. The integer solution may still be correct, but branch-and-bound has to do the work of rediscovering reality.
Tight bounds are not optional. If a node can only ever include 2,000 units because of warehouse capacity, do not use 10 million as the upper bound. If a SKU can only be ordered in 12-unit packs and never more than 240 units in the horizon, use that knowledge. A tight formulation is not mathematical vanity. It is operational knowledge encoded so the solver does not have to guess.
The same applies to preprocessing. If a branch of the MOQ tree is impossible because the vendor is inactive, the SKU is discontinued, the buying window is closed, or the product cannot arrive before the selling season ends, prune it before optimization. Pruning impossible decisions is not cheating. It is modeling the decision that actually exists.
Not every tree belongs in the MILP
Some systems do not need a full MILP representation of the MOQ tree. If the tree is small, the allowed bundles may be enumerated. If the objective is separable and the coupling is limited, a greedy or dynamic programming approach may work. If the tree is huge but highly structured, decomposition may be better.
The wrong choice is to pretend the tree does not exist.
For expensive simulations, the MOQ tree may define the atomic moves available to a search algorithm. Opening a vendor group, adding one pack to an active branch, swapping one SKU within a group, or closing a weak branch are all possible moves. The tree tells the algorithm what a legal move means.
In a production planning system, this can be more useful than forcing everything into one beautiful formulation. The model can generate candidate bundles, evaluate them under uncertainty, and update a global priority queue of economic moves. The tree still matters. It just lives in the policy logic rather than a monolithic MILP.
Uncertainty changes the value of opening a branch
MOQ decisions are risky because they create lumps.
Buying one extra pack is often small. Opening a new group threshold may force hundreds or thousands of dollars of additional inventory. The expected value of that activation depends on uncertain demand, lead time, seasonality, returns, markdown risk, supplier reliability, and future ordering opportunities.
A deterministic forecast can make MOQ decisions look cleaner than they are. If the forecast says demand is 100 units, a 96-unit pack looks perfect. But if demand is highly skewed, intermittent, or promotion-dependent, that same pack may create too much inventory in most futures and not enough in the few futures where demand spikes.
The MOQ tree should therefore be evaluated economically across scenarios. The question is not whether the forecast clears the minimum. The question is whether opening the node creates positive expected value after accounting for the downside risk of leftover inventory.
Metrics that matter
Track more than whether the order satisfied the MOQ.
Useful metrics include binding MOQ nodes, incremental inventory forced by MOQ logic, expected profit before and after MOQ feasibility, deadweight buy caused by activation thresholds, pack rounding loss, lost margin avoided by opening a node, solver root gap for MOQ constraints, preprocessing time, tree build time, tree cache hit rate, and number of planner overrides linked to MOQ logic.
The “deadweight buy” metric is especially useful. It measures inventory purchased primarily to satisfy a threshold rather than because the item has strong standalone economics. Deadweight buy is not always bad. Sometimes it is the price of accessing a profitable vendor order. But it should be visible.
Implementation notes
Represent the MOQ tree as a versioned object with stable node IDs. Do not infer the tree structure from string parsing at runtime if it can be avoided. Node IDs should survive name changes, vendor description changes, and hierarchy edits.
Separate static structure from decision-dependent state. The fact that SKU A belongs under group B is relatively static. The fact that group B is open in a candidate order is decision-dependent. The fact that demand in scenario 17 makes group B valuable is stochastic. Mixing these three layers makes caching dangerous and debugging painful.
Cache the static tree. Recompute the decision state. Evaluate stochastic value separately. That separation often cuts runtime without corrupting logic.
Log the path from each ordered SKU to the root. When a planner asks why an item was bought, the system should be able to say: “this SKU was added because vendor order V was open, group G had an active minimum, pack increment P was available, and the marginal value after satisfying the threshold was positive.”
Failure modes
The first failure mode is rebuilding the tree inside every simulation loop. If the tree is static for a run, build it once.
The second failure mode is mutating a cached tree. A cached static object should not quietly accumulate candidate-specific state.
The third failure mode is flattening the hierarchy too early. If the vendor-level minimum, group-level minimum, and SKU pack rule are collapsed into one list of allowed quantities, you lose the explanation and make future changes harder.
The fourth failure mode is treating MOQ feasibility as separate from economics. A feasible order is not necessarily a good order. An infeasible standalone SKU may be excellent when bundled with another branch.
The fifth failure mode is using fake precision. If the MOQ forces case packs, do not show planners recommendations with decimal units or false granularity. The decision should be displayed in the units the business can execute.
What to do in practice
Draw the MOQ tree before writing code. Mark every node with its activation rule, minimum, maximum, increment, owner, and expiration logic. Decide which parts are static, which parts are candidate-specific, and which parts are scenario-dependent. Then choose the algorithm.
A small tree may fit cleanly in a MILP. A large tree may need decomposition or search. A volatile tree may need stronger data governance before optimization is even worth attempting.
MOQ trees are not just ugly supplier data. They are the shape of the actual business decision. Treat them that way.