Blanket Order & Long-Term Contract Fulfillment
The selling-side half of a standing commitment drawn down by individual orders over time
1. Requirements
1.1 Functional requirements
- Record a standing commercial commitment between a legal entity and a customer (a Blanket Order, of the selling flavor — the same document type also serves a purchasing flavor, covered by the production-planning design): a validity window and a per-item quantity and price, frozen at authoring time.
- Let an individual Sales Order line draw down against a Blanket Order document, matched to its commitment line by item code (not by a line-level reference), at that frozen price; the quantity is not clamped at entry, but the save-time guard rejects anything past the remaining quantity — which, since the selling-side allowance ships unset, means the remaining quantity exactly.
- Keep the blanket line’s own “ordered so far” figure current by re-summing every submitted Sales Order line that references it, every time any such order is submitted, cancelled, or its status changes.
- Reject a drawdown that would push the cumulative ordered quantity for one blanket line past its remaining quantity by more than a configurable overrun allowance.
- Offer a second origination path: generate a brand-new Quotation or Sales Order directly from the Blanket Order, pre-filled with each line’s quantity still remaining.
- Carry the drawdown reference forward automatically when a Quotation converts into a Sales Order, so a commitment established at the quoting stage is not lost at the ordering stage.
1.2 Non-functional requirements
- Always-current drawdown figure: the “ordered so far” quantity must never be trusted as independently editable state — it is fully re-derived from currently submitted orders every time it is refreshed, never incremented.
- Frozen terms: an individual order’s price and quantity against a blanket line are fixed at the moment the reference is created; nothing about later edits to the Blanket Order itself flows back onto orders already drawing on it.
1.3 Constraints
- The Blanket Order carries no status field or lifecycle of its own beyond the ordinary draft/submitted/cancelled states — no “Closed” or “Fulfilled” condition exists once its lines are fully drawn down.
- The overrun allowance is a single instance-wide percentage (a separate setting per side — selling vs. purchasing — even though the guard logic that reads it is identical code), not configurable per Blanket Order or per customer.
- Nothing about a Blanket Order is visible to the production-planning design’s demand aggregation directly; it only reaches a plan transitively, through the Sales Orders drawn against it (see that design for the two-hop relationship on the manufacturing side).
2. High-Level Design
2.1 Component diagram
2.2 The drawdown lifecycle
A Blanket Order line is drawn down one of two ways, and both feed the same guard and refresh:
- Direct reference. An author sets three fields on a Quotation or Sales Order line — an against-blanket flag, a link to the Blanket Order document itself (not to one of its lines), and the frozen rate copied from the matching commitment line — either by hand or because the line was copied down from a Quotation that already carried them. The commitment line is resolved by matching the order line’s own item code against the Blanket Order’s lines, so every order line referencing the same Blanket Order and item code is grouped and summed together — several lines for one item aggregate into a single drawdown check — and the guard below runs once per (Blanket Order, item) pair.
- Generated draft. An author instead starts from the Blanket Order and asks it to produce a new Quotation or Sales Order (or, on the purchasing side, a Purchase Order), each line pre-filled with whatever quantity remains unordered on the corresponding blanket line — never negative, excluded entirely once nothing remains — with the same three drawdown fields set automatically. Only the rate is copied; later edits are constrained only by the guard in step 3.
- The overrun guard, run whenever a Sales Order carrying blanket-order lines is saved: for each referenced Blanket Order line,
remaining = line quantity − ordered-so-far, andallowed = remaining × (1 + allowance% ÷ 100). A requested quantity exceedingallowedrejects the save, naming the line and the ceiling. The allowance is a single selling-side setting (the purchasing flavor reads its own separately configured setting, though the check is the same code). - The refresh, run whenever any Sales Order carrying a blanket-order reference is submitted, cancelled, or has its status changed (including closed/reopened): the blanket line’s “ordered so far” figure is recomputed from scratch by summing quantity across every currently submitted Sales Order line referencing it, excluding closed orders — a full re-derivation, not an increment, so a cancellation naturally lowers the figure back down.
The guard and the refresh share the same set of referencing order lines — self-correcting after a cancellation — but not the same sum: the refresh totals stock-UOM quantity while the guard accumulates transaction-UOM quantity against a line with no declared UOM, so the comparison only holds cleanly when order lines are entered in the item’s stock UOM.
3. Deep Dive
3.1 Data model
Blanket Order — a submittable header record: a selling-or-purchasing type flag, a customer or supplier, a validity window (from/to date), and a line table. It carries no status field of its own; its lifecycle is the plain draft/submitted/cancelled state every submittable record has, with nothing analogous to the “Closed” condition the Sales Order carries.
Blanket Order line — per item: quantity, rate, a live ordered_qty (the refreshed drawdown figure described above), and a party-specific item code cross-reference resolved once at the Blanket Order’s own save time from the customer’s (or supplier’s) item-code cross-reference master and copied onto the line for display — a lookup convenience, not something drawdown logic reads.
Drawdown reference (on a Quotation or Sales Order line) — three fields that ride along together: a boolean flag, a link to the Blanket Order document itself (there is no line-level link field), and the rate frozen at the moment the reference was set. Which commitment line a drawdown counts against is resolved by matching the order line’s own item code against the Blanket Order’s lines — the reason a Blanket Order cannot carry two lines for the same item: item code is the only key tying a drawdown back to one commitment line, and a duplicate would make that match ambiguous. A Quotation line carrying these three fields passes them unchanged onto the Sales Order line created when that Quotation converts (see the order-to-cash design), so a commitment tagged as early as the quoting stage survives into the committed order without being re-entered.
3.2 Error handling
- Overrun beyond allowance: rejected by name, quoting the item and the allowed ceiling computed from the remaining quantity and the configured allowance.
- Negative line quantity on the Blanket Order itself: rejected at the Blanket Order’s own save time.
- Duplicate item lines on one Blanket Order: rejected at save time.
- Validity window: a Blanket Order’s own from-date cannot be later than its to-date.
4. Scale and Reliability
- Refresh cost scales with orders drawn against one Blanket Order line, not with catalog size: recomputing “ordered so far” re-sums only the Sales Order lines referencing that specific Blanket Order, triggered synchronously by the referencing order’s own submit/cancel/status-change — no background job, no batching across multiple orders drawn down in the same session.
- No cross-order locking on the drawdown figure: the guard and the refresh both read a fresh sum at the moment they run, so two orders saved against the same blanket line in close succession are each guarded by their own read rather than a shared lock — the same soft-guarantee shape used elsewhere in this document set for reservation-style checks.
5. Trade-off Analysis
| Decision | Trade-off |
|---|---|
| “Ordered so far” is always re-derived from submitted orders, never incrementally maintained | Self-correcting after any cancellation, at the cost of a full re-sum on every referencing order’s submit/cancel rather than a cheap increment. |
| Two independent origination paths (manual tagging vs. generated draft) rather than one enforced path | Flexible — a commitment can be drawn down from a document authored any way — at the cost of the overrun guard being the only thing standing between either path and an over-committed blanket line. |
| A single instance-wide overrun allowance rather than one configurable per Blanket Order or per customer | Simple to administer, at the cost of every commitment sharing the same tolerance regardless of how firm any individual customer’s agreement actually is. |
| No status/lifecycle field on the Blanket Order itself | Nothing to keep synchronized as drawdown progresses — but there is also no way to see “fully drawn down” without comparing quantity to ordered-so-far by hand, unlike the Sales Order’s own derived status. |
| Blanket Order reaches production planning only transitively, through the Sales Orders drawn against it | Keeps the commitment’s own guard logic scoped to the transactional documents it actually governs, at the cost of a production plan having no direct visibility into which demand originated from a standing commitment (see the production-planning design). |
6. What to Revisit as the System Grows
- A derived completion state for the Blanket Order itself (e.g., fully drawn down vs. still open), mirroring the Sales Order’s own derived-status pattern, rather than requiring a manual quantity-vs-ordered-so-far comparison.
- A per-customer or per-commitment overrun allowance, once a single instance-wide percentage stops fitting agreements of meaningfully different firmness.
- Reconciling the two origination paths’ guarantees: the generated-draft path already caps its pre-filled quantity at what remains, while the manual-tagging path relies entirely on the save-time guard catching an overrun after the fact — worth converging on one shared pre-check if the manual path proves error-prone in practice.
- Converge the guard and the refresh on one quantity basis, or give the Blanket Order line an explicit UOM — they currently compare stock-UOM against transaction-UOM quantity.
This document sits at the lower end of its calibration band deliberately: the selling-side behavior here is a small, complete mechanism — two origination paths converging on one guard and one refresh — and padding it further would misrepresent how little of it there actually is.