You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ofbiz.apache.org by "Swapnil Shah (Jira)" <ji...@apache.org> on 2026/10/06 08:27:00 UTC

[jira] [Updated] (OFBIZ-13462) MRP Run - Constrained (Finite Capacity) Mode

     [ https://issues.apache.org/jira/browse/OFBIZ-13462?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Swapnil Shah updated OFBIZ-13462:
---------------------------------
    Description: 
The OFBiz MRP engine currently plans with an infinite-capacity assumption. When it back-schedules a proposed Production Run from its required-by date, it only checks the machine's working calendar i.e., on which weekdays the machine is open and for how many hours, and never checks how much of that time is already taken, either by the existing Production Runs on the shop floor or by the other orders proposed within the same MRP run.

As a result, every proposed order sees every machine as completely free. For example, two runs each needing 6 hours on a machine with an 8-hour day would both get planned onto the same day (i.e., 12 hours of work into an 8-hour window) without any warning, so the plan looks feasible on paper but is impossible to execute, and planners have to find and resolve these overloads manually.

MRP should offer an optional mode to trigger a Constrained Capacity run with finite-capacity planning i.e., treat each machine's daily capacity the way MRP already treats material, as a limited resource that each planned order consumes, so that later orders only get what remains, demand is prioritized by date (the same principle used for material netting), and orders that cannot fit are visibly delayed (flagged Late) or recorded as short (flagged Short), with the Requirement keeping its full quantity and storing the part capacity or material cannot cover, rather than silently overbooked.

Not to mention, the default (Unconstrained Capacity) run would remain untouched, so existing users see zero change unless they opt in for a Constrained run or make it the default (check the "Configuring Default MRP Mode" section below).

The 'Constrained Capacity' mode is optional and disabled by default, and MRP defaults to the 'Unconstrained Capacity Plan' mode, which behaves exactly as today.
h2. High Level Feature Expectation

*Terminology:*
 * a "proposed Production Run" is a planned manufacturing order that the MRP run creates;
 * a "committed Production Run" is an existing approved or running Production Run on the shop floor, and the "originating demand" is what triggered a proposal, typically a customer Sales Order or a stock level falling below its configured minimum.

*How the MRP run would work when the new Constrained Capacity mode is switched on:*
 # *Committed work gets placed first.* Open and running Production Runs would be laid into capacity before any proposal gets sized, irrespective of their dates, and MRP would never move or modify one. A proposal would never take the hours held by a committed run, so no overlap arises and nothing needs to be resolved manually afterwards.
 # *Competing demand gets served in priority classes, and then by date* i.e., Sales Orders first, then the parts of work already released, then Minimum Stock, and then Forecast.
 # *Orders get scheduled backward from their due date.* If capacity forces the start into the past, the order would get scheduled forward instead and marked Late.
 # {*}If an order cannot be completed inside the planning horizon{*}, its Requirement would keep the full quantity and record how much of it cannot be covered (up to all of it), and get marked Short. What can be made is the quantity minus the unmet quantity, and the quantity never gets quietly reduced.
 # *Knock-on effects run down the product tree.* A delayed component would push its parent's start, so no assembly gets planned to start before its parts are ready, and a short component would cap what the parent can build. Nothing gets carried onto a later order for the same product, as the shortfall stays on the Requirement that could not cover it.
 # {*}Late/Short indicators{*}, the date the plan was measured against and any unmet quantity would be stored on the Requirement and shown on the Requirement screens, while what limited a demand (i.e., the bottleneck machine or part) would be named in the run's messages.

h2. Scenario Planning: Two plans side by side - Constrained and Unconstrained modes

Each MRP run can be executed in one of two modes i.e., Constrained Capacity (finite capacity run, this feature) or Unconstrained Capacity (infinite capacity run, today's behavior), and everything a run produces would get stamped with its mode. A new run would replace only its own mode's results i.e., its MRP events and its unapproved Requirements, while the other mode's plan survives untouched. This way planners can keep both pictures at once, what demand truly wants (Unconstrained) next to what capacity can deliver (Constrained), and read the gap between them as the measurable cost of the capacity constraint.
h2. Configuring Default MRP Mode

Each Facility can carry a Default MRP Mode (Facility.defaultMrpModeEnumId). A run started without a mode (e.g., every scheduled run) would plan in that Facility's default mode, and a Facility with no default would plan Unconstrained, exactly as today. There is no screen to set it yet, so for now it has to be set as data.

Both modes would propose ordinary Requirements (REQ_PROPOSED), and either of them can be approved. The Facility's default mode is its plan of record, and hence that is the plan to approve from, whereas the other mode's plan is just for comparison and gets replaced by that mode's next run. To make the other mode's plan the live one, change the Facility's Default MRP Mode and re-run MRP, so that the live plan gets regenerated against everything that has changed since.
h2. A facility plans only what it stocks

Both modes would raise Requirements only for the products with a ProductFacility row at the Facility being planned, and the supply needed for anything else would get reported on the plan as "No ProductFacility record for [facility]; no requirement created.", irrespective of whether the product is bought or made. The Constrained mode wouldn't plan such a demand at all, so it would never book machine time for work the Facility is not going to do. Supplying one Facility's demand from another Facility is inter-facility transfer planning (OFBIZ-7356).
h2. Related work
 * OFBIZ-13628 — MRP Planning Rules. To begin with, Makes the order of the demand classes (R3), dated-before-undated within a class, and the use of idle machine time (R11) configurable per Facility Group, and records on a run header which rule and parameters produced each plan. Later on as the need arises any new rule can be coded for and controlled using rule setting by planners.    
 * OFBIZ-13630 — Run MRP for a Facility Group. Plans every member of a Facility Group, one plan per member, in one transaction.

h2. Important Rules

*R1 — Two modes, and the existing one stays untouched.* A run would be either Constrained (capacity is finite) or Unconstrained (today's behavior). Each run stamps its own results and replaces only its own, so a planner can hold both and compare what the factory wants against what it can actually do. With the feature unused, the results would be identical to the current release.

*R2 — Orders get planned one at a time, in priority order.* Each demand would get planned through its whole product tree before the next one is considered, and all of them would draw on the same pool of stock and machine hours. Whoever gets planned first takes what it needs, and the later demands only see what is left. This is what allows every Requirement to be traced back to the order it serves.

*R2a — Every demand keeps its own Requirement, even for the same product.* Two orders for one product would get planned one after the other and recorded as two Requirements, and never merged into a single larger one. Each one would carry the quantity, dates and shortfall of the demand it serves, so a planner can always tell which order a proposed batch belongs to. Merging would lose that link, and then there would be no way to tell on which customer a shortage falls.

{*}R3 — Priority classes decide who gets served first, ahead of dates{*}, and dates order the demands within a class.
 # Approved Sales Orders
 # Parts for Production Runs already on the floor
 # Parts for approved Production Run Requirements
 # Topping stock back up to its minimum
 # Sales Forecast

(Making this order configurable is OFBIZ-13628, which adds R3a to R3c.)

*R4 — Anything already on hand or already coming gets counted first.* Stock, approved Purchase Orders, open Production Runs and approved Requirements would all reduce what the plan proposes. Supply arriving later than wanted would still count i.e., the work would wait for it and get flagged, rather than the same material getting ordered twice.

*R4a — An open Production Run delivers what it still owes, irrespective of the state of its operations.* A run's remaining output counts as supply because output is still owed on it, and not because machine time is still booked for it. A run whose last machine step is finished (with only a machine-less step such as a trim or an inspection left) is in fact closer to delivering, so its balance would get credited and dated from the run's own finish. The only case that credits nothing is a run whose remaining work cannot be placed inside the planning window at all, because its components cannot arrive in time.

*R5 — A level covered by supply pulls no components.* For instance, if the skateboards are already coming, the plan wouldn't order deck materials for them.

*R6 — A shortage gets flagged, not hidden.* A Requirement would always show the full quantity needed, and where capacity or material falls short, it would record how much cannot be covered rather than quietly asking for less. What can actually be made is the quantity minus the unmet quantity.

*R7 — A part that arrives in time causes a delay, whereas one that cannot causes a shortage.* Where a purchase can land inside the planning window, it would get ordered and production would simply wait for it, so the quantity stays unaffected. Only a part that cannot be here in time (because it has no supplier whose terms are in force, or because its lead time runs past the window) would limit how much can be built. Lead time is counted in the supplier's working days.

*R8 — The required-by date is when the whole order gets finished, walked through the working calendar, even if that falls past the horizon.* The receipt would show its own, earlier date i.e., when the units that genuinely land are ready.

*R8a — The ideal date assumes material and capacity were fully available, and could fall in the past.* Beside every date the plan can actually achieve, a Requirement would carry the date it should have had i.e., what the demand would have required if nothing had been short and the machine had been free, measured against each resource's own calendar. It deliberately runs back past today when the demand is already overdue, as that is what would show a purchase as late instead of quietly re-baselining it to today. What separates it from the achievable date is the load and never the calendar, so an ideal date would honor working days, holidays and shutdowns exactly as the achievable one does, because a closed day is a fact about the plant and not a queue of other orders.

*R9 — No date ever gets set in the past.* A run started at 4 pm wouldn't plan work into that morning, and the work that should already have started would begin now and get flagged late.

*R10 — Work already on the floor gets placed first and never moved.* Only what genuinely remains would get booked i.e., the outstanding quantity, skipping the finished operations, and charging the setup only where the floor has recorded that it was done.

*R11 — A higher-priority order never suffers so that a lower-priority one can run, and no job ever gets split.* Where an order is waiting for its parts, the machine in front of it would be held. Lower-priority work can take that time only when both of these hold: it cannot move the waiting order by even a minute, and it can be finished in full before that order begins. If either of them fails, the work would get planned after the waiting order and the machine would simply stand idle. In other words, idle time gets accepted rather than bought at the cost of the order that was already promised. A job would never be broken in two around another one, which would mean setting the machine up twice while the plan had paid for only one setup.

Point to note here is that this prohibition is on splitting a job in time on one machine i.e., a job never gets broken around other work sitting in the middle of its day. Dividing a job between the machines of a group is a different thing, governed by R12, and each piece of a divided job is itself an unbroken block on its own machine.

*R12 — A job runs on one machine of a group, and jobs spread across the group.* Two 8-hour machines offer 16 hours of work in a day, but one job would use one machine and run at that machine's pace. Where several jobs need the group, they would get placed on different machines and run at the same time, with the emptier machine chosen first. A job too large for any single machine to deliver on time would get divided between the machines, and each piece would pay its own setup. A step could instead name one machine, in which case only that machine would be considered.

A job that already meets its date would never be divided, even when other machines stand idle, as dividing exists to make a date achievable and not to finish early (the same reasoning as R15).

*R13 — A shared calendar is not a shared machine.* Two machines on the same calendar would each get their own full hours.

*R14 — Time comes from the estimates, and capacity gets used only where a machine is named.* A step with a time but no machine would still take that long and consume no capacity.

*R15 — Capacity gets balanced by day, and work gets placed to finish when it is due.* No machine would be booked beyond its hours for a day, and every job would carry a start and finish time. A job that can be delivered on time would get placed to finish at its due moment, and not as early as the machine allows, e.g., an order due at 16:00 would run up to 16:00 rather than finishing mid-morning and sitting as stock. Jobs queue behind one another on the same machine, the time held in front of an order waiting for its parts isn't given away, and a smaller job can take that gap only if it finishes before the waiting order begins. An order that cannot be delivered on time would get placed as early as it can be instead, since finishing late is the only remaining choice. What the plan doesn't do is optimize the resulting order i.e., it follows the planning priority and not the fewest machine changeovers.

*R16 — Approving a Requirement makes it a commitment.* Re-running the plan would never delete or downgrade an approved Requirement, and both modes would count it as supply.

The numbering follows the master document, and R17 to R19 are on OFBIZ-13630.

*R20 — A facility plans only what it stocks.* Both modes would raise a Requirement only for a product with a ProductFacility row at the Facility being planned. When supply is needed for any other product, the plan would record an error event instead, "No ProductFacility record for [facility]; no requirement created.", irrespective of whether the product is bought or made. In the Constrained mode, a demand for a product the Facility doesn't stock (e.g., a Sales Order, a Forecast, or a component of work already released) would be kept on record and reported, but not planned i.e., nothing gets made or bought for it, so no bill of materials gets exploded and no machine time gets booked for work that is never going to be done there. A component the Facility doesn't stock, needed by a product it does stock, would still get planned because its parent needs it, but it wouldn't raise any Requirement.
h2. How Constrained Capacity Mode Works
 * *Committed work gets placed first.* Open Production Runs would be laid into capacity before any proposal gets sized, irrespective of their dates. A committed run scheduled later wouldn't be displaced by an earlier-needed proposal, and MRP would never reschedule or modify one.
 * *Demands get served in priority classes, and then by date* i.e., Sales Orders, then the components of open Production Runs, then the components of approved Production Run Requirements, then below-minimum stock, and then Forecast. Dates order the demands within a class, and not across classes.
 * *Backward scheduling with a forward fallback.* Orders would get scheduled backward from their due date, and where capacity forces a start into the past, they would get scheduled forward instead and flagged Late.
 * *A shortage gets flagged, not shrunk.* Where capacity or material cannot cover the full quantity, the Requirement would keep its net requirement and record the unmetQuantity with Short. What can actually be made is quantity − unmetQuantity, and the quantity never gets quietly reduced.
 * *Cascades run down the tree, not across orders.* A delayed component would push its parent's start, and a short component would cap what the parent can build. There is no backlog carried onto a later order for the same product, as the shortfall already sits on the Requirement that could not cover it.
 * *Flags and tracking.* Late and Short indicators, the date the plan was measured against, the unmet quantities and the mode would persist on Requirements, and the mode would persist on events. They are not yet carried onto a Production Run or Purchase Order at conversion.
 * *Only what the Facility stocks gets planned.* A demand for a product with no ProductFacility row at the Facility would be recorded and reported, but not planned, and a component the Facility doesn't stock would still get planned for its parent's sake but wouldn't raise any Requirement (R20).
 * *One plan per Facility and mode.* A run would replace its own mode's events and unapproved Requirements for the Facility it plans, and nothing else.

h2. Entity Model extensions
h3. Data model

Every addition is nullable and stays inert until used, so a database that loads the reference data and never runs the Constrained mode would plan exactly as before. No new tables are needed, as the capacity bookkeeping during a run is held in memory, and what persists is the plan i.e., Requirements and events as today, carrying the fields below.

*Fields added to existing entities*
||Entity||Field||Type||Meaning||
|Requirement|isLate|indicator|Y when finite scheduling pushed the completion past the date the demand needed. Rolled up i.e., an end item would be late if any run feeding it was.|
|Requirement|isShort|indicator|Y when capacity or material cannot cover the full quantity.|
|Requirement|unmetQuantity|fixed-point|The part of the quantity that cannot be covered; what can be made is quantity − unmetQuantity.|
|Requirement|idealRequiredByDate|date-time|The date the demand needed, measured as if material and capacity were free (R8a), which is the reference the achievable date is read against. Set on every Constrained Requirement, flagged or not.|
|Requirement|mrpModeEnumId|id|The mode whose run proposed it. It scopes the re-initialization i.e., a run would delete only its own mode's unapproved Requirements.|
|MrpEvent|mrpModeEnumId|id|The mode whose run wrote it. isLate already exists on MrpEvent and gets reused, and the existing facilityId would now be filled on every Constrained event.|
|Facility|defaultMrpModeEnumId|id|The Facility's default mode, used when a run names none, and empty means Unconstrained. Set as data, as there is no screen for it yet.|

Index REQ_MRPMODE_IDX on Requirement (facilityId, statusId, requirementTypeId, mrpModeEnumId) keeps the mode-scoped delete on an index range. The mode fields carry no foreign key, as their values come from the MRP_MODE enumeration.

*Supporting reference data (seed data, not schema)*
 * Enumeration type MRP_MODE: MRP_CONSTRAINED, MRP_UNCONSTRAINED.
 * MRP event types INTERNAL_REQ_RECP and INTERNAL_REQ_REQ i.e., the output and the component demand of an approved Production Run Requirement, which the classic engine ignores.
 * Facility calendar type SHIPPING, and a SHIPPING calendar (weekdays from 08:30, eight hours), which is the default calendar the outbound leg (ProductFacility.daysToShip) gets walked through.

What is deliberately not persisted: the capacity ledger, where each job was placed, which machine of a group it runs on, and the explanation of what limited a demand (that is given in the run's messages).

*Upgrading an existing database.* All columns are nullable, and the entity engine would add them at start-up. Load the seed data (./gradlew "ofbiz --load-data readers=seed") for the enumerations and statuses, as without it the MRP Mode lists would be empty.
h3. Service and screen surface
 * executeMrp would gain mrpMode (empty means the Facility's default) and planningHorizonDays, and would delegate a Constrained run to the new executeConstrainedMrp, which returns mrpId and requirementsCreated.
 * Run MRP would offer the mode and the horizon. Find Inventory Event Plan would filter by mode and show the ERROR events as notices, and the Requirement screens would filter by mode and show the Late, Short, unmet and ideal-date fields.

*Points that matter functionally:*
 * Capacity bookkeeping happens in working memory for the run and leaves no new tables behind, and what persists is the Requirements (carrying these fields) and the MRP events.
 * Existing data stays untouched. Requirements created before this feature would simply have the new fields empty, so no migration or cleanup is needed. With the Constrained mode unused, a plan stays unchanged, except that a made product the Facility doesn't stock would now get reported as well as a bought one (R20).
 * Approved Requirements are outside all of this. Approval fixes a Requirement as a commitment i.e., it would never be deleted by any run of either mode and would be counted as real supply by both modes.

The flags are durable. They would stay on the Requirement after the MRP screen is closed and get shown on the Requirement screens, which can be filtered by the mode that produced a Requirement. They are not yet carried onto the Production Run when a Requirement gets converted.
h2. Use Cases

Every case is written against the manufacturing-demo dataset (OFBIZ-13438) so that anyone can set it up and check it, and every case can be verified from the data the run produces i.e., no case depends on reading a log.
h3. The demo scenario in plain terms

A skateboard factory, Facility MFG_PLANT, with two machines:
||Machine||Works||Hours||
|LAM|Monday, Wednesday, Friday|08:30–16:30|
|AMC|Monday to Friday|08:30–16:30|

Two things are made:
 * DSK15144-00, the skateboard deck, made on LAM from veneers and glue.
 * DCGAPSOSR12, the finished skateboard, made on AMC from one deck plus a sticker, a warranty card and a transfer.

Every manufacturing step costs the same in this dataset i.e., 15 minutes to set the machine up, and then 5 minutes per unit. So a full 8-hour day is 480 minutes, less 15 for setup, leaving 465 i.e., 93 units a day on either machine, and that single figure explains most of the numbers below.

Bills of material: one skateboard needs 1 deck, 1 sticker, 1 warranty card and 1 transfer, while one deck needs 2 face veneers, 2 cross-ply veneers, 3 core veneers and 0.83 of a unit of glue.

Every purchased part has a supplier, a lead time and a minimum order quantity. What separates them is how long they take, as a part that can arrive inside the planning window would merely delay production, whereas one whose lead time runs past the window would limit how much can be built at all. That distinction drives several cases below.

Minimum Stock levels are 100 finished skateboards and 50 decks. The demo doesn't seed any stock or orders, so the cases below assume a small set of transactional data (Sales Orders, Production Runs, Purchase Orders and opening stock), defined once in "The scenario these cases run against" and referenced by name.
h3. Key Terms
 * *Demand* — something that needs making or buying, e.g., a Sales Order, a Minimum Stock, or parts for an order already on the shop floor.
 * *Requirement* — what the plan proposes in response i.e., make this many, by this date.
 * *Committed work* — Production Runs already released to the floor, which the plan works around.
 * *Late* — the plan cannot deliver by the date asked for. *Short* — it cannot deliver the full quantity.
 * *Horizon/Planning Horizon (or fence)* — how far ahead the run plans; beyond it, the run says nothing.

h3. The scenario these cases run against

The demo dataset supplies the standing configuration i.e., the plant, machines, calendars, routings, bills of material, products and suppliers, and the transactional data below completes it. None of it ships with the demo, so either create it or load it as a test fixture.

*Reference run date:* Monday 2 March 2026, 08:00, which is before the shift opens, so the first day is whole. Fixing the run date matters because LAM works Monday, Wednesday and Friday, and the same order placed on a Tuesday would produce different dates. Working days in the fortnight that follows:
||Machine||Working days||
|LAM|Mon 2, Wed 4, Fri 6, Mon 9, Wed 11, Fri 13 March|
|AMC|Mon 2 – Fri 6, Mon 9 – Fri 13 March|

*Capacity:* 480 minutes a day, less 15 minutes of setup, at 5 minutes a unit i.e., 93 units per machine per day. A batch of n units occupies 15 + 5n minutes.

*Supplier terms*

Every purchased component has a supplier record with a lead time and a minimum order quantity. The demo ships the minimum order quantities but no lead times, so set standardLeadTimeDays as below.
||Component||Supplier||Lead time||Minimum order||
|DRGAMBSK8II glue|SUP_002|3 days|50|
|DRVM52105036FAS face veneer|SUP_001|7 days|100|
|DRVM60361050XA cross-ply veneer|SUP_001|7 days|100|
|DRVM62105036CA core veneer|SUP_001|45 days|100|
|DCSPPUSA sticker|SUP_003|5 days|500|
|DRSASOCWC2 warranty card|SUP_003|5 days|100|
|DRTGPSOSR12 transfer|SUP_003|5 days|100|

Lead times are counted in the supplier's working days, so seven of them would land later than seven calendar days. Core veneer is the outlier on purpose, as at 45 working days it cannot arrive inside a normal planning window, and that is how the cases needing a genuinely unobtainable part are built. Everything else can be bought in time, and hence would only ever delay production.

*Stock on hand at MFG_PLANT*
||Fixture||Product||Quantity||
|STK-DECK-40|DSK15144-00 deck|40|
|STK-DECK-20|DSK15144-00 deck|20|
|STK-VENEER-10|face, cross-ply and core veneer|10 each|
|STK-TRIM|sticker, warranty card, transfer|500 each|

STK-TRIM keeps the finished-goods cases about machine time rather than about missing trim, so use it wherever a case is not specifically about a material shortage.

*Sales Orders*

All approved, for DCGAPSOSR12, shipping from MFG_PLANT.
||Fixture||Quantity||Wanted by||Occupies||
|SO-A|60|Wed 4 Mar|5h15m on AMC|
|SO-B|50|Wed 4 Mar|4h35m on AMC|
|SO-LATE|200|Wed 4 Mar|16h55m on AMC|
|SO-BIG|600|Fri 6 Mar|more than a 5-day horizon holds|
|SO-100|100|Fri 13 Mar|8h35m on AMC|
|SO-400|400|Fri 13 Mar|33h35m on AMC|
|SO-PAIR-1, SO-PAIR-2|50 each|Fri 13 Mar|4h35m each|

SO-A and SO-B together want 110 units on a day that holds 93, which is the contention in CASE-1, whereas SO-LATE needs nearly 17 hours against a Wednesday deadline, so it cannot be met on time.

*Production Runs*
||Fixture||Product||Quantity||Status||Notes||
|PR-OPEN|DSK15144-00|50|Approved|starts Mon 2 Mar; 4h25m on LAM|
|PR-OVERDUE|DSK15144-00|50|Approved|estimated start Mon 23 Feb; never started|
|PR-RUNNING|DSK15144-00|100|Running|40 produced; LAM step complete with actual setup recorded|
|PR-BARE|DSK15144-00|100|Running|nothing produced, nothing issued, no actual setup recorded|
|PR-ISSUED|DSK15144-00|100|Approved|150 of its 300 core veneers already issued|

*Purchase Orders and approved Requirements*
||Fixture||What||Notes||
|PO-50|Approved Purchase Order, 50 DCGAPSOSR12|estimated delivery Wed 11 Mar|
|REQ-INT-50|Approved INTERNAL_REQUIREMENT, 50 DCGAPSOSR12|required by Fri 13 Mar|
|REQ-PROD-50|Approved PRODUCT_REQUIREMENT, 50 cross-ply veneer|required by Wed 11 Mar|

CASE-21 uses SO-400 against PO-50, PR-OPEN and REQ-INT-50 i.e., 400 wanted, 150 already coming, and 250 proposed.
h3. Reading the results

Exact timestamps depend on how the engine packs the work into each day, so check the relationships rather than matching the strings i.e., no day booked beyond its hours, the earlier demand served first, a parent starting no earlier than its components finish, and the quantity kept whole with the shortfall recorded separately. Those would hold irrespective of the run date, and the figures above are chosen so that each case crosses the threshold it is testing.
h3. A. Competing for machine time

*CASE-1 — Two orders want the same machine on the same day.*
*Setup:* SO-A (60 units) and SO-B (50 units), both wanted the same Wednesday, while AMC makes 93 a day.
*Expected:* the earlier-dated order would get served first and take what it needs, and the second one would get planned into the hours that remain and spill onto the previous working day. Neither day would be booked beyond 8 hours.
*Why:* today both would get planned onto the same day, 110 units into a 93-unit day, with nothing to warn the planner.

*CASE-2 — Work already on the floor keeps its machine time.*
*Setup:* PR-OPEN (50 decks, approved, starting Monday), and then a Sales Order needing deck time.
*Expected:* the released run would keep Monday, and the new demand would get planned around it, as the plan never moves or rewrites work already on the floor.

*CASE-3 — An overdue run still occupies the machine.*
*Setup:* PR-OVERDUE, same as PR-OPEN but its planned start is a week in the past and it never started.
*Expected:* its remaining hours would be treated as occupying the machine from today onwards, and not from a date in the past. If the run happens after the shift has closed, that work would land on the next working day.
*Why:* the work still has to happen, and pretending it happened last week would hand its hours to something else.

*CASE-4 — Only the work genuinely left gets booked.*
*Setup:* PR-RUNNING i.e., 100 decks, 40 already produced, with the LAM step finished and its setup recorded.
*Expected:* only 60 units of the remaining work would get booked, the completed step wouldn't be booked again, and the setup wouldn't be charged a second time.
*Why:* re-booking finished work takes hours back off the shop floor and pushes everything behind it later.

*CASE-5 — Parts already issued to a run don't get demanded again.*
*Setup:* PR-ISSUED i.e., a run needing 300 core veneers, with 150 of them already issued to it.
*Expected:* demand would get raised for the outstanding 50 only, and a part fully issued wouldn't raise any.
*Why:* the plan only reorders what has not yet physically left the store.

*CASE-6 — Setup gets charged until the floor says it was done.*
*Setup:* PR-BARE, marked running, but with nothing produced, nothing issued and no actual setup recorded.
*Expected:* its 15 minutes of setup would still get charged.
*Why:* marking a job as started is just an intention, whereas recorded setup time is a fact, and releasing the setup on a status alone would hand the plan 15 minutes that the machine has not actually saved.

*CASE-7 — Late rather than short.*
*Setup:* SO-LATE i.e., 200 skateboards wanted the first Wednesday, roughly 17 hours of AMC time.
*Expected:* the plan would schedule it forward across the following working days, mark it Late, and keep the original Wednesday date so that the delay can be measured.
*Why:* a planner needs to know both what was promised and what is achievable.

*CASE-8 — Short when the horizon genuinely cannot fit it.*
*Setup:* SO-BIG i.e., 600 skateboards, run with a 5-day horizon, which holds about 465.
*Expected:* the Requirement would show the full quantity ordered, marked Short, recording how many units cannot be covered, and it would never get quietly reduced.
*Why:* a Requirement cut down to what fits looks achievable and hides the gap.

*CASE-9 — Even a demand that cannot be started at all gets shown.*
*Setup:* so little time remains that the 15-minute setup alone won't fit.
*Expected:* a Requirement would still get created, for the whole quantity, entirely unmet.
*Why:* dropping the row would remove the demand, and everything beneath it, from the plan.

*CASE-10 — A component's minimum order quantity doesn't inflate what is made.*
*Setup:* stickers DCSPPUSA, whose supplier SUP_003 requires 500, needed by a demand for far fewer.
*Expected:* the purchase would get raised to 500, because that is the smallest order the supplier accepts, while what is made wouldn't follow it i.e., the production Requirement would stay at what demand and capacity justify, and the balance of the stickers would get recorded as stock available to the later demands.
*Why:* a purchasing term constrains buying, and not building, so raising a production Requirement to meet a component's minimum would manufacture goods that no demand asked for. See CASE-26 for the same minimum viewed from the purchasing side.

*CASE-11 — Work that cannot finish inside the horizon books nothing.*
*Setup:* a demand whose work cannot complete before the horizon ends, however it gets placed.
*Expected:* no machine time would get reserved, and the Requirement would carry its full quantity as unmet. Its dates would run from the moment its machine next has time, and not from the start of the run, and never across the hours another order already holds. Where the window is full throughout, that moment would fall beyond the window.
*Why:* reserving hours for work that cannot happen denies them to the work that can. And a date anchored to the run's own start would say that the order could begin today when the machine is committed for the whole window, which reads as two orders starting together on one machine.

*CASE-12 — A machine waiting for parts is held, and not given away.*
*Setup:* a high-priority order that cannot start until its veneers arrive at midday, on an otherwise empty machine.
*Expected:* the morning would be held, and a lower-priority order wouldn't get planned into the hours the waiting order will occupy.

*CASE-13 — A small job can use that idle morning, whereas a large one cannot.*
*Setup:* continuing CASE-12, two candidates i.e., one that would finish before midday and one that wouldn't.
*Expected:* the small one would run in the gap, and the large one would get planned after the waiting order. Neither of them gets split around it, and the waiting order doesn't move.
*Why:* splitting a job means setting the machine up twice for one setup's worth of planned time.

*CASE-14 — Two orders due the same day: the later one finishes where the earlier one starts.*
*Setup:* two Sales Orders for the finished deck on the same day, with stock at every level below so that only machine time binds: 60 units due 16:00 and 50 units due 16:30. At 15 minutes of setup plus 5 minutes a unit, that is 315 and 265 minutes of work against a 480-minute shift.
*Expected:* the 60-unit order would get planned from 10:45 to 16:00, finishing exactly when it is due, and not at 13:45. The 50-unit order cannot fit in the 30 minutes left after it, so it would finish at 10:45 where the first order begins, and reach back into the previous day for the balance i.e., 14:20 to 16:30 there, and then 08:30 to 10:45 on the due day. Both would keep their full quantity and neither would be late, while thirty minutes of the due day would be left unused.
*Why:* finishing when due rather than as early as possible is what keeps finished stock off the shelf. The second order shows the cost of never splitting a job honestly, as the 30-minute remnant cannot be used by a 265-minute order and the plan accepts that idle half-hour rather than breaking the order in two and paying its setup twice. It also shows that reaching into an earlier day takes the END of that day, so the work stays one unbroken block across the overnight break rather than two pieces with a day between them.

*CASE-15 — A run whose last machine step is finished still counts as supply.*
*Setup:* an open Production Run for the finished deck with 60 units still to produce, whose LAM step is marked finished, leaving only the machine-less trim, and a demand for 300 units.
*Expected:* a Requirement for 240 units i.e., the 60 still owed by the run would get credited as supply, dated from the run's own estimated finish, and marking the LAM step finished shouldn't change the number.
*Why:* supply is owed output, and not reserved machine hours. Before this was fixed, finishing the last machine step made the run's remaining units disappear from the plan and the Requirement jumped from 240 to 300, so a run looked further from delivering precisely because it had got closer.

*CASE-16 — Two orders for one product on one machine don't share a start date.*
*Setup:* two approved Sales Orders for the deck DSK15144-00 at MFG_PLANT, with no Production Runs and stock at every level below, so machine time is the only constraint: 400 units due today at 16:30, and 50 units due the following Friday at 16:30. A six-day window over LAM's Mon/Wed/Fri calendar, so three working days.
*Expected:* the earlier-due order would get sized to what the window holds and take all of it, keeping its full 400 with the balance unmet. The later-due order can be given nothing i.e., 50 units, wholly unmet, with both orders Late and Short. Their start dates would differ, as the second one gets dated from when LAM next has time, after the first order's committed window, and its finish wouldn't precede the first order's.
*Why:* capacity claimed by an earlier demand is gone, so a later one has to be dated somewhere else. Two identical start dates would say that both orders begin together on one machine, and a blocked order finishing before the order that took the capacity would invert the reading entirely. Both figures were right, and only the dates were wrong, and wrong in the optimistic direction.
*Note on the arithmetic:* the buildable quantity depends on the hour the run gets submitted, because the first day is trimmed to the shift time still ahead of it. A run at 08:30 has 1,440 minutes over three days and builds 285, whereas a run at 16:05 has 984 and builds 193, so derive the expected figures from the capacity remaining at the run instant rather than assuming a start-of-day run.
h3. B. Calendars, shifts and dates

*CASE-17 — A run started late in the day.*
*Setup:* the plan gets run at 16:46, after LAM's 16:30 close.
*Expected:* nothing would get planned into that day, and the first work would land on the next day LAM works. If run at 15:00 instead, only the remaining 90 minutes would be available.
*Why:* hours that have already passed are not capacity.

*CASE-18 — Work spanning a shift close and non-working days.*
*Setup:* 10 hours of deck work starting Monday morning on LAM, which works Monday, Wednesday and Friday.
*Expected:* 8 hours on Monday, and the remaining 2 from Wednesday's opening, as the work would never land in the evening or on a day the machine doesn't run.

*CASE-19 — Supplier lead time gets counted in working days.*
*Setup:* cross-ply veneer DRVM60361050XA with standardLeadTimeDays set to 7 on supplier SUP_001.
*Expected:* the arrival date would be that many of the supplier's working days ahead, so it lands later than a plain calendar count, and the same date would decide whether the shortage is a delay or a cap.

*CASE-20 — Holidays and shutdowns count on every date, inside the window and outside it.*
*Setup:* a machine working Monday, Wednesday and Friday, with an exception day recorded against its calendar closing it for a week, and a demand needing several days of that machine, run so that the work reaches back before the run date and forward past the planning window.
*Expected:* both the achievable dates and the ideal need dates would step over the closed week and over the non-working weekdays, and the number of days walked back would match the machine's real working pattern rather than a count of calendar days. A holiday falling on the run's own day would close what is left of that day too.
*Why:* a calendar means the same thing whichever side of the planning window a date falls on. Previously the window was expanded from the real calendar (exception days included), while anything outside it used a working week inferred from that window, which silently dropped holidays and shutdowns and treated a three-day-a-week machine as working seven. The effect was to walk back too few days and report parts as needed later than they truly were, understating how late an order already was.
h3. C. Stock and purchasing

*CASE-21 — What is already coming gets counted before anything is proposed.*
*Setup:* SO-400, plus PO-50, PR-OPEN and REQ-INT-50 i.e., 150 units already coming.
*Expected:* the plan would propose 250, and not 400, with each source dated when it can genuinely be there.

*CASE-22 — Supply reduces a Requirement even when no machine is free.*
*Setup:* demand for 50 decks, with 50 already arriving and LAM fully booked.
*Expected:* no Requirement would get raised, as using stock that already exists needs no machine time.

*CASE-23 — A covered level pulls no components.*
*Setup:* SO-100 covered entirely by approved Production Run Requirements for the same 100 units.
*Expected:* no deck, sticker, warranty-card or transfer demand would get raised beneath it.
*Why:* ordering deck veneers for skateboards nobody is going to build wastes money and consumes the deck supply.

*CASE-24 — Stock gets counted once.*
*Setup:* STK-DECK-40 against a demand for 100 decks.
*Expected:* a Requirement for 60, never 20 (counted twice) and never 100 (not counted).

*CASE-25 — A part that arrives in time delays, whereas one that cannot caps the quantity.*
*Setup:* STK-VENEER-10 and a deck demand for 100, run with a 30-day horizon. Cross-ply veneer arrives in 7 working days, whereas core veneer takes 45 and cannot arrive inside the window.
*Expected:* the cross-ply and face veneers would get ordered and the decks would simply wait for them, flagged late. The core veneer would cap the production at the 10 units on hand (3 per deck, so 3 decks), and the shortage would get recorded against it. The same would hold for a part whose only supplier's terms have expired, as it cannot be bought, so it would cap the quantity too.
*Why:* a part that arrives late is a scheduling problem, whereas a part that cannot arrive at all is a hard limit on what the factory can promise.

*CASE-26 — A minimum order quantity's surplus becomes usable stock.*
*Setup:* two small demands for stickers DCSPPUSA, with a minimum order of 500.
*Expected:* one order for 500, and not two, as the surplus would be treated as stock the second demand consumes.

*CASE-27 — A purchase that should already have been placed.*
*Setup:* the same 7-day part, needed sooner than 7 working days from now.
*Expected:* the purchase would start now, get flagged late, and couldn't arrive before its computed date.
h3. D. Priority and firm commitments

*CASE-28 — Priority classes decide who gets served first.*
*Setup:* on one day, deck demand from a Sales Order, from PR-OPEN, from REQ-INT-50, and from the 50-unit Minimum Stock level, together more than LAM can make.
*Expected:* served in that order irrespective of their dates, each one taking only what the class above leaves, and the stock top-up absorbing the shortfall.
*Why:* a customer order shouldn't lose machine time to a stock top-up that could wait.

*CASE-29 — Approved Requirements survive and count as supply.*
*Setup:* REQ-PROD-50 and REQ-INT-50 exist, approved, when the run starts.
*Expected:* neither of them would be deleted, both would reduce what gets proposed, and the approved Production Run Requirement would also take its machine time.
*Why:* approving is how a planner locks a part of the plan, so a rerun shouldn't undo it.

*CASE-30 — An approved Production Run Requirement's parts become demand.*
*Expected:* its components would get demanded at its own priority (above stock top-ups, below the work already on the floor), due when the order itself is due.

*CASE-31 — Forecast gets planned last.*
*Expected:* Forecast demand would get planned after every other class and consume only what they leave, ordered within itself by period.
h3. E. Multi-level products

*CASE-32 — A late deck pushes the skateboard.*
*Setup:* a Sales Order for finished skateboards where the decks cannot be made in time.
*Expected:* the decks would take the first capacity available, and the skateboards would get moved to start at or after the decks are finished, with both flagged late.

*CASE-33 — A skateboard never starts before its decks exist.*
*Expected:* for every finished-goods Requirement in the plan (and not only for the flagged ones), its start would be at or after the receipt of the deck batch feeding it i.e., the moment the units that genuinely land are ready. Where that receipt falls outside the consuming machine's working hours, the start would roll forward to the next moment that machine can work.

The floor is the receipt, and not the Requirement's required-by date. Those are two different dates, and deliberately so, as a required-by date covers the whole order including the units that are never made, whereas a receipt covers only what actually arrives. A level can consume only what arrives, so flooring it on the whole-order date would hold it back for units that are never going to exist.

*Worked example.* A deck Requirement for 100 with 7 unmet lands 93 decks at Friday 16:30, and carries a required-by of the following Monday 09:05, which is the notional finish of all 100. The skateboard feeding off it gets floored on the Friday receipt, and since Friday 16:30 is the assembly machine's shift close, its start would roll to Monday 08:30. That start is before the deck's required-by date and is nonetheless correct, because every deck that will ever exist was ready on Friday.

*Why it is worth stating:* read as the whole-order date, the same plan looks like an assembly starting 35 minutes before its components are complete, whereas read as the receipt, it is a level starting as soon as its parts are genuinely on hand. So in short, the plan is the same either way, and only the sentence describing it was ambiguous.

*CASE-34 — A deck shortage caps the skateboards.*
*Setup:* SO-100, with LAM's earlier days booked so that only 60 decks can be made in time.
*Expected:* the skateboard run would get sized at 60, while its Requirement would still record the full 100 with 40 unmet, and the machine time on AMC would get booked only for what is going to be built, with the rest released.
*Why:* the plan should never ask for finished goods that its components cannot support.

*CASE-35 — Stock counts toward that cap.*
*Setup:* same as CASE-34, plus STK-DECK-20.
*Expected:* 80 skateboards buildable and 20 unmet, and where several components fall short, the tightest one would decide.

*CASE-36 — A late part in one branch doesn't stall another.*
*Setup:* cross-ply veneer delayed by its lead time, while the stickers and warranty cards are in stock.
*Expected:* only the levels that actually consume the late veneers would get pushed out.

*CASE-37 — Two orders wanting the same part.*
*Setup:* SO-PAIR-1 and SO-PAIR-2, 50 each, with the decks needed the same day.
*Expected:* each one would get planned through its own tree, and they would compete for the same veneers and the same LAM hours in priority order, so whichever gets served second would wait and get flagged late.
h3. F. Machine groups

*CASE-38 — Jobs on a group run side by side.*
*Setup:* a group of two 8-hour machines, one of them already committed for a full day.
*Expected:* a new job would run that same day on the free machine, at that machine's pace, and not queue behind the committed one, whereas a third job would find both machines full and move to the previous working day.
*Why:* two machines can genuinely run two jobs at once, and reporting the second one as queued understated what the plant could deliver and dated it later than reality.

*CASE-39 — A job too big for one machine gets divided, and pays a setup per piece.*
*Setup:* a job needing 12 hours on a group of two 8-hour machines, with only that day available.
*Expected:* divided between the two machines (as no single machine can deliver it that day), with each piece carrying its own setup, so the plan would consume slightly more machine time than the job's 12 hours. Both pieces would run concurrently and the job would get delivered on time. A job whose per-unit rate is not on file wouldn't be divided, as a piece of it couldn't be costed, so it would stay one block and take the delay.
*Why:* the second setup is real and the plan has to charge it. The alternative i.e., spreading every job over every machine, would pay that setup on jobs that never needed it and leave no machine free for the next job.

*CASE-40 — Members with different working weeks.*
*Setup:* a group whose members work Monday–Friday and Monday/Wednesday/Friday respectively, on a Tuesday.
*Expected:* the group would offer one machine's shift that day i.e., 8 hours of work in 8 hours, and not half of two.

*CASE-41 — A step naming one machine.*
*Expected:* it would run at that single machine's pace, its hours would come out of the group's pool so that nothing else can claim them, and a second step naming the same machine would get pushed out even though the group still shows capacity, because that capacity belongs to the other machine.
h3. G. Routing steps

*CASE-42 — A step with a time but no machine.*
*Setup:* the deck routing's "Shape Deck" step, which has no machine assigned.
*Expected:* it would contribute its time to the schedule but consume no machine capacity, so a later job on LAM would see only the LAM step's hours taken.

*CASE-43 — A step with a machine but no times, and a step with neither.*
*Expected:* both of them are effectively instantaneous, and neither of them would limit the quantity.
*Why:* this is the existing OFBiz arithmetic and it shouldn't change.
h3. H. Dates, flags and modes

*CASE-44 — The required-by date covers the whole order.*
*Setup:* a run that can make only a part of its quantity.
*Expected:* its required-by date would be when all of it gets finished, even past the horizon, while the receipt would be dated when the units that genuinely land are ready. The receipt is also the date a consuming level gets floored on, so a level would start when its parts genuinely arrive rather than when the order that produced them notionally completes (see CASE-33).

*CASE-45 — Every Requirement records the date it was measured against.*
*Expected:* present on every Requirement, flagged or not i.e., the order's own date for a finished good, and the consuming step's start for a component.

*CASE-46 — No date ever gets set in the past.*
*Expected:* no Requirement would start before the moment the run began, under any combination of overdue supply, late purchases and elapsed shifts.

*CASE-47 — Lateness gets flagged where it happens and carried upward.*
*Expected:* each delayed batch would carry its own flag, so a delayed deck is visible to whoever schedules LAM, and the finished skateboard would be late if anything feeding it was.

*CASE-48 — Both plans coexist.*
*Expected:* a Constrained and an Unconstrained run would each keep their own results, and re-running one would leave the other one (and every approved Requirement) untouched, so that the two can be compared side by side.

*CASE-49 — The existing MRP stays unaffected.*
*Expected:* with the Constrained mode unused, the results would be identical to the current release.
h3. I. Where a product is stocked

R20. Each case builds a three-level product of its own i.e., an assembly made from a sub-assembly made from a part, stocked at one Facility only.

*CASE-50 — The Unconstrained mode reports a made product the Facility doesn't stock, and not only a bought one. (R20)*
*Setup:* a product stocked at FAC_A, made from a sub-assembly that is made from a part, while neither the sub-assembly nor the part is stocked anywhere, and its Minimum Stock at FAC_A is the demand. Run the Unconstrained mode.
*Expected:* one Requirement, for the stocked product, and the plan would carry an error event for the part and for the sub-assembly, each one naming FAC_A.
*Why:* stock OFBiz reported only the part. The sub-assembly got a proposed receipt with no Requirement behind it and nothing to say why, which is how a skateboard sold from WebStoreWarehouse (a warehouse that doesn't stock it) disappeared from the plan without any notice.

*CASE-51 — The Constrained mode raises no Requirement for a level the Facility doesn't stock. (R20)*
*Setup:* same as CASE-50, run in the Constrained mode.
*Expected:* the same Requirement and the same two error events, as the stocked product would still get planned, while the sub-assembly and the part would get proposed receipts with no Requirement.
*Why:* before R20, the Constrained mode raised Requirements for all three levels at a Facility that stocks only one of them, where the classic engine raised one.

*CASE-52 — The Constrained mode plans nothing for a demand the Facility doesn't stock. (R20)*
*Setup:* the product of CASE-50, not stocked at FAC_B, and an approved Sales Order for six of it shipping from FAC_B.
*Expected:* no Requirement at FAC_B for the product or anything in its bill of materials. The Sales Order demand would stay on record and an error event would name the product and FAC_B, while no proposed receipt or component demand would get written for it.
*Why:* planning the order would explode its bill of materials at FAC_B and book machine time there i.e., work FAC_B is never going to do, which would then crowd out the work it would do. Serving FAC_B's order from the Facility that makes the product is an inter-facility transfer, which this work doesn't attempt.
h2. What the plan does not attempt
 * *Optimizing the order of work within a day.* The plan does produce a sequence (every job carries a start and finish time, and jobs queue behind one another on a machine), but that order follows the planning priority. It isn't rearranged to reduce changeovers, and sequence-dependent setup times are not modeled, so a scheduler could still improve on it.
 * *Recording which machine of a group.* The plan places each job on one machine of a group, but that choice is internal to the run, and since Requirement has no machine field, it isn't stored.
 * *Carrying a shortfall onto a later order.* None is needed, as the shortfall stays visible on the Requirement that could not cover it.
 * *Part-finished operations.* A step half way through would be treated as not started, because the data records no partial progress per step, which overstates the time left rather than understating it.
 * *Merging demands for the same product into one Requirement.* This is deliberate, as separate Requirements are what let every batch be traced to the order it serves. It does mean more rows than a consolidating planner would produce, and that a setup gets charged per Requirement rather than once for a combined batch.
 * *Exception WEEK patterns.* TechDataCalendarExcDay (individual holidays, shutdowns and one-off closures) gets honored on every calendar the plan walks, whereas TechDataCalendarExcWeek, a temporary replacement week pattern, doesn't. This is inherited, as stock OFBiz honors neither, and the classic Unconstrained run ignores exception days entirely.
 * *Exception days on the purchasing and shipping legs.* Supplier lead time gets walked through the supplier's own calendar and the outbound leg through the Facility's SHIPPING calendar, each one correctly separate from the machine's. But those two walks use the stock OFBiz calendar helpers, which don't read exception days, so a supplier's own works holiday wouldn't extend its lead time. This is again inherited rather than introduced.
 * *One Facility per run, and the plan gets rebuilt in full each time rather than adjusted.* Planning a Facility Group is OFBIZ-13630.
 * *Make or buy by Facility.* Whether a product is made is decided by the product (a bill of materials and no main supplier) and not by the Facility, so a Facility that stocks a made product would plan to make it there.
 * *Supplying one Facility from another.* A demand at a Facility that doesn't stock the product gets reported and not planned (R20), and planning it as a transfer from the Facility that makes it is OFBIZ-7356.
 * {*}Alternative machines and routings, and lot sizing beyond a minimum order quantity{*}, are not modeled.

h2. Behavior proven below the case level

Two rules are enforced but cannot be exercised through an MRP run's output, because reproducing them needs control of the system clock or of the internal state rather than of the data. They are covered by unit tests, and are recorded here just so that they don't get mistaken for gaps:
 * *Day buckets across a daylight-saving change.* Each bucket maps to a real local day, so a local day of 23 or 25 hours wouldn't make the later buckets drift.
 * {*}A completion never precedes its own start{*}, and work never lands in an evening or on a non-working day, whatever the calendar arithmetic is asked to walk.

h2. Not yet implemented - Candidate for Next Phase
 * A screen to set a Facility's Default MRP Mode, which today has to be set as data.
 * Carrying the Late and Short context onto a Production Run when a Requirement gets converted.
 * The explanation of what limited a demand appears in the run's messages, and not on the Requirement.
 * Inter-facility transfer planning (OFBIZ-7356).
 * A declared maximum number of splits per routing step, for a shop that needs an order divided even when one machine could deliver it.
 * Recording which machine on the Production Run created from a Requirement.
 * A separate capacity-leveling pass i.e., balancing across the whole order book and re-sequencing to close the gaps. This is deliberately out of scope, as this engine plans one order at a time in priority sequence and never re-plans a placed order.
 * Piece-to-piece pipelining between routing steps i.e., where step 1 gets divided, step 2 would chain on the latest piece's finish rather than getting fed by the first piece to complete.

(Configurable planning parameters are OFBIZ-13628, and planning a Facility Group is OFBIZ-13630.)

  was:
OFBiz MRP engine currently plans with an infinite-capacity assumption. When it back-schedules a proposed production run from its required-by date, it only checks the machine's working calendar — which weekdays the machine is open and for how many hours. It never checks how much of that time is already taken, either by existing production runs on the shop floor or by other orders proposed within the same MRP run.

As a result, every proposed order sees every machine as completely free. Two runs each needing 6 hours on a machine with an 8-hour day are both planned onto the same day — 12 hours of work into an 8-hour window — with no warning. The plan looks feasible on paper but is impossible to execute, and planners must find and resolve these overloads manually.

MRP should offer an optional mode to trigger a *_Constrained Capacity_* run with finite-capacity planning i.e., treat each machine's daily capacity the way MRP already treats material — as a limited resource that each planned order consumes — so that later orders only get what remains, demand is prioritized by date (the same principle used for material netting), and orders that cannot fit are visibly delayed (flagged {*}Late{*}) or recorded as short — the requirement keeps its full quantity and stores the part capacity or material cannot cover (flagged {*}Short{*}) — rather than silently overbooked.

Not to mention, the *default ({_}Unconstrained Capacity{_})* run would remain untouched, so existing users see zero change unless they opt in for Constrained run or make it default (check "Configuring Default MRP Mode" section below).

The ‘{*}Constrained Capacity{*}’ mode is *Optional* and disabled by default, MRP defaults to ‘Unconstrained Capacity Plan’ mode which behaves exactly as today.
h2. *High Level Feature Expectation*

{*}_Terminology_{*}{_}:{_}
 * _a "proposed production run" is a planned manufacturing order that the MRP run creates;_
 * _a "committed production run" is an existing approved or running production run on the shop floor; the "originating demand" is what triggered a proposal — typically a customer sales order, or a stock level falling below its configured minimum._

How the MRP run works when the new *Constrained Capacity* mode is switched on:
 * *Committed work is placed first.* Open and running production runs are laid into capacity before any proposal is sized, whatever their dates, and MRP never moves or modifies one. A proposal never takes hours a committed run holds, so no overlap arises and nothing needs to be resolved by hand afterwards.
 * *Competing demand is served in priority classes, then by date* — sales orders, then the parts of work already released, then minimum stock, then forecast.
 * Orders are scheduled backward from their due date; if capacity forces the start into the past, the order is scheduled forward instead and marked {*}Late{*}.
 * If an order cannot be completed inside the planning horizon, its requirement keeps the *full* quantity and records how much cannot be covered — up to all of it — and is marked {*}Short{*}. What can be made is quantity minus unmet quantity; the quantity is never quietly reduced.
 * *Knock-on effects run down the product tree.* A delayed component pushes its parent's start, so no assembly is planned to start before its parts are ready, and a short component caps what the parent can build. Nothing is carried onto a later order for the same product: the shortfall stays on the requirement that could not cover it.
 * {*}Late/Short indicators{*}, the date the plan was measured against and any unmet quantity are stored on the requirement and shown on the requirement screens. What limited a demand — the bottleneck machine or part — is named in the run's messages.

*Scenario Planning: Two plans side by side - Constrained and Unconstrained modes*

Each MRP run can be executed in one of two modes — Constrained Capacity (finite capacity run, this feature) or Unconstrained Capacity (infinite capacity run- today's behaviour) — and everything a run produces is stamped with its mode. A new run replaces only its own mode's results: its MRP events and its unapproved requirements. The other mode's plan survives untouched. This lets planners keep both pictures at once — what demand truly wants (unconstrained) next to what capacity can deliver (constrained) — and read the gap between them as the measurable cost of the capacity constraint.

*Configuring Default MRP Mode*

Each facility can carry a *Default MRP Mode* ({{{}Facility.defaultMrpModeEnumId{}}}). A run started without a mode — every scheduled run, for instance — plans in that facility's default, and a facility with no default plans Unconstrained, exactly as today. There is not yet a screen to set it: it is set as data.

Both modes propose ordinary requirements ({{{}REQ_PROPOSED{}}}), and either can be approved. The facility's default mode is its {*}plan of record{*}: that is the plan to approve from. The other mode's plan is for comparison, and is replaced by that mode's next run. To make the other mode's plan the live one, change the facility's Default MRP Mode and re-run MRP, so the live plan is regenerated against everything that has changed since.

*_A facility plans only what it stocks_*

Both modes raise requirements only for products with a {{ProductFacility}} row at the facility being planned. Supply needed for anything else is reported on the plan — "No ProductFacility record for [facility]; no requirement created." — whether the product is bought or made. The constrained mode does not plan such a demand at all, so it never books machine time for work the facility will not do. Supplying one facility's demand from another is inter-facility transfer planning (OFBIZ-7356).

*_Related work_* 
 * *[planning-rules ticket] — MRP planning rules.* Makes the order of the demand classes (R3), dated-before-undated within a class, and the use of idle machine time (R11) configurable per facility group, and records on a run header which rule and parameters produced each plan.
 * *[facility-group ticket] — Run MRP for a facility group.* Plans every member of a facility group, one plan per member, in one transaction.

h2. Important Rules

*R1 — Two modes, and the existing one is untouched.* A run is either *Constrained* (capacity is finite) or *Unconstrained* (today's behaviour). Each stamps its own results and replaces only its own, so a planner can hold both and compare what the factory _wants_ against what it {_}can do{_}. With the feature unused, results are identical to the current release.

*R2 — Orders are planned one at a time, in priority order.* Each demand is planned through its whole product tree before the next is considered, all of them drawing on the same pool of stock and machine hours. Whoever is planned first takes what it needs; later demands see what is left. This is what allows every requirement to be traced back to the order it serves.

*R2a — Every demand keeps its own requirement, even for the same product.* Two orders for one product are planned one after the other and recorded as two requirements, never merged into a single larger one. Each carries the quantity, dates and shortfall of the demand it serves, so a planner can always answer which order a proposed batch belongs to. Merging would lose that link, and there would be no way to tell which customer a shortage falls on.

{*}R3 — Priority classes decide who is served first{*}, ahead of dates. Dates order demands _within_ a class.
 # Approved sales orders
 # Parts for production runs already on the floor
 # Parts for approved production run requirements
 # Topping stock back up to its minimum
 # Sales forecast

(Making this order configurable is [planning-rules ticket], which adds R3a to R3c.)

*R4 — Anything already on hand or already coming is counted first.* Stock, approved purchase orders, open production runs and approved requirements all reduce what the plan proposes. Supply that arrives _later_ than wanted still counts — the work waits for it and is flagged, rather than the same material being ordered twice.

*R4a — An open production run delivers what it still owes, whatever state its operations are in.* A run's remaining output counts as supply because output is still owed on it, not because machine time is still booked for it. A run whose last machine step is finished — with only a machine-less step such as a trim or an inspection left — is _closer_ to delivering, so its balance is credited and dated from the run's own finish. The one case that credits nothing is a run whose remaining work cannot be placed inside the planning window at all, because its components cannot arrive in time.

*R5 — A level covered by supply pulls no components.* If the skateboards are already coming, the plan does not order deck materials for them.

*R6 — A shortage is flagged, not hidden.* A requirement always shows the *full* quantity needed. Where capacity or material falls short, it records how much cannot be covered rather than quietly asking for less. What can actually be made is {_}quantity minus unmet quantity{_}.

*R7 — A part that arrives in time causes a delay; one that cannot causes a shortage.* Where a purchase can land inside the planning window, it is ordered and production simply waits — the quantity is unaffected. Only a part that cannot be here in time, because it has no supplier or because its lead time runs past the window, limits how much can be built. Lead time is counted in the supplier's *working* days.

{*}R8 — The required-by date is when the whole order is finished{*}, walked through the working calendar, even if that falls past the horizon. The receipt shows its own, earlier date: when the units that genuinely land are ready.

*R8a — The ideal date assumes material and capacity were fully available, and may fall in the past.* Beside every date the plan can actually achieve, a requirement carries the date it _should_ have had: what the demand would have required if nothing had been short and the machine had been free, measured against each resource's own calendar. It deliberately runs back past today when the demand is already overdue — that is what shows a purchase as late instead of quietly re-baselining it to today. What separates this from the achievable date is {*}load{*}, never the calendar: an ideal date honours working days, holidays and shutdowns exactly as the achievable one does, because a closed day is a fact about the plant and not a queue of other orders.

*R9 — No date is ever set in the past.* A run started at 4 pm does not plan work into that morning. Work that should already have started begins *now* and is flagged late.

*R10 — Work already on the floor is placed first and never moved.* Only what genuinely remains is booked — the outstanding quantity, skipping finished operations, and charging setup only where the floor has recorded it was done.

*R11 — A higher-priority order never suffers so that a lower-priority one can run, and no job is ever split.* Where an order is waiting for its parts, the machine in front of it is held. Lower-priority work may take that time only when *both* hold: it cannot move the waiting order by so much as a minute, and it can be finished *in full* before that order begins. If either fails, the work is planned after the waiting order and the machine simply stands idle — idle time is accepted rather than bought at the cost of the order that was already promised. A job is never broken in two around another, which would mean setting the machine up twice while the plan had paid for one setup.

This prohibition is on splitting a job *in time on one machine* — a job is never broken around other work sitting in the middle of its day. Dividing a job between the *machines* of a group is a different thing, governed by R12, and each piece of a divided job is itself an unbroken block on its own machine.

*R12 — A job runs on one machine of a group, and jobs spread across the group.* Two 8-hour machines offer 16 hours of work in a day, but one job uses one machine and runs at that machine's pace. Where several jobs need the group they are placed on different machines and run {*}at the same time{*}, the emptier machine chosen first. A job too large for any single machine to deliver on time is divided between machines, and {*}each piece pays its own setup{*}. A step may instead name one machine, in which case only that machine is considered.

A job that already meets its date is never divided, even when other machines stand idle: dividing exists to make a date achievable, not to finish early — the same reasoning as R15.

*R13 — A shared calendar is not a shared machine.* Two machines on the same calendar each get their own full hours.

*R14 — Time comes from the estimates; capacity is used only where a machine is named.* A step with a time but no machine still takes that long and consumes no capacity.

*R15 — Capacity is balanced by day, and work is placed to finish when it is due.* ** No machine is booked beyond its hours for a day, and every job carries a start and finish time. A job that can be delivered on time is placed to finish at its due moment, not as early as the machine allows — an order due at 16:00 runs up to 16:00 rather than finishing mid-morning and sitting as stock. Jobs queue behind one another on the same machine, time held in front of an order waiting for parts is not given away, and a smaller job may take that gap only if it finishes before the waiting order begins. An order that cannot be delivered on time is placed as early as it can be instead, since finishing late is the only remaining choice. What the plan does not do is _optimise_ the resulting order — it follows planning priority, not the fewest machine changeovers.

*R16 — Approving a requirement makes it a commitment.* Re-running the plan never deletes or downgrades an approved requirement, and both modes count it as supply.

The number is the master document's; R17 to R19 are on [facility-group ticket].

*R20 — A facility plans only what it stocks.* Both modes raise a requirement only for a product with a {{ProductFacility}} row at the facility being planned. When supply is needed for any other product, the plan records an error event instead — "No ProductFacility record for [facility]; no requirement created." — whether the product is bought or made. In the constrained mode a _demand_ for a product the facility does not stock — a sales order, a forecast, a component of work already released — is kept on record and reported, but not planned: nothing is made or bought for it, so no bill of materials is exploded and no machine time is booked for work that will never be done there. A component the facility does not stock, needed by a product it does, is still planned, because its parent needs it, but raises no requirement.
h1. How Constrained Capacity Mode Works 
 # *Committed work is placed first.* Open production runs are laid into capacity before any proposal is sized — whatever their dates. A committed run scheduled later is not displaced by an earlier-needed proposal, and MRP never reschedules or modifies one.
 # *Demands are served in priority classes, then by date.* Sales orders, then the components of open production runs, then the components of approved production run requirements, then below-minimum stock, then forecast. Dates order demands within a class, not across classes.
 # *Backward scheduling with a forward fallback.* Orders schedule backward from their due date; where capacity would force a start into the past, they are scheduled forward instead and flagged {*}Late{*}.
 # *A shortage is flagged, not shrunk.* Where capacity or material cannot cover the full quantity, the requirement keeps its _net requirement_ and records {{unmetQuantity}} with {*}Short{*}. What can actually be made is {{{}quantity − unmetQuantity{}}}. The quantity is never quietly reduced.
 # *Cascades run down the tree, not across orders.* A delayed component pushes its parent's start; a short component caps what the parent can build. There is no backlog carried onto a later order for the same product — the shortfall already sits on the requirement that could not cover it.
 # *Flags and tracking.* Late and Short indicators, the date the plan was measured against, unmet quantities and the mode persist on requirements; the mode persists on events. They are *not yet* carried onto a production run or purchase order at conversion.
 # *Only what the facility stocks is planned.* A demand for a product with no {{ProductFacility}} row at the facility is recorded and reported, not planned; a component the facility does not stock is still planned for its parent's sake but raises no requirement (R20).
 # *One plan per facility and mode.* A run replaces its own mode's events and unapproved requirements for the facility it plans, and nothing else.

h2. *Entity Model extensions*
h2. *Data model*

Every addition is nullable and inert until used: a database that loads the reference data and never runs the constrained mode plans exactly as before. *No new tables:* capacity bookkeeping during a run is held in memory, and what persists is the plan — requirements and events, as today — carrying the fields below.

*Fields added to existing entities*
||Entity||Field||Type||Meaning||
|{{Requirement}}|{{isLate}}|indicator|Y when finite scheduling pushed completion past the date the demand needed. Rolled up: an end item is late if any run feeding it was.|
|{{Requirement}}|{{isShort}}|indicator|Y when capacity or material cannot cover the full quantity.|
|{{Requirement}}|{{unmetQuantity}}|fixed-point|The part of {{quantity}} that cannot be covered; what can be made is {{{}quantity − unmetQuantity{}}}.|
|{{Requirement}}|{{idealRequiredByDate}}|date-time|The date the demand needed, measured as if material and capacity were free (R8a) — the reference the achievable date is read against. Set on every constrained requirement, flagged or not.|
|{{Requirement}}|{{mrpModeEnumId}}|id|The mode whose run proposed it. Scopes re-initialisation: a run deletes only its own mode's unapproved requirements.|
|{{MrpEvent}}|{{mrpModeEnumId}}|id|The mode whose run wrote it. {{isLate}} already exists on {{MrpEvent}} and is reused; the existing {{facilityId}} is now filled on every constrained event.|
|{{Facility}}|{{defaultMrpModeEnumId}}|id|The facility's default mode, used when a run names none; empty means Unconstrained. Set as data; there is no screen for it yet.|

Index {{REQ_MRPMODE_IDX}} on {{Requirement}} ({{{}facilityId{}}}, {{{}statusId{}}}, {{{}requirementTypeId{}}}, {{{}mrpModeEnumId{}}}) keeps the mode-scoped delete on an index range. The mode fields carry no foreign key; their values come from the {{MRP_MODE}} enumeration.

*Supporting reference data (seed data, not schema)*
 * Enumeration type {{{}MRP_MODE{}}}: {{{}MRP_CONSTRAINED{}}}, {{{}MRP_UNCONSTRAINED{}}}.
 * MRP event types {{INTERNAL_REQ_RECP}} and {{INTERNAL_REQ_REQ}} — the output and the component demand of an approved production run requirement, which the classic engine ignores.
 * Facility calendar type {{{}SHIPPING{}}}, and a {{SHIPPING}} calendar (weekdays from 08:30, eight hours): the default calendar the outbound leg ({{{}ProductFacility.daysToShip{}}}) is walked through.

*What is deliberately not persisted:* the capacity ledger, where each job was placed, which machine of a group it runs on, and the explanation of what limited a demand, which is given in the run's messages.

*Upgrading an existing database.* All columns are nullable, and the entity engine adds them at start-up. Load the seed data ({{{}./gradlew "ofbiz --load-data readers=seed"{}}}) for the enumerations and statuses: without it the MRP Mode lists are empty. 

*Service and screen surface*
 * {{executeMrp}} gains {{mrpMode}} (empty: the facility's default) and {{{}planningHorizonDays{}}}, and delegates a constrained run to the new {{{}executeConstrainedMrp{}}}, which returns {{mrpId}} and {{{}requirementsCreated{}}}.
 * *Run MRP* offers the mode and the horizon. *Find Inventory Event Plan* filters by mode and shows ERROR events as notices. The *requirement screens* filter by mode and show the Late, Short, unmet and ideal-date fields.

*Points that matter functionally:*
 * Capacity bookkeeping happens in working memory for the run and leaves no new tables behind. What persists is the requirements (carrying these fields) and the MRP events.
 * Existing data is untouched. Requirements created before this feature simply have the new fields empty; no migration or cleanup is needed. With the constrained mode unused, a plan is unchanged, except that a made product the facility does not stock is now reported as well as a bought one (R20).
 * Approved requirements are outside all of this. Approval fixes a requirement as a commitment: it is never deleted by any run of either mode and is counted as real supply by both modes.

The flags are durable. They stay on the requirement after the MRP screen is closed and are shown on the requirement screens, which can be filtered by the mode that produced a requirement. They are *not yet* carried onto the production run when a requirement is converted.
h1. Use Cases

Every case is written against the *manufacturing-demo dataset (OFBIZ-13438)* so it can be set up and checked by anyone, and every case is verifiable from the data the run produces — no case depends on reading a log.
h3. The demo scenario in plain terms

A skateboard factory, facility {*}MFG_PLANT{*}, with two machines:
||Machine||Works||Hours||
|*LAM*|Monday, Wednesday, Friday|08:30–16:30|
|*AMC*|Monday to Friday|08:30–16:30|

Two things are made:
 * {*}DSK15144-00{*}, the skateboard {*}deck{*}, made on LAM from veneers and glue.
 * {*}DCGAPSOSR12{*}, the finished {*}skateboard{*}, made on AMC from one deck plus a sticker, a warranty card and a transfer.

Every manufacturing step costs the same in this dataset: *15 minutes to set the machine up, then 5 minutes per unit.* So a full 8-hour day is 480 minutes, less 15 for setup, leaving 465 — {*}93 units a day{*}, on either machine. That single figure explains most of the numbers below.

Bills of material: one skateboard needs 1 deck, 1 sticker, 1 warranty card, 1 transfer. One deck needs 2 face veneers, 2 cross-ply veneers, 3 core veneers and 0.83 of a unit of glue.

Every purchased part has a supplier, a lead time and a minimum order quantity. What separates them is {*}how long they take{*}: a part that can arrive inside the planning window merely delays production, while one whose lead time runs past the window limits how much can be built at all. That distinction drives several cases below.

Minimum stock levels are 100 finished skateboards and 50 decks. The demo seeds no stock or orders, so the cases below assume a small set of transactional data — sales orders, production runs, purchase orders and opening stock — defined once in "The scenario these cases run against" and referenced by name.
h3. Key Terms 
 * *Demand* — something that needs making or buying: a sales order, a min stock, or parts for an order already on the shop floor.
 * *Requirement* — what the plan proposes in response: make this many, by this date.
 * *Committed work* — production runs already released to the floor. The plan works around them.
 * *Late* — the plan cannot deliver by the date asked for. *Short* — it cannot deliver the full quantity.
 * *Horizon/Planning Horizon* (or fence) — how far ahead the run plans. Beyond it, the run says nothing.

h2. The scenario these cases run against

The demo dataset supplies the standing configuration — plant, machines, calendars, routings, bills of material, products and suppliers. The transactional data below completes it. None of it ships with the demo; create it, or load it as a test fixture.

*Reference run date: Monday 2 March 2026, 08:00* — before the shift opens, so the first day is whole. Fixing the run date matters because LAM works Monday, Wednesday and Friday: the same order placed on a Tuesday produces different dates. Working days in the fortnight that follows:
|*LAM*|Mon 2, Wed 4, Fri 6, Mon 9, Wed 11, Fri 13 March|
|*AMC*|Mon 2 – Fri 6, Mon 9 – Fri 13 March|

*Capacity:* 480 minutes a day, less 15 minutes setup, at 5 minutes a unit — {*}93 units per machine per day{*}. A batch of _n_ units occupies {{15 + 5n}} minutes.
h3. Supplier terms

Every purchased component has a supplier record with a lead time and a minimum order quantity. The demo ships minimum order quantities but no lead times, so set {{standardLeadTimeDays}} as below.
||Component||Supplier||Lead time||Minimum order||
|{{DRGAMBSK8II}} glue|SUP_002|3 days|50|
|{{DRVM52105036FAS}} face veneer|SUP_001|7 days|100|
|{{DRVM60361050XA}} cross-ply veneer|SUP_001|7 days|100|
|{{DRVM62105036CA}} core veneer|SUP_001|*45 days*|100|
|{{DCSPPUSA}} sticker|SUP_003|5 days|500|
|{{DRSASOCWC2}} warranty card|SUP_003|5 days|100|
|{{DRTGPSOSR12}} transfer|SUP_003|5 days|100|

Lead times are counted in the supplier's *working* days, so seven of them land later than seven calendar days. {*}Core veneer is the outlier on purpose{*}: at 45 working days it cannot arrive inside a normal planning window, which is how the cases that need a genuinely unobtainable part are built. Everything else can be bought in time and therefore only ever delays production.
h3. Stock on hand at MFG_PLANT
||Fixture||Product||Quantity||
|*STK-DECK-40*|{{DSK15144-00}} deck|40|
|*STK-DECK-20*|{{DSK15144-00}} deck|20|
|*STK-VENEER-10*|face, cross-ply and core veneer|10 each|
|*STK-TRIM*|sticker, warranty card, transfer|500 each|

{{STK-TRIM}} keeps the finished-goods cases about machine time rather than about missing trim; use it wherever a case is not specifically about material shortage.
h3. Sales orders

All approved, for {{{}DCGAPSOSR12{}}}, shipping from MFG_PLANT.
||Fixture||Quantity||Wanted by||Occupies||
|*SO-A*|60|Wed 4 Mar|5h15m on AMC|
|*SO-B*|50|Wed 4 Mar|4h35m on AMC|
|*SO-LATE*|200|Wed 4 Mar|16h55m on AMC|
|*SO-BIG*|600|Fri 6 Mar|more than a 5-day horizon holds|
|*SO-100*|100|Fri 13 Mar|8h35m on AMC|
|*SO-400*|400|Fri 13 Mar|33h35m on AMC|
|{*}SO-PAIR-1{*}, *SO-PAIR-2*|50 each|Fri 13 Mar|4h35m each|

SO-A and SO-B together want 110 units on a day that holds 93 — the contention in CASE-1. SO-LATE needs nearly 17 hours against a Wednesday deadline, so it cannot be met on time.
h3. Production runs
||Fixture||Product||Quantity||Status||Notes||
|*PR-OPEN*|{{DSK15144-00}}|50|Approved|starts Mon 2 Mar; 4h25m on LAM|
|*PR-OVERDUE*|{{DSK15144-00}}|50|Approved|estimated start Mon 23 Feb; never started|
|*PR-RUNNING*|{{DSK15144-00}}|100|Running|40 produced; LAM step complete with actual setup recorded|
|*PR-BARE*|{{DSK15144-00}}|100|Running|nothing produced, nothing issued, no actual setup recorded|
|*PR-ISSUED*|{{DSK15144-00}}|100|Approved|150 of its 300 core veneers already issued|
h3. Purchase orders and approved requirements
||Fixture||What||Notes||
|*PO-50*|Approved purchase order, 50 {{DCGAPSOSR12}}|estimated delivery Wed 11 Mar|
|*REQ-INT-50*|Approved {{{}INTERNAL_REQUIREMENT{}}}, 50 {{DCGAPSOSR12}}|required by Fri 13 Mar|
|*REQ-PROD-50*|Approved {{{}PRODUCT_REQUIREMENT{}}}, 50 cross-ply veneer|required by Wed 11 Mar|

CASE-21 uses SO-400 against PO-50, PR-OPEN and REQ-INT-50 — 400 wanted, 150 already coming, 250 proposed.
h3. Reading the results

Exact timestamps depend on how the engine packs work into each day, so check the *relationships* rather than matching strings: no day booked beyond its hours, the earlier demand served first, a parent starting no earlier than its components finish, quantity kept whole with the shortfall recorded separately. Those hold whatever the run date; the figures above are chosen so each case crosses the threshold it is testing.
h2. A. Competing for machine time

*CASE-1 — Two orders want the same machine on the same day.* 
*_Setup:_* *SO-A* (60 units) and *SO-B* (50 units), both wanted the same Friday. AMC makes 93 a day. 
*_Expected:_* the earlier-dated order is served first and takes what it needs; the second is planned into the hours that remain and spills onto the previous working day. Neither day is booked beyond 8 hours. 
*_Why:_* today both would be planned onto the same day, 110 units into a 93-unit day, with nothing to warn the planner.

*CASE-2 — Work already on the floor keeps its machine time.* 
*_Setup:_* *PR-OPEN* (50 decks, approved, starting Monday), then a sales order needing deck time. 
*_Expected:_* the released run keeps Monday; the new demand is planned around it. The plan never moves or rewrites work already on the floor.

*CASE-3 — An overdue run still occupies the machine.* 
*_Setup:_* *PR-OVERDUE* — as PR-OPEN, but its planned start is a week in the past and it never started. 
*_Expected:_* its remaining hours are treated as occupying the machine from today forward, not from a date in the past. If the run happens after the shift has closed, that work lands on the next working day. 
*_Why:_* the work still has to happen; pretending it happened last week would hand its hours to something else.

*CASE-4 — Only the work genuinely left is booked.* 
*_Setup:_* *PR-RUNNING* — 100 decks, 40 already produced, the LAM step finished and its setup recorded. 
*_Expected:_* only 60 units of remaining work are booked, the completed step is not booked again, and setup is not charged a second time. 
*_Why:_* re-booking finished work takes hours back off the shop floor and pushes everything behind it later.

*CASE-5 — Parts already issued to a run are not demanded again.* 
*_Setup:_* *PR-ISSUED* — a run needing 300 core veneers, 150 of them already issued to it. 
*_Expected:_* demand is raised for the outstanding *50* only. A part fully issued raises none. 
*_Why:_* the plan only reorders what has not yet physically left the store.

*CASE-6 — Setup is charged until the floor says it was done.* 
*_Setup:_* *PR-BARE* — marked running, but nothing produced, nothing issued, no actual setup recorded. 
*_Expected:_* its 15 minutes of setup are still charged. 
*_Why:_* marking a job as started is an intention; recorded setup time is a fact. Releasing the setup on a status alone hands the plan 15 minutes that the machine has not actually saved.

*CASE-7 — Late rather than short.* 
*_Setup:_* *SO-LATE* — 200 skateboards wanted the first Wednesday, roughly 17 hours of AMC time. 
*_Expected:_* the plan schedules it forward across the following working days, marks it {*}Late{*}, and keeps the original Wednesday date so the delay can be measured. 
*_Why:_* a planner needs to know both what was promised and what is achievable.

*CASE-8 — Short when the horizon genuinely cannot fit it.* 
*_Setup:_ SO-BIG* — 600 skateboards, run with a 5-day horizon, which holds about 465. 
*_Expected:_* the requirement shows the *full* quantity ordered, marked {*}Short{*}, recording how many units cannot be covered. It is never quietly reduced. 
*_Why:_* a requirement cut down to what fits looks achievable and hides the gap.

*CASE-9 — Even a demand that cannot be started at all is shown.* 
*_Setup:_* so little time remains that the 15-minute setup alone will not fit. 
*_Expected:_* a requirement is still created, for the whole quantity, entirely unmet. 
*_Why:_* dropping the row would remove the demand — and everything beneath it — from the plan.

*CASE-10 — A component's minimum order quantity does not inflate what is made.*

_{*}Setup{*}:_ stickers {{{}DCSPPUSA{}}}, whose supplier SUP_003 requires 500, needed by a demand for far fewer.

_{*}Expected{*}:_ the purchase is raised to 500, because that is the smallest order the supplier accepts. What is *made* does not follow it — the production requirement stays at what demand and capacity justify, and the balance of the stickers is recorded as stock available to later demands.

_{*}Why{*}:_ a purchasing term constrains buying, not building. Raising a production requirement to meet a component's minimum would manufacture goods no demand asked for. See *CASE-26* for the same minimum viewed from the purchasing side.

*CASE-11 — Work that cannot finish inside the horizon books nothing.*

*_Setup:_*
A demand whose work cannot complete before the horizon ends however it is placed.
*_Expected:_*
No machine time is reserved and the requirement carries its full quantity as unmet. Its dates run from the moment its machine next has time — not from the start of the run, and never across hours another order already holds. Where the window is full throughout, that moment falls beyond the window.
*_Why:_*
Reserving hours for work that cannot happen denies them to work that can. And a date anchored to the run's own start says the order could begin today when the machine is committed for the whole window — which reads as two orders starting together on one machine.

*CASE-12 — A machine waiting for parts is held, not given away.* 
*_Setup:_* a high-priority order that cannot start until its veneers arrive at midday, on an otherwise empty machine. 
*_Expected:_* the morning is held. A lower-priority order is not planned into hours the waiting order will occupy.

*CASE-13 — A small job may use that idle morning; a large one may not.* 
*_Setup:_* continuing CASE-12, two candidates — one that would finish before midday, one that would not. 
*_Expected:_* the small one runs in the gap; the large one is planned after the waiting order. Neither is split around it, and the waiting order does not move. 
*_Why:_* splitting a job means setting the machine up twice for one setup's worth of planned time.

*CASE-14 — Two orders due the same day: the later one finishes where the earlier one starts.*

*_Setup:_*
Two sales orders for the finished deck on the same day, with stock at every level below so only machine time binds: 60 units due 16:00 and 50 units due 16:30. At 15 minutes setup plus 5 minutes a unit, that is 315 and 265 minutes of work against a 480-minute shift.

*_Expected:_*
The 60-unit order is planned 10:45 to 16:00 — finishing exactly when it is due, not at 13:45. The 50-unit order cannot fit in the 30 minutes left after it, so it finishes at 10:45, where the first order begins, and reaches back into the previous day for the balance: 14:20 to 16:30 there, then 08:30 to 10:45 on the due day. Both keep their full quantity and neither is late. Thirty minutes of the due day are left unused.

*_Why:_*
Finishing when due rather than as early as possible is what keeps finished stock off the shelf. The second order shows the cost of never splitting a job honestly: the 30-minute remnant cannot be used by a 265-minute order, and the plan accepts that idle half-hour rather than breaking the order in two and paying its setup twice. It also shows that reaching into an earlier day takes the END of that day, so the work is one unbroken block across the overnight break rather than two pieces with a day between them.

*CASE-15 — A run whose last machine step is finished still counts as supply.*

*_Setup:_*
An open production run for the finished deck with 60 units still to produce, whose LAM step is marked finished, leaving only the machine-less trim. Demand for 300 units.

*_Expected:_*
A requirement for 240 units — the 60 still owed by the run are credited as supply, dated from the run's own estimated finish. Marking the LAM step finished must not change the number.

*_Why:_*
Supply is owed output, not reserved machine hours. Before this was fixed, finishing the last machine step made the run's remaining units disappear from the plan and the requirement jumped from 240 to 300, so a run looked further from delivering precisely because it had got closer.

*CASE-16 — Two orders for one product on one machine do not share a start date.*

*_Setup:_*
Two approved sales orders for the deck {{DSK15144-00}} at MFG_PLANT, no production runs and stock at every level below, so machine time is the only constraint. 400 units due today at 16:30, and 50 units due the following Friday at 16:30. A six-day window over LAM's Mon/Wed/Fri calendar, so three working days.

*_Expected:_*
The earlier-due order is sized to what the window holds and takes all of it, keeping its full 400 with the balance unmet. The later-due order can be given nothing: 50 units, wholly unmet, both orders Late and Short. Their start dates differ — the second is dated from when LAM next has time, after the first order's committed window, and its finish does not precede the first order's.

*_Why:_*
Capacity claimed by an earlier demand is gone, so a later one has to be dated somewhere else. Two identical start dates say both orders begin together on one machine, and a blocked order finishing before the order that took the capacity inverts the reading entirely. Both figures were right — only the dates were wrong, and wrong in the optimistic direction.

*_Note on the arithmetic:_*
Buildable quantity depends on the hour the run is submitted, because the first day is trimmed to the shift time still ahead of it. A run at 08:30 has 1,440 minutes over three days and builds 285; a run at 16:05 has 984 and builds 193. Derive the expected figures from the capacity remaining at the run instant rather than assuming a start-of-day run.
h2. B. Calendars, shifts and dates

*CASE-17 — A run started late in the day.* 
*_Setup:_* the plan is run at 16:46, after LAM's 16:30 close. 
*_Expected:_* nothing is planned into that day; the first work lands on the next day LAM works. Run at 15:00 instead, only the remaining 90 minutes are available. 
*_Why:_* hours that have already passed are not capacity.

*CASE-18 — Work spanning a shift close and non-working days.* 
*_Setup:_* 10 hours of deck work starting Monday morning on LAM, which works Monday, Wednesday and Friday. 
*_Expected:_* 8 hours Monday and the remaining 2 from Wednesday's opening. Work never lands in the evening or on a day the machine does not run.

*CASE-19 — Supplier lead time is counted in working days.* 
*_Setup:_* cross-ply veneer {{DRVM60361050XA}} with {{standardLeadTimeDays}} set to 7 on supplier SUP_001. 
*_Expected:_* the arrival date is that many of the *supplier's working* days ahead, so it lands later than a plain calendar count, and the same date decides whether the shortage is a delay or a cap.

*CASE-20 — Holidays and shutdowns count on every date, inside the window and outside it.*

*_Setup:_*
A machine working Monday, Wednesday and Friday, with an exception day recorded against its calendar closing it for a week. Demand needing several days of that machine, run so that the work reaches back before the run date and forward past the planning window.

*_Expected:_*
Both the achievable dates and the ideal need dates step over the closed week and over the non-working weekdays. The number of days walked back matches the machine's real working pattern, not a count of calendar days.

*_Why:_*
A calendar means the same thing whichever side of the planning window a date falls on. Previously the window was expanded from the real calendar — exception days included — while anything outside it used a working week inferred from that window, which silently dropped holidays and shutdowns and treated a three-day-a-week machine as working seven. The effect was to walk back too few days and report parts as needed _later_ than they truly were, understating how late an order already was.
h2. C. Stock and purchasing

*CASE-21 — What is already coming is counted before anything is proposed.* 
*_Setup:_* {*}SO-400{*}, plus {*}PO-50{*}, *PR-OPEN* and *REQ-INT-50* — 150 units already coming. 
*_Expected:_* the plan proposes {*}250{*}, not 400. Each source is dated when it can genuinely be there.

*CASE-22 — Supply reduces a requirement even when no machine is free.* 
*_Setup:_* demand for 50 decks, 50 already arriving, LAM fully booked. 
*_Expected:_* no requirement is raised. Using stock that already exists needs no machine time.

*CASE-23 — A covered level pulls no components.* 
*_Setup:_* *SO-100* covered entirely by approved production run requirements for the same 100 units. 
*_Expected:_* no deck, sticker, warranty-card or transfer demand is raised beneath it. 
*_Why:_* ordering deck veneers for skateboards nobody will build wastes money and consumes the deck supply.

*CASE-24 — Stock is counted once.* 
*_Setup:_* *STK-DECK-40* against demand for 100 decks. 
*_Expected:_* a requirement for *60* — never 20 (counted twice) and never 100 (not counted).

*CASE-25 — A part that arrives in time delays; one that cannot caps the quantity.* 
*_Setup:_* *STK-VENEER-10* and deck demand for 100, run with a 30-day horizon. Cross-ply veneer arrives in 7 working days; core veneer takes 45 and cannot arrive inside the window. 
*_Expected:_* the cross-ply and face veneers are ordered and the decks simply wait for them, flagged late. The core veneer caps production at the 10 units on hand — 3 per deck, so 3 decks — and the shortage is recorded against it. 
*_Why:_* a part that arrives late is a scheduling problem; a part that cannot arrive at all is a hard limit on what the factory can promise.

*CASE-26 — A minimum order quantity's surplus becomes usable stock.* 
*_Setup:_* two small demands for stickers {{{}DCSPPUSA{}}}, minimum order 500. 
*_Expected:_* one order for 500, not two. The surplus is treated as stock the second demand consumes.

*CASE-27 — A purchase that should already have been placed.* 
*_Setup:_* the same 7-day part, needed sooner than 7 working days from now. 
*_Expected:_* the purchase starts {*}now{*}, is flagged late, and cannot arrive before its computed date.
h2. D. Priority and firm commitments

*CASE-28 — Priority classes decide who is served first.* 
*_Setup:_* on one day, deck demand from a sales order, from {*}PR-OPEN{*}, from {*}REQ-INT-50{*}, and from the 50-unit minimum stock level — together more than LAM can make. 
*_Expected:_* served in that order regardless of their dates, each taking only what the class above leaves. The stock top-up absorbs the shortfall. 
*_Why:_* a customer order should not lose machine time to a stock top-up that could wait.

*CASE-29 — Approved requirements survive and count as supply.* 
*_Setup:_* *REQ-PROD-50* and *REQ-INT-50* exist, approved, when the run starts. 
*_Expected:_* neither is deleted, both reduce what is proposed, and the approved production run requirements also takes its machine time. 
*_Why:_* approving is how a planner locks part of the plan; a rerun must not undo it.

*CASE-30 —* {*}An approved production run requirement's parts become demand.{*}{*}{{*}}
*_Expected:_* its components are demanded at its own priority — above stock top-ups, below work already on the floor — due when the order itself is due.

*CASE-31 — Forecast is planned last.* 
*_Expected:_* forecast demand is planned after every other class and consumes only what they leave, ordered within itself by period.
h2. E. Multi-level products

*CASE-32 — A late deck pushes the skateboard.* 
*_Setup:_* a sales order for finished skateboards where the decks cannot be made in time. 
*_Expected:_* the decks take the first capacity available and the skateboards are moved to start at or after the decks are finished. Both are flagged late.

*CASE-33 — A skateboard never starts before its decks exist.*

_{*}Expected{*}:_ for every finished-goods requirement in the plan, its start is at or after the *receipt* of the deck batch feeding it — the moment the units that genuinely land are ready — and not only for the flagged ones. Where that receipt falls outside the consuming machine's working hours, the start rolls forward to the next moment that machine can work.

_The floor is the receipt, not the requirement's required-by date._ Those are different dates and deliberately so: a required-by date covers the *whole* order including units that are never made, while a receipt covers only what actually arrives. A level can consume only what arrives, so flooring it on the whole-order date would hold it back for units that will never exist.

_{*}Worked example{*}._ A deck requirement for 100 with 7 unmet lands 93 decks at Friday 16:30 and carries a required-by of the following Monday 09:05 — the notional finish of all 100. The skateboard feeding off it is floored on the Friday receipt; Friday 16:30 is the assembly machine's shift close, so its start rolls to Monday 08:30. That start is *before* the deck's required-by date and is nonetheless correct, because every deck that will ever exist was ready on Friday.

_{*}Why it is worth stating{*}:_ read as the whole-order date, the same plan looks like an assembly starting 35 minutes before its components are complete. Read as the receipt, it is a level starting as soon as its parts are genuinely on hand. The plan is the same either way; only the sentence describing it was ambiguous.

*CASE-34 — A deck shortage caps the skateboards.*

_{*}Setup{*}:_ {*}SO-100{*}, with LAM's earlier days booked so only 60 decks can be made in time.

_{*}Expected{*}:_ the skateboard run is sized at 60, while its requirement still records the full 100 with 40 unmet. Machine time on AMC is booked only for what will be built, and the rest is released.

_{*}Why{*}:_ the plan must never ask for finished goods its components cannot support.

*CASE-35 — Stock counts toward that cap.*

_{*}Setup{*}:_ as CASE-34, plus {*}STK-DECK-20{*}.

_{*}Expected{*}:_ 80 skateboards buildable, 20 unmet. Where several components fall short, the tightest one decides.

*CASE-36 — A late part in one branch does not stall another.* 
*_Setup:_* cross-ply veneer delayed by its lead time, while stickers and warranty cards are in stock. 
*_Expected:_* only the levels that actually consume the late veneers are pushed out.

*CASE-37 — Two orders wanting the same part.* 
*_Setup:_* *SO-PAIR-1* and *SO-PAIR-2* — 50 each, decks needed the same day. 
*_Expected:_* each is planned through its own tree; they compete for the same veneers and the same LAM hours in priority order, and whichever is served second waits and is flagged late.
h2. F. Machine groups

*CASE-38 — Jobs on a group run side by side.*

_{*}Setup{*}:_ a group of two 8-hour machines, one of them already committed for a full day.
_{*}Expected{*}:_ a new job runs *that same day* on the free machine, at that machine's pace — not queued behind the committed one. A third job finds both machines full and moves to the previous working day.
_{*}Why{*}:_ two machines can genuinely run two jobs at once. Reporting the second as queued understated what the plant could deliver and dated it later than reality.

*CASE-39 — A job too big for one machine is divided, and pays a setup per piece.*

_{*}Setup{*}:_ a job needing 12 hours on a group of two 8-hour machines, with only that day available.
_{*}Expected{*}:_ divided between the two machines — no single machine can deliver it that day — with each piece carrying its own setup, so the plan consumes slightly more machine time than the job's 12 hours. Both pieces run concurrently and the job is delivered on time. A job whose per-unit rate is not on file is *not* divided: a piece of it could not be costed, so it stays one block and takes the delay.
_{*}Why{*}:_ the second setup is real and the plan must charge it. The alternative — spreading every job over every machine — would pay that setup on jobs that never needed it and would leave no machine free for the next job.

*CASE-40 — Members with different working weeks.* 
*_Setup:_* a group whose members work Monday–Friday and Monday/Wednesday/Friday respectively, on a Tuesday. 
*_Expected:_* the group offers one machine's shift that day — 8 hours of work in 8 hours — not half of two.

*CASE-41 — A step naming one machine.* 
*_Expected:_* it runs at that single machine's pace, its hours come out of the group's pool so nothing else can claim them, and a second step naming the same machine is pushed out even though the group still shows capacity — because that capacity belongs to the other machine.
h2. G. Routing steps

*CASE-42 — A step with a time but no machine.* 
*_Setup:_* the deck routing's "Shape Deck" step, which has no machine assigned. 
*_Expected:_* it contributes its time to the schedule but consumes no machine capacity, and a later job on LAM sees only the LAM step's hours taken.

*CASE-43 — A step with a machine but no times, and a step with neither.* 
*_Expected:_* both are effectively instantaneous and neither limits the quantity. _Why:_ this is existing OFBiz arithmetic and must not change.
h2. H. Dates, flags and modes

*CASE-44 — The required-by date covers the whole order.* 
*_Setup:_* a run that can make only part of its quantity. 
*_Expected:_* its required-by date is when *all* of it would be finished, even past the horizon, while the receipt is dated when the units that genuinely land are ready. The receipt is also the date a consuming level is floored on, so a level starts when its parts genuinely arrive rather than when the order that produced them notionally completes — see CASE-33.

*CASE-45 — Every requirement records the date it was measured against.* 
*_Expected:_* present on every requirement, flagged or not — the order's own date for a finished good, the consuming step's start for a component.

*CASE-46 — No date is ever set in the past.* 
*_Expected:_* no requirement starts before the moment the run began, under any combination of overdue supply, late purchases and elapsed shifts.

*CASE-47 — Lateness is flagged where it happens and carried upward.* 
*_Expected:_* each delayed batch carries its own flag, so a delayed deck is visible to whoever schedules LAM, and the finished skateboard is late if anything feeding it was.

*CASE-48 — Both plans coexist.* 
*_Expected:_* a constrained and an unconstrained run each keep their own results; re-running one leaves the other and every approved requirement untouched, so the two can be compared side by side.

*CASE-49 — The existing MRP is unaffected.* 
*_Expected:_* with the constrained mode unused, results are identical to the current release.
h2. I. Where a product is stocked

R20. Each case builds a three-level product of its own — an assembly made from a sub-assembly made from a part — stocked at one facility only.

*CASE-50 — The unconstrained mode reports a made product the facility does not stock, not only a bought one.* _(R20)_

_{*}Setup{*}:_ a product stocked at FAC_A, made from a sub-assembly that is made from a part; neither the sub-assembly nor the part is stocked anywhere. Its minimum stock at FAC_A is the demand. Run the unconstrained mode.
_{*}Expected{*}:_ one requirement, for the stocked product. The plan carries an error event for the part {*}and for the sub-assembly{*}, each naming FAC_A.
_{*}Why{*}:_ stock OFBiz reported only the part. The sub-assembly got a proposed receipt with no requirement behind it and nothing to say why — which is how a skateboard sold from WebStoreWarehouse, a warehouse that does not stock it, disappeared from the plan without a word.

*CASE-51 — The constrained mode raises no requirement for a level the facility does not stock.* _(R20)_

_{*}Setup{*}:_ as CASE-50, run in the constrained mode.
_{*}Expected{*}:_ the same requirement, and the same two error events. The stocked product is still planned; the sub-assembly and the part get proposed receipts with no requirement.
_{*}Why{*}:_ before R20 the constrained mode raised requirements for all three levels at a facility that stocks only one of them, where the classic engine raised one.

*CASE-52 — The constrained mode plans nothing for a demand the facility does not stock.* _(R20)_

_{*}Setup{*}:_ the product of CASE-50, not stocked at FAC_B, and an approved sales order for six of it shipping from FAC_B.
_{*}Expected{*}:_ no requirement at FAC_B for the product or anything in its bill of materials. The sales-order demand is on record; an error event names the product and FAC_B; no proposed receipt and no component demand is written for it.
_{*}Why{*}:_ planning the order would explode its bill of materials at FAC_B and book machine time there — work FAC_B will never do, which then crowds out the work it will. Serving FAC_B's order from the facility that makes the product is an inter-facility transfer, which this work does not attempt.
h1. What the plan does not attempt
 * *Optimising the order of work within a day.* The plan does produce a sequence — every job carries a start and finish time, and jobs queue behind one another on a machine — but that order follows planning priority. It is not rearranged to reduce changeovers, and sequence-dependent setup times are not modelled, so a scheduler may still improve on it.
 * *Recording which machine of a group.* The plan places each job on one machine of a group, but that choice is internal to the run: {{Requirement}} has no machine field, so it is not stored.
 * *Carrying a shortfall onto a later order.* None is needed — the shortfall stays visible on the requirement that could not cover it.
 * *Part-finished operations.* A step half way through is treated as not started, because the data records no partial progress per step. This overstates the time left rather than understating it.
 * *Merging demands for the same product into one requirement.* Deliberate: separate requirements are what let every batch be traced to the order it serves. It does mean more rows than a consolidating planner would produce, and that a setup is charged per requirement rather than once for a combined batch.
 * *Exception WEEK patterns.* {{TechDataCalendarExcDay}} — individual holidays, shutdowns and one-off closures — is honoured on every calendar the plan walks. {{{}TechDataCalendarExcWeek{}}}, a temporary replacement week pattern, is not. This is inherited: stock OFBiz honours neither, and the classic unconstrained run ignores exception days entirely.
 * *Exception days on the purchasing and shipping legs.* Supplier lead time is walked through the supplier's own calendar and the outbound leg through the facility's SHIPPING calendar, each correctly separate from the machine's. But those two walks use the stock OFBiz calendar helpers, which do not read exception days, so a supplier's own works holiday does not extend its lead time. Inherited rather than introduced.
 * {*}One facility per run{*}, and the plan is rebuilt in full each time rather than adjusted. Planning a facility group is [facility-group ticket].
 * *Make or buy by facility.* Whether a product is made is decided by the product (a bill of materials and no main supplier), not by the facility: a facility that stocks a made product plans to make it there.
 * *Supplying one facility from another.* A demand at a facility that does not stock the product is reported, not planned (R20). Planning it as a transfer from the facility that makes it is OFBIZ-7356.
 * Alternative machines and routings, and lot sizing beyond a minimum order quantity, are not modelled.

h1. Behavior proven below the case level

Two rules are enforced but cannot be exercised through an MRP run's output, because reproducing them needs control of the system clock or of internal state rather than of data. They are covered by unit tests and are recorded here so they are not mistaken for gaps:
 * *Day buckets across a daylight-saving change.* Each bucket maps to a real local day; a local day of 23 or 25 hours does not make later buckets drift.
 * {*}A completion never precedes its own start{*}, and work never lands in an evening or on a non-working day, whatever the calendar arithmetic is asked to walk.

h1. Not yet implemented - Candidate for Next Phase 
 * *A screen to set a facility's Default MRP Mode.* Today it is set as data.
 * Late and Short context is not carried onto a production run when a requirement is converted.
 * The explanation of what limited a demand appears in the run's messages, not on the requirement.
 * *Inter-facility transfer planning* (OFBIZ-7356).
 * *A declared maximum number of splits per routing step,* for a shop that needs an order divided even when one machine could deliver it.
 * *Recording which machine on the production run* created from a requirement.
 * *A separate capacity-levelling pass* — balancing across the whole order book and re-sequencing to close gaps. Deliberately out of scope: this engine plans one order at a time in priority sequence and never re-plans a placed order.
 * *Piece-to-piece pipelining* between routing steps: where step 1 is divided, step 2 chains on the latest piece's finish rather than being fed by the first piece to complete.

(Configurable planning parameters are [planning-rules ticket]; planning a facility group is [facility-group ticket].)

 


> MRP Run - Constrained (Finite Capacity) Mode
> --------------------------------------------
>
>                 Key: OFBIZ-13462
>                 URL: https://issues.apache.org/jira/browse/OFBIZ-13462
>             Project: OFBiz
>          Issue Type: New Feature
>            Reporter: Swapnil Shah
>            Priority: Major
>
> The OFBiz MRP engine currently plans with an infinite-capacity assumption. When it back-schedules a proposed Production Run from its required-by date, it only checks the machine's working calendar i.e., on which weekdays the machine is open and for how many hours, and never checks how much of that time is already taken, either by the existing Production Runs on the shop floor or by the other orders proposed within the same MRP run.
> As a result, every proposed order sees every machine as completely free. For example, two runs each needing 6 hours on a machine with an 8-hour day would both get planned onto the same day (i.e., 12 hours of work into an 8-hour window) without any warning, so the plan looks feasible on paper but is impossible to execute, and planners have to find and resolve these overloads manually.
> MRP should offer an optional mode to trigger a Constrained Capacity run with finite-capacity planning i.e., treat each machine's daily capacity the way MRP already treats material, as a limited resource that each planned order consumes, so that later orders only get what remains, demand is prioritized by date (the same principle used for material netting), and orders that cannot fit are visibly delayed (flagged Late) or recorded as short (flagged Short), with the Requirement keeping its full quantity and storing the part capacity or material cannot cover, rather than silently overbooked.
> Not to mention, the default (Unconstrained Capacity) run would remain untouched, so existing users see zero change unless they opt in for a Constrained run or make it the default (check the "Configuring Default MRP Mode" section below).
> The 'Constrained Capacity' mode is optional and disabled by default, and MRP defaults to the 'Unconstrained Capacity Plan' mode, which behaves exactly as today.
> h2. High Level Feature Expectation
> *Terminology:*
>  * a "proposed Production Run" is a planned manufacturing order that the MRP run creates;
>  * a "committed Production Run" is an existing approved or running Production Run on the shop floor, and the "originating demand" is what triggered a proposal, typically a customer Sales Order or a stock level falling below its configured minimum.
> *How the MRP run would work when the new Constrained Capacity mode is switched on:*
>  # *Committed work gets placed first.* Open and running Production Runs would be laid into capacity before any proposal gets sized, irrespective of their dates, and MRP would never move or modify one. A proposal would never take the hours held by a committed run, so no overlap arises and nothing needs to be resolved manually afterwards.
>  # *Competing demand gets served in priority classes, and then by date* i.e., Sales Orders first, then the parts of work already released, then Minimum Stock, and then Forecast.
>  # *Orders get scheduled backward from their due date.* If capacity forces the start into the past, the order would get scheduled forward instead and marked Late.
>  # {*}If an order cannot be completed inside the planning horizon{*}, its Requirement would keep the full quantity and record how much of it cannot be covered (up to all of it), and get marked Short. What can be made is the quantity minus the unmet quantity, and the quantity never gets quietly reduced.
>  # *Knock-on effects run down the product tree.* A delayed component would push its parent's start, so no assembly gets planned to start before its parts are ready, and a short component would cap what the parent can build. Nothing gets carried onto a later order for the same product, as the shortfall stays on the Requirement that could not cover it.
>  # {*}Late/Short indicators{*}, the date the plan was measured against and any unmet quantity would be stored on the Requirement and shown on the Requirement screens, while what limited a demand (i.e., the bottleneck machine or part) would be named in the run's messages.
> h2. Scenario Planning: Two plans side by side - Constrained and Unconstrained modes
> Each MRP run can be executed in one of two modes i.e., Constrained Capacity (finite capacity run, this feature) or Unconstrained Capacity (infinite capacity run, today's behavior), and everything a run produces would get stamped with its mode. A new run would replace only its own mode's results i.e., its MRP events and its unapproved Requirements, while the other mode's plan survives untouched. This way planners can keep both pictures at once, what demand truly wants (Unconstrained) next to what capacity can deliver (Constrained), and read the gap between them as the measurable cost of the capacity constraint.
> h2. Configuring Default MRP Mode
> Each Facility can carry a Default MRP Mode (Facility.defaultMrpModeEnumId). A run started without a mode (e.g., every scheduled run) would plan in that Facility's default mode, and a Facility with no default would plan Unconstrained, exactly as today. There is no screen to set it yet, so for now it has to be set as data.
> Both modes would propose ordinary Requirements (REQ_PROPOSED), and either of them can be approved. The Facility's default mode is its plan of record, and hence that is the plan to approve from, whereas the other mode's plan is just for comparison and gets replaced by that mode's next run. To make the other mode's plan the live one, change the Facility's Default MRP Mode and re-run MRP, so that the live plan gets regenerated against everything that has changed since.
> h2. A facility plans only what it stocks
> Both modes would raise Requirements only for the products with a ProductFacility row at the Facility being planned, and the supply needed for anything else would get reported on the plan as "No ProductFacility record for [facility]; no requirement created.", irrespective of whether the product is bought or made. The Constrained mode wouldn't plan such a demand at all, so it would never book machine time for work the Facility is not going to do. Supplying one Facility's demand from another Facility is inter-facility transfer planning (OFBIZ-7356).
> h2. Related work
>  * OFBIZ-13628 — MRP Planning Rules. To begin with, Makes the order of the demand classes (R3), dated-before-undated within a class, and the use of idle machine time (R11) configurable per Facility Group, and records on a run header which rule and parameters produced each plan. Later on as the need arises any new rule can be coded for and controlled using rule setting by planners.    
>  * OFBIZ-13630 — Run MRP for a Facility Group. Plans every member of a Facility Group, one plan per member, in one transaction.
> h2. Important Rules
> *R1 — Two modes, and the existing one stays untouched.* A run would be either Constrained (capacity is finite) or Unconstrained (today's behavior). Each run stamps its own results and replaces only its own, so a planner can hold both and compare what the factory wants against what it can actually do. With the feature unused, the results would be identical to the current release.
> *R2 — Orders get planned one at a time, in priority order.* Each demand would get planned through its whole product tree before the next one is considered, and all of them would draw on the same pool of stock and machine hours. Whoever gets planned first takes what it needs, and the later demands only see what is left. This is what allows every Requirement to be traced back to the order it serves.
> *R2a — Every demand keeps its own Requirement, even for the same product.* Two orders for one product would get planned one after the other and recorded as two Requirements, and never merged into a single larger one. Each one would carry the quantity, dates and shortfall of the demand it serves, so a planner can always tell which order a proposed batch belongs to. Merging would lose that link, and then there would be no way to tell on which customer a shortage falls.
> {*}R3 — Priority classes decide who gets served first, ahead of dates{*}, and dates order the demands within a class.
>  # Approved Sales Orders
>  # Parts for Production Runs already on the floor
>  # Parts for approved Production Run Requirements
>  # Topping stock back up to its minimum
>  # Sales Forecast
> (Making this order configurable is OFBIZ-13628, which adds R3a to R3c.)
> *R4 — Anything already on hand or already coming gets counted first.* Stock, approved Purchase Orders, open Production Runs and approved Requirements would all reduce what the plan proposes. Supply arriving later than wanted would still count i.e., the work would wait for it and get flagged, rather than the same material getting ordered twice.
> *R4a — An open Production Run delivers what it still owes, irrespective of the state of its operations.* A run's remaining output counts as supply because output is still owed on it, and not because machine time is still booked for it. A run whose last machine step is finished (with only a machine-less step such as a trim or an inspection left) is in fact closer to delivering, so its balance would get credited and dated from the run's own finish. The only case that credits nothing is a run whose remaining work cannot be placed inside the planning window at all, because its components cannot arrive in time.
> *R5 — A level covered by supply pulls no components.* For instance, if the skateboards are already coming, the plan wouldn't order deck materials for them.
> *R6 — A shortage gets flagged, not hidden.* A Requirement would always show the full quantity needed, and where capacity or material falls short, it would record how much cannot be covered rather than quietly asking for less. What can actually be made is the quantity minus the unmet quantity.
> *R7 — A part that arrives in time causes a delay, whereas one that cannot causes a shortage.* Where a purchase can land inside the planning window, it would get ordered and production would simply wait for it, so the quantity stays unaffected. Only a part that cannot be here in time (because it has no supplier whose terms are in force, or because its lead time runs past the window) would limit how much can be built. Lead time is counted in the supplier's working days.
> *R8 — The required-by date is when the whole order gets finished, walked through the working calendar, even if that falls past the horizon.* The receipt would show its own, earlier date i.e., when the units that genuinely land are ready.
> *R8a — The ideal date assumes material and capacity were fully available, and could fall in the past.* Beside every date the plan can actually achieve, a Requirement would carry the date it should have had i.e., what the demand would have required if nothing had been short and the machine had been free, measured against each resource's own calendar. It deliberately runs back past today when the demand is already overdue, as that is what would show a purchase as late instead of quietly re-baselining it to today. What separates it from the achievable date is the load and never the calendar, so an ideal date would honor working days, holidays and shutdowns exactly as the achievable one does, because a closed day is a fact about the plant and not a queue of other orders.
> *R9 — No date ever gets set in the past.* A run started at 4 pm wouldn't plan work into that morning, and the work that should already have started would begin now and get flagged late.
> *R10 — Work already on the floor gets placed first and never moved.* Only what genuinely remains would get booked i.e., the outstanding quantity, skipping the finished operations, and charging the setup only where the floor has recorded that it was done.
> *R11 — A higher-priority order never suffers so that a lower-priority one can run, and no job ever gets split.* Where an order is waiting for its parts, the machine in front of it would be held. Lower-priority work can take that time only when both of these hold: it cannot move the waiting order by even a minute, and it can be finished in full before that order begins. If either of them fails, the work would get planned after the waiting order and the machine would simply stand idle. In other words, idle time gets accepted rather than bought at the cost of the order that was already promised. A job would never be broken in two around another one, which would mean setting the machine up twice while the plan had paid for only one setup.
> Point to note here is that this prohibition is on splitting a job in time on one machine i.e., a job never gets broken around other work sitting in the middle of its day. Dividing a job between the machines of a group is a different thing, governed by R12, and each piece of a divided job is itself an unbroken block on its own machine.
> *R12 — A job runs on one machine of a group, and jobs spread across the group.* Two 8-hour machines offer 16 hours of work in a day, but one job would use one machine and run at that machine's pace. Where several jobs need the group, they would get placed on different machines and run at the same time, with the emptier machine chosen first. A job too large for any single machine to deliver on time would get divided between the machines, and each piece would pay its own setup. A step could instead name one machine, in which case only that machine would be considered.
> A job that already meets its date would never be divided, even when other machines stand idle, as dividing exists to make a date achievable and not to finish early (the same reasoning as R15).
> *R13 — A shared calendar is not a shared machine.* Two machines on the same calendar would each get their own full hours.
> *R14 — Time comes from the estimates, and capacity gets used only where a machine is named.* A step with a time but no machine would still take that long and consume no capacity.
> *R15 — Capacity gets balanced by day, and work gets placed to finish when it is due.* No machine would be booked beyond its hours for a day, and every job would carry a start and finish time. A job that can be delivered on time would get placed to finish at its due moment, and not as early as the machine allows, e.g., an order due at 16:00 would run up to 16:00 rather than finishing mid-morning and sitting as stock. Jobs queue behind one another on the same machine, the time held in front of an order waiting for its parts isn't given away, and a smaller job can take that gap only if it finishes before the waiting order begins. An order that cannot be delivered on time would get placed as early as it can be instead, since finishing late is the only remaining choice. What the plan doesn't do is optimize the resulting order i.e., it follows the planning priority and not the fewest machine changeovers.
> *R16 — Approving a Requirement makes it a commitment.* Re-running the plan would never delete or downgrade an approved Requirement, and both modes would count it as supply.
> The numbering follows the master document, and R17 to R19 are on OFBIZ-13630.
> *R20 — A facility plans only what it stocks.* Both modes would raise a Requirement only for a product with a ProductFacility row at the Facility being planned. When supply is needed for any other product, the plan would record an error event instead, "No ProductFacility record for [facility]; no requirement created.", irrespective of whether the product is bought or made. In the Constrained mode, a demand for a product the Facility doesn't stock (e.g., a Sales Order, a Forecast, or a component of work already released) would be kept on record and reported, but not planned i.e., nothing gets made or bought for it, so no bill of materials gets exploded and no machine time gets booked for work that is never going to be done there. A component the Facility doesn't stock, needed by a product it does stock, would still get planned because its parent needs it, but it wouldn't raise any Requirement.
> h2. How Constrained Capacity Mode Works
>  * *Committed work gets placed first.* Open Production Runs would be laid into capacity before any proposal gets sized, irrespective of their dates. A committed run scheduled later wouldn't be displaced by an earlier-needed proposal, and MRP would never reschedule or modify one.
>  * *Demands get served in priority classes, and then by date* i.e., Sales Orders, then the components of open Production Runs, then the components of approved Production Run Requirements, then below-minimum stock, and then Forecast. Dates order the demands within a class, and not across classes.
>  * *Backward scheduling with a forward fallback.* Orders would get scheduled backward from their due date, and where capacity forces a start into the past, they would get scheduled forward instead and flagged Late.
>  * *A shortage gets flagged, not shrunk.* Where capacity or material cannot cover the full quantity, the Requirement would keep its net requirement and record the unmetQuantity with Short. What can actually be made is quantity − unmetQuantity, and the quantity never gets quietly reduced.
>  * *Cascades run down the tree, not across orders.* A delayed component would push its parent's start, and a short component would cap what the parent can build. There is no backlog carried onto a later order for the same product, as the shortfall already sits on the Requirement that could not cover it.
>  * *Flags and tracking.* Late and Short indicators, the date the plan was measured against, the unmet quantities and the mode would persist on Requirements, and the mode would persist on events. They are not yet carried onto a Production Run or Purchase Order at conversion.
>  * *Only what the Facility stocks gets planned.* A demand for a product with no ProductFacility row at the Facility would be recorded and reported, but not planned, and a component the Facility doesn't stock would still get planned for its parent's sake but wouldn't raise any Requirement (R20).
>  * *One plan per Facility and mode.* A run would replace its own mode's events and unapproved Requirements for the Facility it plans, and nothing else.
> h2. Entity Model extensions
> h3. Data model
> Every addition is nullable and stays inert until used, so a database that loads the reference data and never runs the Constrained mode would plan exactly as before. No new tables are needed, as the capacity bookkeeping during a run is held in memory, and what persists is the plan i.e., Requirements and events as today, carrying the fields below.
> *Fields added to existing entities*
> ||Entity||Field||Type||Meaning||
> |Requirement|isLate|indicator|Y when finite scheduling pushed the completion past the date the demand needed. Rolled up i.e., an end item would be late if any run feeding it was.|
> |Requirement|isShort|indicator|Y when capacity or material cannot cover the full quantity.|
> |Requirement|unmetQuantity|fixed-point|The part of the quantity that cannot be covered; what can be made is quantity − unmetQuantity.|
> |Requirement|idealRequiredByDate|date-time|The date the demand needed, measured as if material and capacity were free (R8a), which is the reference the achievable date is read against. Set on every Constrained Requirement, flagged or not.|
> |Requirement|mrpModeEnumId|id|The mode whose run proposed it. It scopes the re-initialization i.e., a run would delete only its own mode's unapproved Requirements.|
> |MrpEvent|mrpModeEnumId|id|The mode whose run wrote it. isLate already exists on MrpEvent and gets reused, and the existing facilityId would now be filled on every Constrained event.|
> |Facility|defaultMrpModeEnumId|id|The Facility's default mode, used when a run names none, and empty means Unconstrained. Set as data, as there is no screen for it yet.|
> Index REQ_MRPMODE_IDX on Requirement (facilityId, statusId, requirementTypeId, mrpModeEnumId) keeps the mode-scoped delete on an index range. The mode fields carry no foreign key, as their values come from the MRP_MODE enumeration.
> *Supporting reference data (seed data, not schema)*
>  * Enumeration type MRP_MODE: MRP_CONSTRAINED, MRP_UNCONSTRAINED.
>  * MRP event types INTERNAL_REQ_RECP and INTERNAL_REQ_REQ i.e., the output and the component demand of an approved Production Run Requirement, which the classic engine ignores.
>  * Facility calendar type SHIPPING, and a SHIPPING calendar (weekdays from 08:30, eight hours), which is the default calendar the outbound leg (ProductFacility.daysToShip) gets walked through.
> What is deliberately not persisted: the capacity ledger, where each job was placed, which machine of a group it runs on, and the explanation of what limited a demand (that is given in the run's messages).
> *Upgrading an existing database.* All columns are nullable, and the entity engine would add them at start-up. Load the seed data (./gradlew "ofbiz --load-data readers=seed") for the enumerations and statuses, as without it the MRP Mode lists would be empty.
> h3. Service and screen surface
>  * executeMrp would gain mrpMode (empty means the Facility's default) and planningHorizonDays, and would delegate a Constrained run to the new executeConstrainedMrp, which returns mrpId and requirementsCreated.
>  * Run MRP would offer the mode and the horizon. Find Inventory Event Plan would filter by mode and show the ERROR events as notices, and the Requirement screens would filter by mode and show the Late, Short, unmet and ideal-date fields.
> *Points that matter functionally:*
>  * Capacity bookkeeping happens in working memory for the run and leaves no new tables behind, and what persists is the Requirements (carrying these fields) and the MRP events.
>  * Existing data stays untouched. Requirements created before this feature would simply have the new fields empty, so no migration or cleanup is needed. With the Constrained mode unused, a plan stays unchanged, except that a made product the Facility doesn't stock would now get reported as well as a bought one (R20).
>  * Approved Requirements are outside all of this. Approval fixes a Requirement as a commitment i.e., it would never be deleted by any run of either mode and would be counted as real supply by both modes.
> The flags are durable. They would stay on the Requirement after the MRP screen is closed and get shown on the Requirement screens, which can be filtered by the mode that produced a Requirement. They are not yet carried onto the Production Run when a Requirement gets converted.
> h2. Use Cases
> Every case is written against the manufacturing-demo dataset (OFBIZ-13438) so that anyone can set it up and check it, and every case can be verified from the data the run produces i.e., no case depends on reading a log.
> h3. The demo scenario in plain terms
> A skateboard factory, Facility MFG_PLANT, with two machines:
> ||Machine||Works||Hours||
> |LAM|Monday, Wednesday, Friday|08:30–16:30|
> |AMC|Monday to Friday|08:30–16:30|
> Two things are made:
>  * DSK15144-00, the skateboard deck, made on LAM from veneers and glue.
>  * DCGAPSOSR12, the finished skateboard, made on AMC from one deck plus a sticker, a warranty card and a transfer.
> Every manufacturing step costs the same in this dataset i.e., 15 minutes to set the machine up, and then 5 minutes per unit. So a full 8-hour day is 480 minutes, less 15 for setup, leaving 465 i.e., 93 units a day on either machine, and that single figure explains most of the numbers below.
> Bills of material: one skateboard needs 1 deck, 1 sticker, 1 warranty card and 1 transfer, while one deck needs 2 face veneers, 2 cross-ply veneers, 3 core veneers and 0.83 of a unit of glue.
> Every purchased part has a supplier, a lead time and a minimum order quantity. What separates them is how long they take, as a part that can arrive inside the planning window would merely delay production, whereas one whose lead time runs past the window would limit how much can be built at all. That distinction drives several cases below.
> Minimum Stock levels are 100 finished skateboards and 50 decks. The demo doesn't seed any stock or orders, so the cases below assume a small set of transactional data (Sales Orders, Production Runs, Purchase Orders and opening stock), defined once in "The scenario these cases run against" and referenced by name.
> h3. Key Terms
>  * *Demand* — something that needs making or buying, e.g., a Sales Order, a Minimum Stock, or parts for an order already on the shop floor.
>  * *Requirement* — what the plan proposes in response i.e., make this many, by this date.
>  * *Committed work* — Production Runs already released to the floor, which the plan works around.
>  * *Late* — the plan cannot deliver by the date asked for. *Short* — it cannot deliver the full quantity.
>  * *Horizon/Planning Horizon (or fence)* — how far ahead the run plans; beyond it, the run says nothing.
> h3. The scenario these cases run against
> The demo dataset supplies the standing configuration i.e., the plant, machines, calendars, routings, bills of material, products and suppliers, and the transactional data below completes it. None of it ships with the demo, so either create it or load it as a test fixture.
> *Reference run date:* Monday 2 March 2026, 08:00, which is before the shift opens, so the first day is whole. Fixing the run date matters because LAM works Monday, Wednesday and Friday, and the same order placed on a Tuesday would produce different dates. Working days in the fortnight that follows:
> ||Machine||Working days||
> |LAM|Mon 2, Wed 4, Fri 6, Mon 9, Wed 11, Fri 13 March|
> |AMC|Mon 2 – Fri 6, Mon 9 – Fri 13 March|
> *Capacity:* 480 minutes a day, less 15 minutes of setup, at 5 minutes a unit i.e., 93 units per machine per day. A batch of n units occupies 15 + 5n minutes.
> *Supplier terms*
> Every purchased component has a supplier record with a lead time and a minimum order quantity. The demo ships the minimum order quantities but no lead times, so set standardLeadTimeDays as below.
> ||Component||Supplier||Lead time||Minimum order||
> |DRGAMBSK8II glue|SUP_002|3 days|50|
> |DRVM52105036FAS face veneer|SUP_001|7 days|100|
> |DRVM60361050XA cross-ply veneer|SUP_001|7 days|100|
> |DRVM62105036CA core veneer|SUP_001|45 days|100|
> |DCSPPUSA sticker|SUP_003|5 days|500|
> |DRSASOCWC2 warranty card|SUP_003|5 days|100|
> |DRTGPSOSR12 transfer|SUP_003|5 days|100|
> Lead times are counted in the supplier's working days, so seven of them would land later than seven calendar days. Core veneer is the outlier on purpose, as at 45 working days it cannot arrive inside a normal planning window, and that is how the cases needing a genuinely unobtainable part are built. Everything else can be bought in time, and hence would only ever delay production.
> *Stock on hand at MFG_PLANT*
> ||Fixture||Product||Quantity||
> |STK-DECK-40|DSK15144-00 deck|40|
> |STK-DECK-20|DSK15144-00 deck|20|
> |STK-VENEER-10|face, cross-ply and core veneer|10 each|
> |STK-TRIM|sticker, warranty card, transfer|500 each|
> STK-TRIM keeps the finished-goods cases about machine time rather than about missing trim, so use it wherever a case is not specifically about a material shortage.
> *Sales Orders*
> All approved, for DCGAPSOSR12, shipping from MFG_PLANT.
> ||Fixture||Quantity||Wanted by||Occupies||
> |SO-A|60|Wed 4 Mar|5h15m on AMC|
> |SO-B|50|Wed 4 Mar|4h35m on AMC|
> |SO-LATE|200|Wed 4 Mar|16h55m on AMC|
> |SO-BIG|600|Fri 6 Mar|more than a 5-day horizon holds|
> |SO-100|100|Fri 13 Mar|8h35m on AMC|
> |SO-400|400|Fri 13 Mar|33h35m on AMC|
> |SO-PAIR-1, SO-PAIR-2|50 each|Fri 13 Mar|4h35m each|
> SO-A and SO-B together want 110 units on a day that holds 93, which is the contention in CASE-1, whereas SO-LATE needs nearly 17 hours against a Wednesday deadline, so it cannot be met on time.
> *Production Runs*
> ||Fixture||Product||Quantity||Status||Notes||
> |PR-OPEN|DSK15144-00|50|Approved|starts Mon 2 Mar; 4h25m on LAM|
> |PR-OVERDUE|DSK15144-00|50|Approved|estimated start Mon 23 Feb; never started|
> |PR-RUNNING|DSK15144-00|100|Running|40 produced; LAM step complete with actual setup recorded|
> |PR-BARE|DSK15144-00|100|Running|nothing produced, nothing issued, no actual setup recorded|
> |PR-ISSUED|DSK15144-00|100|Approved|150 of its 300 core veneers already issued|
> *Purchase Orders and approved Requirements*
> ||Fixture||What||Notes||
> |PO-50|Approved Purchase Order, 50 DCGAPSOSR12|estimated delivery Wed 11 Mar|
> |REQ-INT-50|Approved INTERNAL_REQUIREMENT, 50 DCGAPSOSR12|required by Fri 13 Mar|
> |REQ-PROD-50|Approved PRODUCT_REQUIREMENT, 50 cross-ply veneer|required by Wed 11 Mar|
> CASE-21 uses SO-400 against PO-50, PR-OPEN and REQ-INT-50 i.e., 400 wanted, 150 already coming, and 250 proposed.
> h3. Reading the results
> Exact timestamps depend on how the engine packs the work into each day, so check the relationships rather than matching the strings i.e., no day booked beyond its hours, the earlier demand served first, a parent starting no earlier than its components finish, and the quantity kept whole with the shortfall recorded separately. Those would hold irrespective of the run date, and the figures above are chosen so that each case crosses the threshold it is testing.
> h3. A. Competing for machine time
> *CASE-1 — Two orders want the same machine on the same day.*
> *Setup:* SO-A (60 units) and SO-B (50 units), both wanted the same Wednesday, while AMC makes 93 a day.
> *Expected:* the earlier-dated order would get served first and take what it needs, and the second one would get planned into the hours that remain and spill onto the previous working day. Neither day would be booked beyond 8 hours.
> *Why:* today both would get planned onto the same day, 110 units into a 93-unit day, with nothing to warn the planner.
> *CASE-2 — Work already on the floor keeps its machine time.*
> *Setup:* PR-OPEN (50 decks, approved, starting Monday), and then a Sales Order needing deck time.
> *Expected:* the released run would keep Monday, and the new demand would get planned around it, as the plan never moves or rewrites work already on the floor.
> *CASE-3 — An overdue run still occupies the machine.*
> *Setup:* PR-OVERDUE, same as PR-OPEN but its planned start is a week in the past and it never started.
> *Expected:* its remaining hours would be treated as occupying the machine from today onwards, and not from a date in the past. If the run happens after the shift has closed, that work would land on the next working day.
> *Why:* the work still has to happen, and pretending it happened last week would hand its hours to something else.
> *CASE-4 — Only the work genuinely left gets booked.*
> *Setup:* PR-RUNNING i.e., 100 decks, 40 already produced, with the LAM step finished and its setup recorded.
> *Expected:* only 60 units of the remaining work would get booked, the completed step wouldn't be booked again, and the setup wouldn't be charged a second time.
> *Why:* re-booking finished work takes hours back off the shop floor and pushes everything behind it later.
> *CASE-5 — Parts already issued to a run don't get demanded again.*
> *Setup:* PR-ISSUED i.e., a run needing 300 core veneers, with 150 of them already issued to it.
> *Expected:* demand would get raised for the outstanding 50 only, and a part fully issued wouldn't raise any.
> *Why:* the plan only reorders what has not yet physically left the store.
> *CASE-6 — Setup gets charged until the floor says it was done.*
> *Setup:* PR-BARE, marked running, but with nothing produced, nothing issued and no actual setup recorded.
> *Expected:* its 15 minutes of setup would still get charged.
> *Why:* marking a job as started is just an intention, whereas recorded setup time is a fact, and releasing the setup on a status alone would hand the plan 15 minutes that the machine has not actually saved.
> *CASE-7 — Late rather than short.*
> *Setup:* SO-LATE i.e., 200 skateboards wanted the first Wednesday, roughly 17 hours of AMC time.
> *Expected:* the plan would schedule it forward across the following working days, mark it Late, and keep the original Wednesday date so that the delay can be measured.
> *Why:* a planner needs to know both what was promised and what is achievable.
> *CASE-8 — Short when the horizon genuinely cannot fit it.*
> *Setup:* SO-BIG i.e., 600 skateboards, run with a 5-day horizon, which holds about 465.
> *Expected:* the Requirement would show the full quantity ordered, marked Short, recording how many units cannot be covered, and it would never get quietly reduced.
> *Why:* a Requirement cut down to what fits looks achievable and hides the gap.
> *CASE-9 — Even a demand that cannot be started at all gets shown.*
> *Setup:* so little time remains that the 15-minute setup alone won't fit.
> *Expected:* a Requirement would still get created, for the whole quantity, entirely unmet.
> *Why:* dropping the row would remove the demand, and everything beneath it, from the plan.
> *CASE-10 — A component's minimum order quantity doesn't inflate what is made.*
> *Setup:* stickers DCSPPUSA, whose supplier SUP_003 requires 500, needed by a demand for far fewer.
> *Expected:* the purchase would get raised to 500, because that is the smallest order the supplier accepts, while what is made wouldn't follow it i.e., the production Requirement would stay at what demand and capacity justify, and the balance of the stickers would get recorded as stock available to the later demands.
> *Why:* a purchasing term constrains buying, and not building, so raising a production Requirement to meet a component's minimum would manufacture goods that no demand asked for. See CASE-26 for the same minimum viewed from the purchasing side.
> *CASE-11 — Work that cannot finish inside the horizon books nothing.*
> *Setup:* a demand whose work cannot complete before the horizon ends, however it gets placed.
> *Expected:* no machine time would get reserved, and the Requirement would carry its full quantity as unmet. Its dates would run from the moment its machine next has time, and not from the start of the run, and never across the hours another order already holds. Where the window is full throughout, that moment would fall beyond the window.
> *Why:* reserving hours for work that cannot happen denies them to the work that can. And a date anchored to the run's own start would say that the order could begin today when the machine is committed for the whole window, which reads as two orders starting together on one machine.
> *CASE-12 — A machine waiting for parts is held, and not given away.*
> *Setup:* a high-priority order that cannot start until its veneers arrive at midday, on an otherwise empty machine.
> *Expected:* the morning would be held, and a lower-priority order wouldn't get planned into the hours the waiting order will occupy.
> *CASE-13 — A small job can use that idle morning, whereas a large one cannot.*
> *Setup:* continuing CASE-12, two candidates i.e., one that would finish before midday and one that wouldn't.
> *Expected:* the small one would run in the gap, and the large one would get planned after the waiting order. Neither of them gets split around it, and the waiting order doesn't move.
> *Why:* splitting a job means setting the machine up twice for one setup's worth of planned time.
> *CASE-14 — Two orders due the same day: the later one finishes where the earlier one starts.*
> *Setup:* two Sales Orders for the finished deck on the same day, with stock at every level below so that only machine time binds: 60 units due 16:00 and 50 units due 16:30. At 15 minutes of setup plus 5 minutes a unit, that is 315 and 265 minutes of work against a 480-minute shift.
> *Expected:* the 60-unit order would get planned from 10:45 to 16:00, finishing exactly when it is due, and not at 13:45. The 50-unit order cannot fit in the 30 minutes left after it, so it would finish at 10:45 where the first order begins, and reach back into the previous day for the balance i.e., 14:20 to 16:30 there, and then 08:30 to 10:45 on the due day. Both would keep their full quantity and neither would be late, while thirty minutes of the due day would be left unused.
> *Why:* finishing when due rather than as early as possible is what keeps finished stock off the shelf. The second order shows the cost of never splitting a job honestly, as the 30-minute remnant cannot be used by a 265-minute order and the plan accepts that idle half-hour rather than breaking the order in two and paying its setup twice. It also shows that reaching into an earlier day takes the END of that day, so the work stays one unbroken block across the overnight break rather than two pieces with a day between them.
> *CASE-15 — A run whose last machine step is finished still counts as supply.*
> *Setup:* an open Production Run for the finished deck with 60 units still to produce, whose LAM step is marked finished, leaving only the machine-less trim, and a demand for 300 units.
> *Expected:* a Requirement for 240 units i.e., the 60 still owed by the run would get credited as supply, dated from the run's own estimated finish, and marking the LAM step finished shouldn't change the number.
> *Why:* supply is owed output, and not reserved machine hours. Before this was fixed, finishing the last machine step made the run's remaining units disappear from the plan and the Requirement jumped from 240 to 300, so a run looked further from delivering precisely because it had got closer.
> *CASE-16 — Two orders for one product on one machine don't share a start date.*
> *Setup:* two approved Sales Orders for the deck DSK15144-00 at MFG_PLANT, with no Production Runs and stock at every level below, so machine time is the only constraint: 400 units due today at 16:30, and 50 units due the following Friday at 16:30. A six-day window over LAM's Mon/Wed/Fri calendar, so three working days.
> *Expected:* the earlier-due order would get sized to what the window holds and take all of it, keeping its full 400 with the balance unmet. The later-due order can be given nothing i.e., 50 units, wholly unmet, with both orders Late and Short. Their start dates would differ, as the second one gets dated from when LAM next has time, after the first order's committed window, and its finish wouldn't precede the first order's.
> *Why:* capacity claimed by an earlier demand is gone, so a later one has to be dated somewhere else. Two identical start dates would say that both orders begin together on one machine, and a blocked order finishing before the order that took the capacity would invert the reading entirely. Both figures were right, and only the dates were wrong, and wrong in the optimistic direction.
> *Note on the arithmetic:* the buildable quantity depends on the hour the run gets submitted, because the first day is trimmed to the shift time still ahead of it. A run at 08:30 has 1,440 minutes over three days and builds 285, whereas a run at 16:05 has 984 and builds 193, so derive the expected figures from the capacity remaining at the run instant rather than assuming a start-of-day run.
> h3. B. Calendars, shifts and dates
> *CASE-17 — A run started late in the day.*
> *Setup:* the plan gets run at 16:46, after LAM's 16:30 close.
> *Expected:* nothing would get planned into that day, and the first work would land on the next day LAM works. If run at 15:00 instead, only the remaining 90 minutes would be available.
> *Why:* hours that have already passed are not capacity.
> *CASE-18 — Work spanning a shift close and non-working days.*
> *Setup:* 10 hours of deck work starting Monday morning on LAM, which works Monday, Wednesday and Friday.
> *Expected:* 8 hours on Monday, and the remaining 2 from Wednesday's opening, as the work would never land in the evening or on a day the machine doesn't run.
> *CASE-19 — Supplier lead time gets counted in working days.*
> *Setup:* cross-ply veneer DRVM60361050XA with standardLeadTimeDays set to 7 on supplier SUP_001.
> *Expected:* the arrival date would be that many of the supplier's working days ahead, so it lands later than a plain calendar count, and the same date would decide whether the shortage is a delay or a cap.
> *CASE-20 — Holidays and shutdowns count on every date, inside the window and outside it.*
> *Setup:* a machine working Monday, Wednesday and Friday, with an exception day recorded against its calendar closing it for a week, and a demand needing several days of that machine, run so that the work reaches back before the run date and forward past the planning window.
> *Expected:* both the achievable dates and the ideal need dates would step over the closed week and over the non-working weekdays, and the number of days walked back would match the machine's real working pattern rather than a count of calendar days. A holiday falling on the run's own day would close what is left of that day too.
> *Why:* a calendar means the same thing whichever side of the planning window a date falls on. Previously the window was expanded from the real calendar (exception days included), while anything outside it used a working week inferred from that window, which silently dropped holidays and shutdowns and treated a three-day-a-week machine as working seven. The effect was to walk back too few days and report parts as needed later than they truly were, understating how late an order already was.
> h3. C. Stock and purchasing
> *CASE-21 — What is already coming gets counted before anything is proposed.*
> *Setup:* SO-400, plus PO-50, PR-OPEN and REQ-INT-50 i.e., 150 units already coming.
> *Expected:* the plan would propose 250, and not 400, with each source dated when it can genuinely be there.
> *CASE-22 — Supply reduces a Requirement even when no machine is free.*
> *Setup:* demand for 50 decks, with 50 already arriving and LAM fully booked.
> *Expected:* no Requirement would get raised, as using stock that already exists needs no machine time.
> *CASE-23 — A covered level pulls no components.*
> *Setup:* SO-100 covered entirely by approved Production Run Requirements for the same 100 units.
> *Expected:* no deck, sticker, warranty-card or transfer demand would get raised beneath it.
> *Why:* ordering deck veneers for skateboards nobody is going to build wastes money and consumes the deck supply.
> *CASE-24 — Stock gets counted once.*
> *Setup:* STK-DECK-40 against a demand for 100 decks.
> *Expected:* a Requirement for 60, never 20 (counted twice) and never 100 (not counted).
> *CASE-25 — A part that arrives in time delays, whereas one that cannot caps the quantity.*
> *Setup:* STK-VENEER-10 and a deck demand for 100, run with a 30-day horizon. Cross-ply veneer arrives in 7 working days, whereas core veneer takes 45 and cannot arrive inside the window.
> *Expected:* the cross-ply and face veneers would get ordered and the decks would simply wait for them, flagged late. The core veneer would cap the production at the 10 units on hand (3 per deck, so 3 decks), and the shortage would get recorded against it. The same would hold for a part whose only supplier's terms have expired, as it cannot be bought, so it would cap the quantity too.
> *Why:* a part that arrives late is a scheduling problem, whereas a part that cannot arrive at all is a hard limit on what the factory can promise.
> *CASE-26 — A minimum order quantity's surplus becomes usable stock.*
> *Setup:* two small demands for stickers DCSPPUSA, with a minimum order of 500.
> *Expected:* one order for 500, and not two, as the surplus would be treated as stock the second demand consumes.
> *CASE-27 — A purchase that should already have been placed.*
> *Setup:* the same 7-day part, needed sooner than 7 working days from now.
> *Expected:* the purchase would start now, get flagged late, and couldn't arrive before its computed date.
> h3. D. Priority and firm commitments
> *CASE-28 — Priority classes decide who gets served first.*
> *Setup:* on one day, deck demand from a Sales Order, from PR-OPEN, from REQ-INT-50, and from the 50-unit Minimum Stock level, together more than LAM can make.
> *Expected:* served in that order irrespective of their dates, each one taking only what the class above leaves, and the stock top-up absorbing the shortfall.
> *Why:* a customer order shouldn't lose machine time to a stock top-up that could wait.
> *CASE-29 — Approved Requirements survive and count as supply.*
> *Setup:* REQ-PROD-50 and REQ-INT-50 exist, approved, when the run starts.
> *Expected:* neither of them would be deleted, both would reduce what gets proposed, and the approved Production Run Requirement would also take its machine time.
> *Why:* approving is how a planner locks a part of the plan, so a rerun shouldn't undo it.
> *CASE-30 — An approved Production Run Requirement's parts become demand.*
> *Expected:* its components would get demanded at its own priority (above stock top-ups, below the work already on the floor), due when the order itself is due.
> *CASE-31 — Forecast gets planned last.*
> *Expected:* Forecast demand would get planned after every other class and consume only what they leave, ordered within itself by period.
> h3. E. Multi-level products
> *CASE-32 — A late deck pushes the skateboard.*
> *Setup:* a Sales Order for finished skateboards where the decks cannot be made in time.
> *Expected:* the decks would take the first capacity available, and the skateboards would get moved to start at or after the decks are finished, with both flagged late.
> *CASE-33 — A skateboard never starts before its decks exist.*
> *Expected:* for every finished-goods Requirement in the plan (and not only for the flagged ones), its start would be at or after the receipt of the deck batch feeding it i.e., the moment the units that genuinely land are ready. Where that receipt falls outside the consuming machine's working hours, the start would roll forward to the next moment that machine can work.
> The floor is the receipt, and not the Requirement's required-by date. Those are two different dates, and deliberately so, as a required-by date covers the whole order including the units that are never made, whereas a receipt covers only what actually arrives. A level can consume only what arrives, so flooring it on the whole-order date would hold it back for units that are never going to exist.
> *Worked example.* A deck Requirement for 100 with 7 unmet lands 93 decks at Friday 16:30, and carries a required-by of the following Monday 09:05, which is the notional finish of all 100. The skateboard feeding off it gets floored on the Friday receipt, and since Friday 16:30 is the assembly machine's shift close, its start would roll to Monday 08:30. That start is before the deck's required-by date and is nonetheless correct, because every deck that will ever exist was ready on Friday.
> *Why it is worth stating:* read as the whole-order date, the same plan looks like an assembly starting 35 minutes before its components are complete, whereas read as the receipt, it is a level starting as soon as its parts are genuinely on hand. So in short, the plan is the same either way, and only the sentence describing it was ambiguous.
> *CASE-34 — A deck shortage caps the skateboards.*
> *Setup:* SO-100, with LAM's earlier days booked so that only 60 decks can be made in time.
> *Expected:* the skateboard run would get sized at 60, while its Requirement would still record the full 100 with 40 unmet, and the machine time on AMC would get booked only for what is going to be built, with the rest released.
> *Why:* the plan should never ask for finished goods that its components cannot support.
> *CASE-35 — Stock counts toward that cap.*
> *Setup:* same as CASE-34, plus STK-DECK-20.
> *Expected:* 80 skateboards buildable and 20 unmet, and where several components fall short, the tightest one would decide.
> *CASE-36 — A late part in one branch doesn't stall another.*
> *Setup:* cross-ply veneer delayed by its lead time, while the stickers and warranty cards are in stock.
> *Expected:* only the levels that actually consume the late veneers would get pushed out.
> *CASE-37 — Two orders wanting the same part.*
> *Setup:* SO-PAIR-1 and SO-PAIR-2, 50 each, with the decks needed the same day.
> *Expected:* each one would get planned through its own tree, and they would compete for the same veneers and the same LAM hours in priority order, so whichever gets served second would wait and get flagged late.
> h3. F. Machine groups
> *CASE-38 — Jobs on a group run side by side.*
> *Setup:* a group of two 8-hour machines, one of them already committed for a full day.
> *Expected:* a new job would run that same day on the free machine, at that machine's pace, and not queue behind the committed one, whereas a third job would find both machines full and move to the previous working day.
> *Why:* two machines can genuinely run two jobs at once, and reporting the second one as queued understated what the plant could deliver and dated it later than reality.
> *CASE-39 — A job too big for one machine gets divided, and pays a setup per piece.*
> *Setup:* a job needing 12 hours on a group of two 8-hour machines, with only that day available.
> *Expected:* divided between the two machines (as no single machine can deliver it that day), with each piece carrying its own setup, so the plan would consume slightly more machine time than the job's 12 hours. Both pieces would run concurrently and the job would get delivered on time. A job whose per-unit rate is not on file wouldn't be divided, as a piece of it couldn't be costed, so it would stay one block and take the delay.
> *Why:* the second setup is real and the plan has to charge it. The alternative i.e., spreading every job over every machine, would pay that setup on jobs that never needed it and leave no machine free for the next job.
> *CASE-40 — Members with different working weeks.*
> *Setup:* a group whose members work Monday–Friday and Monday/Wednesday/Friday respectively, on a Tuesday.
> *Expected:* the group would offer one machine's shift that day i.e., 8 hours of work in 8 hours, and not half of two.
> *CASE-41 — A step naming one machine.*
> *Expected:* it would run at that single machine's pace, its hours would come out of the group's pool so that nothing else can claim them, and a second step naming the same machine would get pushed out even though the group still shows capacity, because that capacity belongs to the other machine.
> h3. G. Routing steps
> *CASE-42 — A step with a time but no machine.*
> *Setup:* the deck routing's "Shape Deck" step, which has no machine assigned.
> *Expected:* it would contribute its time to the schedule but consume no machine capacity, so a later job on LAM would see only the LAM step's hours taken.
> *CASE-43 — A step with a machine but no times, and a step with neither.*
> *Expected:* both of them are effectively instantaneous, and neither of them would limit the quantity.
> *Why:* this is the existing OFBiz arithmetic and it shouldn't change.
> h3. H. Dates, flags and modes
> *CASE-44 — The required-by date covers the whole order.*
> *Setup:* a run that can make only a part of its quantity.
> *Expected:* its required-by date would be when all of it gets finished, even past the horizon, while the receipt would be dated when the units that genuinely land are ready. The receipt is also the date a consuming level gets floored on, so a level would start when its parts genuinely arrive rather than when the order that produced them notionally completes (see CASE-33).
> *CASE-45 — Every Requirement records the date it was measured against.*
> *Expected:* present on every Requirement, flagged or not i.e., the order's own date for a finished good, and the consuming step's start for a component.
> *CASE-46 — No date ever gets set in the past.*
> *Expected:* no Requirement would start before the moment the run began, under any combination of overdue supply, late purchases and elapsed shifts.
> *CASE-47 — Lateness gets flagged where it happens and carried upward.*
> *Expected:* each delayed batch would carry its own flag, so a delayed deck is visible to whoever schedules LAM, and the finished skateboard would be late if anything feeding it was.
> *CASE-48 — Both plans coexist.*
> *Expected:* a Constrained and an Unconstrained run would each keep their own results, and re-running one would leave the other one (and every approved Requirement) untouched, so that the two can be compared side by side.
> *CASE-49 — The existing MRP stays unaffected.*
> *Expected:* with the Constrained mode unused, the results would be identical to the current release.
> h3. I. Where a product is stocked
> R20. Each case builds a three-level product of its own i.e., an assembly made from a sub-assembly made from a part, stocked at one Facility only.
> *CASE-50 — The Unconstrained mode reports a made product the Facility doesn't stock, and not only a bought one. (R20)*
> *Setup:* a product stocked at FAC_A, made from a sub-assembly that is made from a part, while neither the sub-assembly nor the part is stocked anywhere, and its Minimum Stock at FAC_A is the demand. Run the Unconstrained mode.
> *Expected:* one Requirement, for the stocked product, and the plan would carry an error event for the part and for the sub-assembly, each one naming FAC_A.
> *Why:* stock OFBiz reported only the part. The sub-assembly got a proposed receipt with no Requirement behind it and nothing to say why, which is how a skateboard sold from WebStoreWarehouse (a warehouse that doesn't stock it) disappeared from the plan without any notice.
> *CASE-51 — The Constrained mode raises no Requirement for a level the Facility doesn't stock. (R20)*
> *Setup:* same as CASE-50, run in the Constrained mode.
> *Expected:* the same Requirement and the same two error events, as the stocked product would still get planned, while the sub-assembly and the part would get proposed receipts with no Requirement.
> *Why:* before R20, the Constrained mode raised Requirements for all three levels at a Facility that stocks only one of them, where the classic engine raised one.
> *CASE-52 — The Constrained mode plans nothing for a demand the Facility doesn't stock. (R20)*
> *Setup:* the product of CASE-50, not stocked at FAC_B, and an approved Sales Order for six of it shipping from FAC_B.
> *Expected:* no Requirement at FAC_B for the product or anything in its bill of materials. The Sales Order demand would stay on record and an error event would name the product and FAC_B, while no proposed receipt or component demand would get written for it.
> *Why:* planning the order would explode its bill of materials at FAC_B and book machine time there i.e., work FAC_B is never going to do, which would then crowd out the work it would do. Serving FAC_B's order from the Facility that makes the product is an inter-facility transfer, which this work doesn't attempt.
> h2. What the plan does not attempt
>  * *Optimizing the order of work within a day.* The plan does produce a sequence (every job carries a start and finish time, and jobs queue behind one another on a machine), but that order follows the planning priority. It isn't rearranged to reduce changeovers, and sequence-dependent setup times are not modeled, so a scheduler could still improve on it.
>  * *Recording which machine of a group.* The plan places each job on one machine of a group, but that choice is internal to the run, and since Requirement has no machine field, it isn't stored.
>  * *Carrying a shortfall onto a later order.* None is needed, as the shortfall stays visible on the Requirement that could not cover it.
>  * *Part-finished operations.* A step half way through would be treated as not started, because the data records no partial progress per step, which overstates the time left rather than understating it.
>  * *Merging demands for the same product into one Requirement.* This is deliberate, as separate Requirements are what let every batch be traced to the order it serves. It does mean more rows than a consolidating planner would produce, and that a setup gets charged per Requirement rather than once for a combined batch.
>  * *Exception WEEK patterns.* TechDataCalendarExcDay (individual holidays, shutdowns and one-off closures) gets honored on every calendar the plan walks, whereas TechDataCalendarExcWeek, a temporary replacement week pattern, doesn't. This is inherited, as stock OFBiz honors neither, and the classic Unconstrained run ignores exception days entirely.
>  * *Exception days on the purchasing and shipping legs.* Supplier lead time gets walked through the supplier's own calendar and the outbound leg through the Facility's SHIPPING calendar, each one correctly separate from the machine's. But those two walks use the stock OFBiz calendar helpers, which don't read exception days, so a supplier's own works holiday wouldn't extend its lead time. This is again inherited rather than introduced.
>  * *One Facility per run, and the plan gets rebuilt in full each time rather than adjusted.* Planning a Facility Group is OFBIZ-13630.
>  * *Make or buy by Facility.* Whether a product is made is decided by the product (a bill of materials and no main supplier) and not by the Facility, so a Facility that stocks a made product would plan to make it there.
>  * *Supplying one Facility from another.* A demand at a Facility that doesn't stock the product gets reported and not planned (R20), and planning it as a transfer from the Facility that makes it is OFBIZ-7356.
>  * {*}Alternative machines and routings, and lot sizing beyond a minimum order quantity{*}, are not modeled.
> h2. Behavior proven below the case level
> Two rules are enforced but cannot be exercised through an MRP run's output, because reproducing them needs control of the system clock or of the internal state rather than of the data. They are covered by unit tests, and are recorded here just so that they don't get mistaken for gaps:
>  * *Day buckets across a daylight-saving change.* Each bucket maps to a real local day, so a local day of 23 or 25 hours wouldn't make the later buckets drift.
>  * {*}A completion never precedes its own start{*}, and work never lands in an evening or on a non-working day, whatever the calendar arithmetic is asked to walk.
> h2. Not yet implemented - Candidate for Next Phase
>  * A screen to set a Facility's Default MRP Mode, which today has to be set as data.
>  * Carrying the Late and Short context onto a Production Run when a Requirement gets converted.
>  * The explanation of what limited a demand appears in the run's messages, and not on the Requirement.
>  * Inter-facility transfer planning (OFBIZ-7356).
>  * A declared maximum number of splits per routing step, for a shop that needs an order divided even when one machine could deliver it.
>  * Recording which machine on the Production Run created from a Requirement.
>  * A separate capacity-leveling pass i.e., balancing across the whole order book and re-sequencing to close the gaps. This is deliberately out of scope, as this engine plans one order at a time in priority sequence and never re-plans a placed order.
>  * Piece-to-piece pipelining between routing steps i.e., where step 1 gets divided, step 2 would chain on the latest piece's finish rather than getting fed by the first piece to complete.
> (Configurable planning parameters are OFBIZ-13628, and planning a Facility Group is OFBIZ-13630.)



--
This message was sent by Atlassian Jira
(v8.20.10#820010)