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:30:00 UTC

[jira] [Updated] (OFBIZ-13630) OFBIZ-13462: MRP Run for a Facility Group

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

Swapnil Shah updated OFBIZ-13630:
---------------------------------
    Description: 
h2. Rationale

A business with a plant and several warehouses currently has to run MRP once per Facility, and nothing ties those runs together. Run MRP does offer a Facility Group, which looks like a network run but in fact it is not, as it plans only the group's first WAREHOUSE and first PLANT as a single pairing and ignores every other member.

Also, the MRP engine cannot safely plan several facilities one after another, because each run deletes the MRP events of every Facility (and not just its own) and then plans from whatever events it finds.
h2. Requirements
 * Choosing a Facility Group should plan every member, each one exactly as it would get planned on its own.
 * Each member should get its own plan, identifiable on its own, and the plans of one run should be grouped by a cycle id.
 * The run should replace only its members' previous plans, and all or nothing i.e., if any member fails, every member would keep its previous plan.
 * Members should get planned in a fixed and repeatable order.
 * By default, each member should plan in its own MRP Mode and under its own Planning Rule, though a run could override either of them.
 * A scheduled job should be able to pass the same choices as the screen.
 * Planning a single Facility should behave exactly as before.

h2. Relationship to other tickets
 * OFBIZ-13462 (finite-capacity MRP) adds the Constrained and Unconstrained modes and each Facility's default mode, which a group run would use per member.
 * OFBIZ-13628 adds the Planning Rules and the MrpRun plan header. A group run would pick each Constrained member's rule, and add the cycle id to that header.
 * OFBIZ-7356 (transfers between facilities) would make the order of the members matter. Until then, a group run is essentially N independent runs.

h2. Behavioral Changes
 * *Breaking:* the warehouse-and-plant pairing is gone. To reproduce it, name the warehouse under Facility and leave Facility Group blank.
 * *Classic engine:* as a group member it would plan only from its own events, and would no longer clear the other members' events. Every Unconstrained run would now also write a plan header and return its mrpId. A single-Facility Unconstrained run is otherwise unchanged, including clearing the Unconstrained events of every Facility, and its output stays identical.

h2. Rules

*R1 — A Facility Group means every member, each one planned as it would be alone.* Members would get planned in turn, by FacilityGroupMember.sequenceNum and then by facility id, each one under its own mrpId and plan header, and the headers of one run would share one cycle id (mrpBatchId). With no mode given, each member would plan in its own default mode, so one group can mix Constrained and Unconstrained facilities; on Run MRP the empty choice reads "each facility's default", and a member with no default would plan Unconstrained. Each Constrained member would plan under its own default rule unless another one is chosen for the run. In other words, with nothing crossing the facilities, a group run would produce exactly the same plans that the members would produce one by one.

*R2 — A group run replaces its members' plans and nobody else's, all at once or not at all.* Before the first member gets planned, the previous plan of every member in the mode it is about to be planned in would get cleared, and no Facility outside the group would be touched. The whole run is one transaction, so if any member fails, every member would keep its previous plan, including the members already re-planned in the failed run.

*R3 — Where a machine stands decides whether it is one machine or a kind of machine.* A machine located at a Facility (FixedAsset.locatedAtFacilityId, else its machine group's) is one unit there, whereas a machine with no location is a kind of machine that every Facility has its own instance of, so each Facility would plan its own instance. No plan changes because of this. The run would report a Facility planning work on a machine that stands at another Facility, and a machine group whose members stand at different facilities.
h2. Data model

*New field*
 * MrpRun.mrpBatchId — joins the headers of one group run, and stays empty for a single-Facility run.

*MrpRun written by Unconstrained runs too*
 * The entity comes from OFBIZ-13628, so now every plan of record would have a header.

*Read, not changed:* FacilityGroupMember (members, sequenceNum, dates) and FixedAsset.locatedAtFacilityId (R3).

No seed or demo data is needed, and the column gets added at start-up.
h2. Services and Screens

*executeMrp for a group:*
||Attribute||Meaning||
|facilityGroupId|The group to plan. Refused together with facilityId.|
|mrpMode|Empty: each member's default. Given: every member.|
|memberRuleIds|Map of facilityId → mrpRuleId for the members whose rule was chosen. Form fields: memberRule_<facilityId>. On a job, as text: \{FAC_A=RULE_1, FAC_B=RULE_2}.|
|planningHorizonDays, mrpName, defaultYearsOffset|Passed to every member.|
|returns mrpBatchId, facilitiesPlanned, memberTimings|The cycle's id, the members in the order they ran, and for each member its run time along with the Constrained engine's phase timings (in milliseconds).|

*Which rule each member uses.* Rules are made eligible for a Facility Group on MRP Rules → a rule → Applies To (OFBIZ-13628), so a group run doesn't need any setting of its own. For each Constrained member, the run would:
 # collect the rules eligible for that member i.e., the ones made eligible, for the mode, for any Facility Group the member belongs to (the group being run, any other group, or _NA_), in force at the run's start;
 # order them by sequence number, then by facility group id, and then by rule id, listing each rule once at its best position;
 # use the first one as the member's default, unless the planner picks another one for that member on Run MRP, or a job passes one in memberRuleIds.

So, in practice:
 * *One policy for the whole group:* make a rule eligible for the group being run, and every member would use it by default.
 * *A different policy for one member:* make another rule eligible for a group containing just that member, at a lower sequence number. That member would then use it in every group run, scheduled ones included (CASE-9).
 * *A one-off change:* pick another rule for the member on Run MRP. It would apply to that run only, and the member's plan header would record it as chosen (isRuleOverridden = Y).
 * *No eligible rule:* the member would plan under the engine's defaults, and Run MRP would show Engine defaults.
 * *Two rules tied at the same sequence number:* the one from the lowest facility group id would be the default, and the log would say so, so that the planner can set distinct numbers.

*Screens*
 * *Run MRP* — choosing a group would list its members in run order, each with the mode it would be planned in. A Constrained member would have a rule drop-down listing exactly its eligible rules, with its default preselected (or showing Engine defaults), while an Unconstrained member wouldn't have any. Changing the group or the mode would redraw the list.
 * *MRP Rules → Applies To* (OFBIZ-13628) — where a rule is made eligible for a Facility Group, a mode and a period, at a sequence number.
 * *Plans of Record* — would add Facility Group, Cycle, MRP Run (linking to that plan's events) and the run name, newest first, beside each plan's rule and whether it was chosen; Unconstrained plans would appear too.
 * *Find Inventory Event Plan* — filter by MRP Run.

h2. Use cases

Each case uses three facilities of its own, each one stocking one bought product with a different Minimum Stock (5, 7 and 11) and no stock on hand. Each plan is then a single Requirement, so a plan leaking into another Facility would show up in its quantity.

*CASE-1 — A group of one would plan exactly what naming the Facility plans.*
*Setup:* plan a Facility alone in Constrained mode, and then plan a group whose only member is that Facility.
*Expected:* the same Requirements, and the same name, rule, choice, horizon and offset on the header, under a new mrpId.
*Why:* this is the promise the whole feature rests on. A member gets run exactly as the Facility would get run alone, and the cycle only adds the batch and the group to its header afterwards.

*CASE-2 — A group of two would plan what two separate runs plan.*
*Setup:* plan two facilities alone, and then plan a group of both.
*Expected:* identical Requirements for each of them.
*Why:* nothing crosses the facilities yet, so a group run is essentially n independent runs, and it is the transfer planning that would make the order and the cycle matter.

*CASE-3 — An empty group and an unknown group are errors.*
*Expected:* both runs would fail.
*Why:* a cycle that plans nothing and still reports success is the worst possible outcome, as the planner would believe that the network is planned.

*CASE-4 — Every member would have its own mrpId, and the cycle's one batch.*
*Setup:* plan a group of two.
*Expected:* two headers with different mrpIds, the same mrpBatchId, the group as facilityGroupId, and each member's events under its own mrpId.
*Why:* MrpEvent's key has no Facility in it, so two facilities under one mrpId would merge their rows. One mrpId per member is an invariant, and the batch is what says which plans were made together.

*CASE-5 — Members would run by sequence, then by Facility, the same way every time.*
*Setup:* three members at sequences 2, 1 and 2, and run the group twice.
*Expected:* the sequence-1 member first, and then the two sequence-2 members by facility id, in the same order both times.

*CASE-6 — A mixed group would plan each member in its own default mode, and an explicit mode would override them all.*
*Setup:* one member defaulting to Constrained and the other one to Unconstrained; run with no mode, and then with a mode.
*Expected:* with no mode, each one would plan in its own; with a mode, both would plan in it and neither would have a plan in the other mode.

*CASE-7 — A group run would leave the plan of a Facility outside the group alone, in either mode.*
*Setup:* plan a third Facility, alone in Constrained mode and as a group of one in Unconstrained mode, and then run a group of the other two in each mode.
*Expected:* the third Facility's header, events and Requirement would stay unchanged.
*Why:* a single-Facility Unconstrained run clears the Unconstrained events of every Facility, as the classic engine always has. A group run shouldn't do so, as its wipe reaches its own members only, finding their classic events (which carry no Facility) through their run headers.

*CASE-8 — Re-running a group would replace its members' plans rather than add to them.*
*Expected:* the previous cycle's events would be gone, and each member would still have exactly one Requirement.

*CASE-9 — Each member would plan under its own default rule unless a rule gets chosen for it.*
*Setup:* a rule eligible for the group at sequence 50, and a rule eligible for a group of one of its members at 10. Run with no choice, and then with the network rule chosen for that member.
*Expected:* with no choice, each member would use its own default and record N; with the choice, that member would use the chosen rule and record Y, while the other one would still record N.
*Why:* a lasting difference for one Facility is configuration (a group of one), while a one-off difference is a choice made for that run, and both would survive into every later group run exactly as configured.

*CASE-10 — A member that cannot be planned would fail the whole cycle, and every previous plan would survive.*
*Setup:* plan the group once, and then run it again naming, for its second member, a rule that is not eligible, and separately a rule whose eligibility has lapsed.
*Expected:* the run would fail, naming the rule and the member. The first member, which got cleared and re-planned inside the failed cycle before the second one was refused, would still have its previous header, events and Requirement.
*Why:* the cycle clears every member before planning the first one, so keeping the members planned before a failure would leave a network half re-planned and half blank, with nothing to say which half is which.

*CASE-11 — A rule would be refused for an Unconstrained member, for a Facility outside the group, and for a whole group at once.*
*Expected:* each one would get refused before anything is planned, naming what was wrong.

*CASE-12 — A job would pass the same choices as the screen.*
*Setup:* run the group with the member rules as the screen's fields (memberRule_<facility>), as a map, and as the text \{FAC_A=RULE_1, FAC_B=RULE_2}.
*Expected:* the same headers in every way.

*CASE-13 — An unlocated machine is every member's own, whereas a located one named from elsewhere would be reported but kept on its own calendar.*
*Setup:* a machine with no Facility, resolved from two facilities; a machine standing at one Facility, resolved from another one; and a machine group whose members stand at two facilities.
*Expected:* the unlocated machine would give each Facility a full instance and raise nothing. The located machine would raise a finding naming where it stands, and keep its real two-hour day rather than a default week. The group would raise a finding naming both facilities.
*Why:* refusing a machine owned by another Facility would hand the planning Facility the default calendar instead i.e., an invented machine, which is worse than a double-booked real one.

*CASE-14 — A member with no eligible rule would plan under the engine's defaults, beside the members that have one.*
*Setup:* a group of two members, and a rule eligible only for a group of one of them. Run the group, Constrained, with no rule chosen.
*Expected:* that member would plan under its rule and record N, while the other one would plan under the engine's defaults, and its header would record no rule and an empty "Chosen Instead of Default".
*Why:* rules are settled member by member, so one member's rule would never reach another one, and a member with no policy configured would still get planned rather than failing the cycle.
h2. Out of scope
 * {*}Nested groups{*}, as only a group's direct members get planned.
 * *A machine shared by two members.* Each member plans against its own capacity, so both of them can book it; R3 reports it, but nothing prevents it.
 * {*}Transfers between members{*}, which is OFBIZ-7356.
 * *Unconstrained events from before this change* have no header, so a group run cannot attribute them and would leave them as they are; the next single-Facility Unconstrained run would remove them.
 * {*}Long runs{*}, as the whole run is one transaction within the two-hour timeout of executeMrp.

  was:
h2. Rationale

A business with a plant and several warehouses has to run MRP once per facility, and nothing ties those runs together. Run MRP offers a Facility Group, which looks like a network run but is not: it plans only the group's {*}first WAREHOUSE and first PLANT{*}, as a single pairing, and ignores every other member.

The MRP engine also cannot safely plan several facilities one after another. Each run deletes every facility's MRP events, not just its own, and then plans from whatever events it finds.
h2. Requirements
 * Choosing a facility group plans {*}every member{*}, each exactly as it would be planned on its own.
 * Each member gets {*}its own plan{*}, identifiable on its own, and the plans of one run are grouped by a {*}cycle id{*}.
 * The run replaces *only its members'* previous plans, and {*}all or nothing{*}: if any member fails, every member keeps its previous plan.
 * Members are planned in a {*}fixed, repeatable order{*}.
 * Each member plans in *its own MRP mode and planning rule* by default; a run may override either.
 * A *scheduled job* can pass the same choices as the screen.
 * Planning a single facility behaves {*}exactly as before{*}.

h2. Relationship to other tickets
 * *OFBIZ-13462* (finite-capacity MRP) adds the Constrained and Unconstrained modes and each facility's default mode, which a group run uses per member.
 * *OFBIZ-13628* adds planning rules and the {{MrpRun}} plan header. A group run picks each Constrained member's rule, and adds the cycle id to that header.
 * *OFBIZ-7356* (transfers between facilities) will make the order of members matter. Until then, a group run is N independent runs.

h2. Behavioral Changes
 * *Breaking:* the warehouse-and-plant pairing is gone. To reproduce it, name the warehouse under Facility and leave Facility Group blank.
 * *Classic engine:* as a group member it plans only from its own events, and no longer clears the other members'. Every Unconstrained run now also writes a plan header and returns its {{{}mrpId{}}}. A single-facility Unconstrained run is otherwise unchanged, including clearing every facility's unconstrained events, and its output is identical.

h2. Rules
 * _{*}R1 — A facility group means every member, each planned as it would be alone{*}._ Members are planned in turn, by {{FacilityGroupMember.sequenceNum}} then facility id, each under its own {{mrpId}} and plan header; the headers of one run share one cycle id ({{{}mrpBatchId{}}}). With no mode given, each member plans in its own default mode, so one group can mix Constrained and Unconstrained facilities; on Run MRP the empty choice reads "each facility's default", and a member with no default plans Unconstrained. Each Constrained member plans under its own default rule unless another is chosen for the run. With nothing crossing facilities, a group run produces exactly the plans the members would produce one by one.
 * _{*}R2 — A group run replaces its members' plans and nobody else's, all at once or not at all{*}._ Before the first member is planned, every member's previous plan in the mode it is about to be planned in is cleared, and no facility outside the group is touched. The whole run is one transaction: if any member fails, every member keeps its previous plan, including members already re-planned in the failed run.
 * _{*}R3 — Where a machine stands decides whether it is one machine or a kind of machine{*}._ A machine located at a facility ({{{}FixedAsset.locatedAtFacilityId{}}}, else its machine group's) is one unit there; a machine with no location is a kind of machine every facility has its own of, so each facility plans its own instance. No plan changes because of this. The run reports a facility planning work on a machine that stands at another facility, and a machine group whose members stand at different facilities.

h2. Data model
 * *New field*
 ** {{MrpRun.mrpBatchId}} — joins the headers of one group run; empty for a single-facility run.
 * *{{MrpRun}} written by Unconstrained runs too*
 ** the entity comes from OFBIZ-13628, so every plan of record has a header.
 * *Read, not changed:* 
 ** {{FacilityGroupMember}} (members, {{{}sequenceNum{}}}, dates) and {{FixedAsset.locatedAtFacilityId}} (R3).
 * No seed or demo data. The column is added at start-up.

h2. Services and Screens

{{executeMrp}} for a group:
||Attribute||Meaning||
|{{facilityGroupId}}|The group to plan. Refused together with {{{}facilityId{}}}.|
|{{mrpMode}}|Empty: each member's default. Given: every member.|
|{{memberRuleIds}}|Map of {{facilityId}} → {{mrpRuleId}} for members whose rule was chosen. Form fields: {{{}memberRule_<facilityId>{}}}. On a job, as text: {{{}{FAC_A=RULE_1, FAC_B=RULE_2}{}}}.|
|{{{}planningHorizonDays{}}}, {{{}mrpName{}}}, {{defaultYearsOffset}}|Passed to every member.|
|returns {{{}mrpBatchId{}}}, {{facilitiesPlanned}}|The cycle's id, and the members in the order they ran.|

 

*Which rule each member uses.* Rules are made eligible for a facility group on *MRP Rules → a rule → Applies To* OFBIZ-13628 ; a group run needs no setting of its own. For each Constrained member, the run:
 # collects the rules eligible for that member — those made eligible, for the mode, for *any* facility group the member belongs to (the group being run, any other group, or {{{}_NA_{}}}), in force at the run's start;
 # orders them by sequence number, then facility group id, then rule id, listing each rule once at its best position;
 # uses the first as the member's default, unless the planner picks another for that member on Run MRP, or a job passes one in {{{}memberRuleIds{}}}.

So, in practice:
 * *One policy for the whole group:* make a rule eligible for the group being run. Every member uses it by default.
 * *A different policy for one member:* make another rule eligible for a group containing just that member, at a lower sequence number. That member uses it in every group run, scheduled ones included (CASE-9).
 * *A one-off change:* pick another rule for the member on Run MRP. It applies to that run only, and the member's plan header records it as chosen ({{{}isRuleOverridden{}}} = Y).
 * *No eligible rule:* the member plans under the engine's defaults, and Run MRP shows {_}Engine defaults{_}.
 * *Two rules tied* at the same sequence number: the one from the lowest facility group id is the default, and the log says so, so the planner can set distinct numbers.

*Screens*
 * *Run MRP* — choosing a group lists its members in run order, each with the mode it will be planned in. A Constrained member has a rule drop-down listing exactly its eligible rules, with its default preselected, or shows {_}Engine defaults{_}; an Unconstrained member has none. Changing the group or the mode redraws the list.
 * *MRP Rules → Applies To* (OFBIZ-13628) — where a rule is made eligible for a facility group, a mode and a period, at a sequence number.
 * *Plans of Record* — adds Facility Group, Cycle, MRP Run (linking to that plan's events) and the run name, newest first, beside each plan's rule and whether it was chosen; Unconstrained plans appear too.
 * *Find Inventory Event Plan* — filter by MRP Run.

h2. Use cases

Each case uses three facilities of its own, each stocking one bought product with a different minimum stock (5, 7 and 11) and no stock. Each plan is then a single requirement, and a plan leaking into another facility shows in its quantity.

*CASE-1 — A group of one plans exactly what naming the facility plans.*

_{*}Setup{*}:_ plan a facility alone in constrained mode; then plan a group whose only member is that facility.

_{*}Expected{*}:_ the same requirements, and the same name, rule, choice, horizon and offset on the header — under a new {{{}mrpId{}}}.

_{*}Why{*}:_ this is the promise the whole feature rests on. A member is run exactly as the facility would be run alone; the cycle adds only the batch and the group to its header afterwards.

*CASE-2 — A group of two plans what two separate runs plan.*

_{*}Setup{*}:_ plan two facilities alone; then plan a group of both.

_{*}Expected{*}:_ identical requirements for each.

_{*}Why{*}:_ nothing crosses facilities yet, so a group run is n independent runs. Transfer planning is what will make the order and the cycle matter.

*CASE-3 — An empty group and an unknown group are errors.*

_{*}Expected{*}:_ both runs fail.

_{*}Why{*}:_ a cycle that plans nothing and reports success is the worst outcome available — the planner believes the network is planned.

*CASE-4 — Every member has its own mrpId, and the cycle's one batch.*

_{*}Setup{*}:_ plan a group of two.

_{*}Expected{*}:_ two headers with different {{{}mrpId{}}}s, the same {{{}mrpBatchId{}}}, the group as {{{}facilityGroupId{}}}, and each member's events under its own {{{}mrpId{}}}.

_{*}Why{*}:_ {{{}MrpEvent{}}}'s key has no facility in it, so two facilities under one {{mrpId}} would merge their rows. One {{mrpId}} per member is an invariant, and the batch is what says which plans were made together.

*CASE-5 — Members run by sequence, then facility, the same way every time.*

_{*}Setup{*}:_ three members at sequences 2, 1 and 2; run the group twice.

_{*}Expected{*}:_ the sequence-1 member first, then the two sequence-2 members by facility id — the same order both times.

*CASE-6 — A mixed group plans each member in its own default mode, and an explicit mode overrides them all.*

_{*}Setup{*}:_ one member defaulting to constrained, the other to unconstrained; run with no mode, then with a mode.

_{*}Expected{*}:_ with no mode, each plans in its own; with a mode, both plan in it and neither has a plan in the other mode.

*CASE-7 — A group run leaves the plan of a facility outside the group alone, in either mode.*

_{*}Setup{*}:_ plan a third facility — alone in constrained mode, and as a group of one in unconstrained mode; then run a group of the other two in each mode.

_{*}Expected{*}:_ the third facility's header, events and requirement are unchanged.

_{*}Why{*}:_ a single-facility unconstrained run clears every facility's unconstrained events, as the classic engine always has. A group run may not: its wipe reaches its members only, finding their classic events — which carry no facility — through their run headers.

*CASE-8 — Re-running a group replaces its members' plans rather than adding to them.*

_{*}Expected{*}:_ the previous cycle's events are gone, and each member still has exactly one requirement.

*CASE-9 — Each member plans under its own default rule unless a rule is chosen for it.*

_{*}Setup{*}:_ a rule eligible for the group at sequence 50, and a rule eligible for a group of one of its members at 10. Run with no choice, then with the network rule chosen for that member.

_{*}Expected{*}:_ with no choice, each member uses its own default and records {{{}N{}}}; with the choice, that member uses the chosen rule and records {{{}Y{}}}, and the other still records {{{}N{}}}.

_{*}Why{*}:_ a lasting difference for one facility is configuration — a group of one — and a one-off difference is a choice made for that run. Both survive into every later group run exactly as configured.

*CASE-10 — A member that cannot be planned fails the whole cycle, and every previous plan survives.*

_{*}Setup{*}:_ plan the group once; then run it again naming, for its second member, a rule that is not eligible — and separately a rule whose eligibility has lapsed.

_{*}Expected{*}:_ the run fails, naming the rule and the member. The first member — cleared and re-planned inside the failed cycle before the second was refused — still has its previous header, events and requirement.

_{*}Why{*}:_ the cycle clears every member before planning the first, so keeping the members planned before a failure would leave a network half re-planned and half blank, with nothing to say which half is which.

*CASE-11 — A rule is refused for an unconstrained member, for a facility outside the group, and for a whole group at once.*

_{*}Expected{*}:_ each refused before anything is planned, naming what was wrong.

*CASE-12 — A job passes the same choices as the screen.*

_{*}Setup{*}:_ run the group with the member rules as the screen's fields ({{{}memberRule_<facility>{}}}), as a map, and as the text \{FAC_A=RULE_1, FAC_B=RULE_2}

_{*}Expected{*}:_ the same headers every way.

*CASE-13 — An unlocated machine is every member's own; a located one named from elsewhere is reported but kept on its own calendar.*

_{*}Setup{*}:_ a machine with no facility, resolved from two facilities; a machine standing at one facility, resolved from another; a machine group whose members stand at two facilities.
 
_{*}Expected{*}:_ the unlocated machine gives each facility a full instance and raises nothing. The located machine raises a finding naming where it stands, and keeps its real two-hour day rather than a default week. The group raises a finding naming both facilities.

_{*}Why{*}:_ refusing a machine another facility owns would hand the planning facility the default calendar instead — an invented machine, which is worse than a double-booked real one.

*CASE-14 — A member with no eligible rule plans under the engine's defaults, beside members that have one.*

_{*}Setup{*}:_ a group of two members; a rule eligible only for a group of one of them. Run the group, Constrained, with no rule chosen.

_{*}Expected{*}:_ that member plans under its rule and records {{{}N{}}}. The other plans under the engine's defaults, and its header records no rule and an empty "Chosen Instead of Default".

_{*}Why{*}:_ rules are settled member by member, so one member's rule never reaches another, and a member with no policy configured still plans rather than failing the cycle.
h2. Out of scope
 * *Nested groups* — only a group's direct members are planned.
 * *A machine shared by two members* — each member plans against its own capacity, so both can book it. R3 reports it; nothing prevents it.
 * *Transfers between members* — OFBIZ-7356.
 * *Unconstrained events from before this change* have no header, so a group run cannot attribute them and leaves them; the next single-facility Unconstrained run removes them.
 * *Long runs* — the whole run is one transaction, within {{{}executeMrp{}}}'s two-hour timeout.


> OFBIZ-13462:  MRP Run for a Facility Group 
> -------------------------------------------
>
>                 Key: OFBIZ-13630
>                 URL: https://issues.apache.org/jira/browse/OFBIZ-13630
>             Project: OFBiz
>          Issue Type: Improvement
>          Components: manufacturing
>            Reporter: Swapnil Shah
>            Priority: Major
>
> h2. Rationale
> A business with a plant and several warehouses currently has to run MRP once per Facility, and nothing ties those runs together. Run MRP does offer a Facility Group, which looks like a network run but in fact it is not, as it plans only the group's first WAREHOUSE and first PLANT as a single pairing and ignores every other member.
> Also, the MRP engine cannot safely plan several facilities one after another, because each run deletes the MRP events of every Facility (and not just its own) and then plans from whatever events it finds.
> h2. Requirements
>  * Choosing a Facility Group should plan every member, each one exactly as it would get planned on its own.
>  * Each member should get its own plan, identifiable on its own, and the plans of one run should be grouped by a cycle id.
>  * The run should replace only its members' previous plans, and all or nothing i.e., if any member fails, every member would keep its previous plan.
>  * Members should get planned in a fixed and repeatable order.
>  * By default, each member should plan in its own MRP Mode and under its own Planning Rule, though a run could override either of them.
>  * A scheduled job should be able to pass the same choices as the screen.
>  * Planning a single Facility should behave exactly as before.
> h2. Relationship to other tickets
>  * OFBIZ-13462 (finite-capacity MRP) adds the Constrained and Unconstrained modes and each Facility's default mode, which a group run would use per member.
>  * OFBIZ-13628 adds the Planning Rules and the MrpRun plan header. A group run would pick each Constrained member's rule, and add the cycle id to that header.
>  * OFBIZ-7356 (transfers between facilities) would make the order of the members matter. Until then, a group run is essentially N independent runs.
> h2. Behavioral Changes
>  * *Breaking:* the warehouse-and-plant pairing is gone. To reproduce it, name the warehouse under Facility and leave Facility Group blank.
>  * *Classic engine:* as a group member it would plan only from its own events, and would no longer clear the other members' events. Every Unconstrained run would now also write a plan header and return its mrpId. A single-Facility Unconstrained run is otherwise unchanged, including clearing the Unconstrained events of every Facility, and its output stays identical.
> h2. Rules
> *R1 — A Facility Group means every member, each one planned as it would be alone.* Members would get planned in turn, by FacilityGroupMember.sequenceNum and then by facility id, each one under its own mrpId and plan header, and the headers of one run would share one cycle id (mrpBatchId). With no mode given, each member would plan in its own default mode, so one group can mix Constrained and Unconstrained facilities; on Run MRP the empty choice reads "each facility's default", and a member with no default would plan Unconstrained. Each Constrained member would plan under its own default rule unless another one is chosen for the run. In other words, with nothing crossing the facilities, a group run would produce exactly the same plans that the members would produce one by one.
> *R2 — A group run replaces its members' plans and nobody else's, all at once or not at all.* Before the first member gets planned, the previous plan of every member in the mode it is about to be planned in would get cleared, and no Facility outside the group would be touched. The whole run is one transaction, so if any member fails, every member would keep its previous plan, including the members already re-planned in the failed run.
> *R3 — Where a machine stands decides whether it is one machine or a kind of machine.* A machine located at a Facility (FixedAsset.locatedAtFacilityId, else its machine group's) is one unit there, whereas a machine with no location is a kind of machine that every Facility has its own instance of, so each Facility would plan its own instance. No plan changes because of this. The run would report a Facility planning work on a machine that stands at another Facility, and a machine group whose members stand at different facilities.
> h2. Data model
> *New field*
>  * MrpRun.mrpBatchId — joins the headers of one group run, and stays empty for a single-Facility run.
> *MrpRun written by Unconstrained runs too*
>  * The entity comes from OFBIZ-13628, so now every plan of record would have a header.
> *Read, not changed:* FacilityGroupMember (members, sequenceNum, dates) and FixedAsset.locatedAtFacilityId (R3).
> No seed or demo data is needed, and the column gets added at start-up.
> h2. Services and Screens
> *executeMrp for a group:*
> ||Attribute||Meaning||
> |facilityGroupId|The group to plan. Refused together with facilityId.|
> |mrpMode|Empty: each member's default. Given: every member.|
> |memberRuleIds|Map of facilityId → mrpRuleId for the members whose rule was chosen. Form fields: memberRule_<facilityId>. On a job, as text: \{FAC_A=RULE_1, FAC_B=RULE_2}.|
> |planningHorizonDays, mrpName, defaultYearsOffset|Passed to every member.|
> |returns mrpBatchId, facilitiesPlanned, memberTimings|The cycle's id, the members in the order they ran, and for each member its run time along with the Constrained engine's phase timings (in milliseconds).|
> *Which rule each member uses.* Rules are made eligible for a Facility Group on MRP Rules → a rule → Applies To (OFBIZ-13628), so a group run doesn't need any setting of its own. For each Constrained member, the run would:
>  # collect the rules eligible for that member i.e., the ones made eligible, for the mode, for any Facility Group the member belongs to (the group being run, any other group, or _NA_), in force at the run's start;
>  # order them by sequence number, then by facility group id, and then by rule id, listing each rule once at its best position;
>  # use the first one as the member's default, unless the planner picks another one for that member on Run MRP, or a job passes one in memberRuleIds.
> So, in practice:
>  * *One policy for the whole group:* make a rule eligible for the group being run, and every member would use it by default.
>  * *A different policy for one member:* make another rule eligible for a group containing just that member, at a lower sequence number. That member would then use it in every group run, scheduled ones included (CASE-9).
>  * *A one-off change:* pick another rule for the member on Run MRP. It would apply to that run only, and the member's plan header would record it as chosen (isRuleOverridden = Y).
>  * *No eligible rule:* the member would plan under the engine's defaults, and Run MRP would show Engine defaults.
>  * *Two rules tied at the same sequence number:* the one from the lowest facility group id would be the default, and the log would say so, so that the planner can set distinct numbers.
> *Screens*
>  * *Run MRP* — choosing a group would list its members in run order, each with the mode it would be planned in. A Constrained member would have a rule drop-down listing exactly its eligible rules, with its default preselected (or showing Engine defaults), while an Unconstrained member wouldn't have any. Changing the group or the mode would redraw the list.
>  * *MRP Rules → Applies To* (OFBIZ-13628) — where a rule is made eligible for a Facility Group, a mode and a period, at a sequence number.
>  * *Plans of Record* — would add Facility Group, Cycle, MRP Run (linking to that plan's events) and the run name, newest first, beside each plan's rule and whether it was chosen; Unconstrained plans would appear too.
>  * *Find Inventory Event Plan* — filter by MRP Run.
> h2. Use cases
> Each case uses three facilities of its own, each one stocking one bought product with a different Minimum Stock (5, 7 and 11) and no stock on hand. Each plan is then a single Requirement, so a plan leaking into another Facility would show up in its quantity.
> *CASE-1 — A group of one would plan exactly what naming the Facility plans.*
> *Setup:* plan a Facility alone in Constrained mode, and then plan a group whose only member is that Facility.
> *Expected:* the same Requirements, and the same name, rule, choice, horizon and offset on the header, under a new mrpId.
> *Why:* this is the promise the whole feature rests on. A member gets run exactly as the Facility would get run alone, and the cycle only adds the batch and the group to its header afterwards.
> *CASE-2 — A group of two would plan what two separate runs plan.*
> *Setup:* plan two facilities alone, and then plan a group of both.
> *Expected:* identical Requirements for each of them.
> *Why:* nothing crosses the facilities yet, so a group run is essentially n independent runs, and it is the transfer planning that would make the order and the cycle matter.
> *CASE-3 — An empty group and an unknown group are errors.*
> *Expected:* both runs would fail.
> *Why:* a cycle that plans nothing and still reports success is the worst possible outcome, as the planner would believe that the network is planned.
> *CASE-4 — Every member would have its own mrpId, and the cycle's one batch.*
> *Setup:* plan a group of two.
> *Expected:* two headers with different mrpIds, the same mrpBatchId, the group as facilityGroupId, and each member's events under its own mrpId.
> *Why:* MrpEvent's key has no Facility in it, so two facilities under one mrpId would merge their rows. One mrpId per member is an invariant, and the batch is what says which plans were made together.
> *CASE-5 — Members would run by sequence, then by Facility, the same way every time.*
> *Setup:* three members at sequences 2, 1 and 2, and run the group twice.
> *Expected:* the sequence-1 member first, and then the two sequence-2 members by facility id, in the same order both times.
> *CASE-6 — A mixed group would plan each member in its own default mode, and an explicit mode would override them all.*
> *Setup:* one member defaulting to Constrained and the other one to Unconstrained; run with no mode, and then with a mode.
> *Expected:* with no mode, each one would plan in its own; with a mode, both would plan in it and neither would have a plan in the other mode.
> *CASE-7 — A group run would leave the plan of a Facility outside the group alone, in either mode.*
> *Setup:* plan a third Facility, alone in Constrained mode and as a group of one in Unconstrained mode, and then run a group of the other two in each mode.
> *Expected:* the third Facility's header, events and Requirement would stay unchanged.
> *Why:* a single-Facility Unconstrained run clears the Unconstrained events of every Facility, as the classic engine always has. A group run shouldn't do so, as its wipe reaches its own members only, finding their classic events (which carry no Facility) through their run headers.
> *CASE-8 — Re-running a group would replace its members' plans rather than add to them.*
> *Expected:* the previous cycle's events would be gone, and each member would still have exactly one Requirement.
> *CASE-9 — Each member would plan under its own default rule unless a rule gets chosen for it.*
> *Setup:* a rule eligible for the group at sequence 50, and a rule eligible for a group of one of its members at 10. Run with no choice, and then with the network rule chosen for that member.
> *Expected:* with no choice, each member would use its own default and record N; with the choice, that member would use the chosen rule and record Y, while the other one would still record N.
> *Why:* a lasting difference for one Facility is configuration (a group of one), while a one-off difference is a choice made for that run, and both would survive into every later group run exactly as configured.
> *CASE-10 — A member that cannot be planned would fail the whole cycle, and every previous plan would survive.*
> *Setup:* plan the group once, and then run it again naming, for its second member, a rule that is not eligible, and separately a rule whose eligibility has lapsed.
> *Expected:* the run would fail, naming the rule and the member. The first member, which got cleared and re-planned inside the failed cycle before the second one was refused, would still have its previous header, events and Requirement.
> *Why:* the cycle clears every member before planning the first one, so keeping the members planned before a failure would leave a network half re-planned and half blank, with nothing to say which half is which.
> *CASE-11 — A rule would be refused for an Unconstrained member, for a Facility outside the group, and for a whole group at once.*
> *Expected:* each one would get refused before anything is planned, naming what was wrong.
> *CASE-12 — A job would pass the same choices as the screen.*
> *Setup:* run the group with the member rules as the screen's fields (memberRule_<facility>), as a map, and as the text \{FAC_A=RULE_1, FAC_B=RULE_2}.
> *Expected:* the same headers in every way.
> *CASE-13 — An unlocated machine is every member's own, whereas a located one named from elsewhere would be reported but kept on its own calendar.*
> *Setup:* a machine with no Facility, resolved from two facilities; a machine standing at one Facility, resolved from another one; and a machine group whose members stand at two facilities.
> *Expected:* the unlocated machine would give each Facility a full instance and raise nothing. The located machine would raise a finding naming where it stands, and keep its real two-hour day rather than a default week. The group would raise a finding naming both facilities.
> *Why:* refusing a machine owned by another Facility would hand the planning Facility the default calendar instead i.e., an invented machine, which is worse than a double-booked real one.
> *CASE-14 — A member with no eligible rule would plan under the engine's defaults, beside the members that have one.*
> *Setup:* a group of two members, and a rule eligible only for a group of one of them. Run the group, Constrained, with no rule chosen.
> *Expected:* that member would plan under its rule and record N, while the other one would plan under the engine's defaults, and its header would record no rule and an empty "Chosen Instead of Default".
> *Why:* rules are settled member by member, so one member's rule would never reach another one, and a member with no policy configured would still get planned rather than failing the cycle.
> h2. Out of scope
>  * {*}Nested groups{*}, as only a group's direct members get planned.
>  * *A machine shared by two members.* Each member plans against its own capacity, so both of them can book it; R3 reports it, but nothing prevents it.
>  * {*}Transfers between members{*}, which is OFBIZ-7356.
>  * *Unconstrained events from before this change* have no header, so a group run cannot attribute them and would leave them as they are; the next single-Facility Unconstrained run would remove them.
>  * {*}Long runs{*}, as the whole run is one transaction within the two-hour timeout of executeMrp.



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