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/08 13:15:00 UTC
[jira] [Updated] (OFBIZ-13666) OFBIZ-6964: Document Scope & Design Revision to support Inter-Facility Transfer Planning in the Constrained MRP
[ https://issues.apache.org/jira/browse/OFBIZ-13666?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Swapnil Shah updated OFBIZ-13666:
---------------------------------
Summary: OFBIZ-6964: Document Scope & Design Revision to support Inter-Facility Transfer Planning in the Constrained MRP (was: OFBIZ-6964: Document Design Revision to support Inter-Facility Transfer Planning in the Constrained MRP)
> OFBIZ-6964: Document Scope & Design Revision to support Inter-Facility Transfer Planning in the Constrained MRP
> ---------------------------------------------------------------------------------------------------------------
>
> Key: OFBIZ-13666
> URL: https://issues.apache.org/jira/browse/OFBIZ-13666
> Project: OFBiz
> Issue Type: Task
> Components: manufacturing, mrp
> Reporter: Swapnil Shah
> Priority: Major
>
> h2. Rationale
> A business running a network of facilities, e.g., a plant, a regional distribution center and a few stores, usually replenishes a short Facility from stock the network already holds before it buys or makes anything more. MRP in OFBiz has no notion of that today, so every Facility plans on its own and a store that is short would simply raise a purchase or a production requirement, even when the distribution center next door is holding more than enough.
> This ticket takes up from where OFBIZ-7356 ended. The design documented there, and the revision I shared in its last comment, have now been finalized, and the finalized approach is built on top of the constrained MRP engine (OFBIZ-13462), the MRP planning rules (OFBIZ-13628) and the MRP run for a Facility Group (OFBIZ-13630), so the work would continue here, and this ticket supersedes the patch attached to OFBIZ-9506.
> In short, a shortfall at a Facility would first get sourced, whole, from the first Facility on its configured lanes that can spare all of it, and only what no source can cover would get made or bought (a transfer itself works at the Product level and never explodes the Bill of Materials), either at the Facility that needs it or at the last source on its lanes, as per the Planning Rule. Compared with the last comment on OFBIZ-7356, the main differences are that transfer is switched on through the Planning Rule rather than a separate planning mode, a shortfall is never split across several sources, lanes can also run between Facility Groups, and transit time is the carriage only, with the source's days-to-ship added to it.
> h2. Requirements
> * A shortfall that MRP computes at a Facility should first be sourced from another Facility on that Facility's configured lanes, before anything gets made or bought.
> * The lanes should be data: between Facility Groups (including a group with itself, which makes its members peers), per Facility, and per Product as an exception.
> * A source should only give what it can spare, i.e., what its own MRP run would leave for the transfer after the demands it serves first, and never below its Minimum Stock unless its own Planning Rule allows it.
> * A shortfall should be sourced whole from one source or not at all, so every shortfall gets answered by exactly one Requirement.
> * A transfer should work at the Product level only, i.e., generating a transfer requirement should not explode the Bill of Materials or book any machine time.
> * Transfer dates should come from the source's days-to-ship and the transit time between the two facilities, walked through the source's SHIPPING calendar, and a transfer that cannot arrive on time should be flagged Late.
> * Approved transfer Requirements and Inventory Transfers already in flight should get counted by every run, as supply at the destination and as demand at the source.
> * What no source can cover should get made or bought at the Facility that needs it, or sent to the last source on its lanes, as per the Planning Rule.
> * A Facility Group run should plan the whole network in one run, in an order that always plans a Facility before the facilities it draws on.
> * The feature should be opt-in through the Planning Rule, so with no lanes and no rule switching it on, nothing changes for an existing installation, and the Unconstrained MRP should not change at all.
> * A planner should see on the Requirement screens which Facility a transfer comes from, and whether it asks the source to plan the quantity.
> h2. Relationship to other tickets
> * *OFBIZ-13462* (constrained MRP) is the engine this extends: transfers get proposed in the Constrained mode only, order by order, at the one place a shortfall gets decided.
> * *OFBIZ-13628* (MRP planning rules) carries the three new settings, and transfer demand takes the free slot in its demand priority ladder.
> * *OFBIZ-13630* (MRP run for a Facility Group) is how a network gets planned, and this ticket adds the planning order by level to it.
> * *OFBIZ-7356* and *OFBIZ-9506* are superseded by this ticket. *OFBIZ-6964* is the original requirement.
> The patch would apply on top of the combined patch of OFBIZ-13462, OFBIZ-13628 and OFBIZ-13630.
> h2. Behavioral Changes
> * With no lanes configured and no Planning Rule setting MRS_GEN_XFER_REQ to Y, a Constrained run plans exactly as before, except that approved transfer Requirements and Inventory Transfers in flight, if there are any, now get counted.
> * Requirement.facilityIdTo and MrpEvent.facilityIdTo get removed. They were leftovers of the OFBIZ-9506 convention in which facilityId meant the source, nothing reads them, and keeping them beside the new facilityIdFrom would leave two columns that each look like the other end of the transfer. OFBiz doesn't drop columns, so an existing database would keep them unused.
> * The lane type BACKUP_WAREHOUSE gets renamed to BACKUP_FACILITY, since a lane can run between any kind of facility. Existing rows need a two-statement migration, which the patch readme carries.
> * The six GZ-8544 ProductFacilityAssoc demo rows, which got committed with the source in facilityIdTo, get flipped so that lanes read source-first, in line with InventoryTransfer.
> * A Facility Group run in which at least one member plans in the Constrained mode plans its members by level (see R12), and a Facility that its groups give two levels would stop the run before anything gets planned. A group whose members all plan Unconstrained behaves exactly as before.
> * An MRP event can never merge two facilities any more: a write that would add one Facility's quantity into another Facility's event stops the run instead.
> h2. Rules
> *R1 — A transfer moves only what the source can spare, and only from a source that can spare all of it.* What a source can spare would be decided the way its own MRP run would serve it: within the planning horizon, the demands its Planning Rule ranks ahead of transfers, e.g., its own Sales Orders and Production Runs, come first irrespective of their dates, followed by the transfers already claimed on it that ship no later, and supply gets counted from the date it really lands. Forecasts and approved but unreleased Production Run Requirements are intentions, so they don't hold back any stock. A transfer is never a reason for the source to buy or make anything.
> *R2 — The source's Minimum Stock is a floor by default.* A transfer would not draw the source below its Minimum Stock, unless the source's own rule sets MRS_XFER_KEEP_MINSTK to N, in which case the source can go down to zero and would rebuild its buffer on its own next run.
> *R3 — A transfer works at the Product level, and the Bill of Materials never gets exploded for it.* It works at the Product level only, i.e., a transfer requirement is for the Product that is short, so generating it would not explode the Bill of Materials, no routing or machine time gets consulted or booked, and no component demand gets raised, neither at the destination nor at the source. In other words, the part of a shortfall that a transfer covers passes nothing down to its components, and only what no source can cover gets made or bought, with the Bill of Materials exploded as usual.
> *R4 — A source that cannot deliver the whole quantity inside the planning horizon is no candidate,* and nothing gets reserved at it. A transfer is never compared with buying, i.e., with transfers on, the search runs first and only what it cannot find gets made or bought.
> *R5 — Sources are tried in their configured order, in time first and then the earliest late arrival.* The first candidate that can supply the whole shortfall by its need date would supply it; failing that, the one whose transfer would arrive earliest within the horizon, marked Late; failing that, R13. A candidate that cannot cover all of it gets passed over, never drawn down.
> *R6 — A Product's own list replaces everything else.* ProductFacilityAssoc rows for a Product at a destination are its complete list, and the group and facility lanes are not consulted for that Product.
> *R7 — Getting the goods out of the door and moving them are two separate legs,* the source's ProductFacility.daysToShip (else Facility.defaultDaysToShip) and the pair's FacilityAssoc.transitTime, both walked through the source's SHIPPING calendar, or counted as plain days where it has none.
> *R8 — An approved transfer is demand at its source,* served after the source's own Sales Orders, Production Runs and approved Production Run Requirements and ahead of its Minimum Stock (or wherever its rule ranks MRP_DMD_INV_TRANSFER). It consumes and never proposes, so whatever it cannot get gets recorded on it as Short.
> *R9 — Only approved transfers and the transfers proposed in the same run count as claims on a source.* A proposed transfer from an earlier run is never read, as it gets deleted when its destination is re-planned. Point to note here is that it is the planning process's job, and not the engine's, to review, approve or reject proposed Requirements before triggering the next run.
> *R10 — A run records events only for the Facility it plans.* An inbound transfer shows at the destination naming its source, and nothing gets written at the source.
> *R11 — Transfers already agreed or already moving get counted by every run,* irrespective of the Planning Rule. Only whether new ones get proposed is configurable.
> *R12 — The planning order is the level, then the run's own sequence.* A Facility Group run would plan its members by the facilityGroupLevel of their groups, highest first, i.e., stores before the distribution center and the distribution center before the plant, then by FacilityGroupMember.sequenceNum in the group being run. A member with no level gets planned first, and a group lane pointing against the levels gets ignored and reported.
> *R13 — Whatever no single source can cover gets made or bought where the destination's rule says.* MRS_XFER_RESID_ENUM = MRP_RESID_DEST, the default, makes or buys it at the destination, whereas MRP_RESID_SOURCE sends it whole to the last source on the destination's list (R17).
> *R14 — Within a Facility Group run, a Facility draws only on facilities planned after it.* A source outside the run is always allowed, and a single Facility run applies its lanes as configured.
> *R15 — A pair with no transit time recorded gets planned at zero carriage,* and the run lists the pair in its messages, so a missing row never removes a source silently.
> *R16 — A Facility that no group lane reaches uses its own FacilityAssoc lanes.* Being a member of a group doesn't switch them off; being reached by a group lane does, and then its FacilityAssoc rows supply the transit times only.
> *R17 — A residual is one request for the whole shortfall to the last source on the list.* It gets raised as a transfer Requirement marked isXferResidual and Short, and the destination raises nothing else for it. The source plans it like its own demand, serving what it can spare and sourcing the rest as it would source any shortfall of its own, in the same Facility Group run while proposed and in every run once approved. With no candidate at all, the shortfall stays at the destination and the run says so.
> h2. Data model
> h3. Retained from OFBIZ-7356
> Most of what OFBIZ-7356 added to the data model is already in OFBiz, and the finalized approach would keep it, with the changes below.
> ||Item (already in OFBiz)||How it would be used now||
> |ProductFacilityAssoc (entity)|Retained as the per Product exception: the rows for a Product at a destination are its complete source list, in sequenceNum order, and replace the group and facility lanes for that Product (R6). It reads source-first, i.e., facilityId is the source and facilityIdTo the destination, as the definition's From / To relations already say. Its transitTime column stays, but is not read, since the transit time lives on FacilityAssoc per Facility pair. A new index PRFACASSOC_TO_IDX (facilityIdTo, productId) serves the destination-first lookup.|
> |FacilityAssocType (entity)|Retained, and it types both the Facility lanes (FacilityAssoc) and the Product lanes (ProductFacilityAssoc).|
> |FacilityAssocType BACKUP_WAREHOUSE|Renamed to BACKUP_FACILITY, as a lane can run between any kind of Facility, e.g., plant to warehouse, warehouse to store or store to store. Being a foreign key on ProductFacilityAssoc, existing rows need repointing before the old type goes (see the migration below).|
> |RequirementType TRANSFER_REQUIREMENT|Retained as the type of every transfer Requirement MRP proposes, whether surplus or residual.|
> |MrpEventType PROP_TRANSFER_RECP|Retained as this run's proposed transfer receipt, written at the destination only, dated at the achievable arrival and naming its source in facilityIdFrom.|
> |Service createTransferFromRequirement|Retained unchanged. It already treats requirement.facilityId as the destination (InventoryTransfer.facilityIdTo) and takes the source as a separate parameter, which agrees with the finalized convention; the new Requirement.facilityIdFrom is what would supply that parameter. Converting Requirements to Inventory Transfers is out of scope here.|
> |GZ-8544 ProductFacilityAssoc demo rows|Retained, but flipped to source-first, as they got committed with the source in facilityIdTo.|
> |Requirement.facilityIdTo, MrpEvent.facilityIdTo|Removed, and replaced by facilityIdFrom (see Behavioral Changes).|
> *Dropped from OFBIZ-7356:* the INITIAL_ATP and PROP_ATP_STOCK_TRANSFER event types, the XFERRQM_STOCK_ATP requirement method, and the replenishment method on ProductStoreFacility, since transfers are no longer driven by ATP and get switched on by the Planning Rule instead.
> *As committed in OFBiz* (unchanged definitions):
> {code:xml}
> <entity entity-name="FacilityAssocType" package-name="org.apache.ofbiz.product.facility" title="Define associations between facilities">
> <field name="facilityAssocTypeId" type="id"/>
> <field name="description" type="description"/>
> <prim-key field="facilityAssocTypeId"/>
> </entity>
> <entity entity-name="ProductFacilityAssoc" package-name="org.apache.ofbiz.product.facility" title="Define associations between Product facilities">
> <field name="productId" type="id"/>
> <field name="facilityId" type="id"/>
> <field name="facilityIdTo" type="id"/>
> <field name="facilityAssocTypeId" type="id"/>
> <field name="fromDate" type="date-time"/>
> <field name="thruDate" type="date-time"/>
> <field name="sequenceNum" type="numeric"/>
> <field name="transitTime" type="numeric"/>
> <prim-key field="productId"/>
> <prim-key field="facilityId"/>
> <prim-key field="facilityIdTo"/>
> <prim-key field="facilityAssocTypeId"/>
> <prim-key field="fromDate"/>
> <relation type="one" fk-name="PRFACASSOC_PRO" rel-entity-name="Product">
> <key-map field-name="productId"/>
> </relation>
> <relation type="one" fk-name="PRFACASSOC_FAC" title="From" rel-entity-name="Facility">
> <key-map field-name="facilityId"/>
> </relation>
> <relation type="one" fk-name="PRFACASSOC_FACTO" title="To" rel-entity-name="Facility">
> <key-map field-name="facilityIdTo" rel-field-name="facilityId"/>
> </relation>
> <relation type="one" fk-name="PRFACASSOC_TYPE" rel-entity-name="FacilityAssocType">
> <key-map field-name="facilityAssocTypeId"/>
> </relation>
> </entity>{code}
> {*}Retained seed data{*}, with the lane type renamed:
> {code:xml}
> <!-- OrderSeedData.xml -->
> <RequirementType description="Inter Facility Transfer Requirement" requirementTypeId="TRANSFER_REQUIREMENT"/>
> <!-- ManufacturingSeedData.xml -->
> <MrpEventType mrpEventTypeId="PROP_TRANSFER_RECP" description="Proposed Transfer receipt"/>
> <!-- ProductSeedData.xml: BACKUP_WAREHOUSE renamed -->
> <FacilityAssocType facilityAssocTypeId="BACKUP_FACILITY" description="Replenishment lane between two facilities"/>{code}
> *The GZ-8544 demo rows, flipped to source-first:*
> {code:xml}
> <ProductFacilityAssoc productId="GZ-8544" facilityId ="RegionalWarehouse1" facilityIdTo="StoreWarehouse1" facilityAssocTypeId="BACKUP_FACILITY" sequenceNum="01" fromDate="2001-01-01 12:00:00" transitTime="03"/>
> <ProductFacilityAssoc productId="GZ-8544" facilityId ="WebStoreWarehouse" facilityIdTo="RegionalWarehouse1" facilityAssocTypeId="BACKUP_FACILITY" sequenceNum="01" fromDate="2001-01-01 12:00:00" transitTime="03"/>
> <ProductFacilityAssoc productId="GZ-8544" facilityId ="WebStoreWarehouse" facilityIdTo="StoreWarehouse1" facilityAssocTypeId="BACKUP_FACILITY" sequenceNum="01" fromDate="2001-01-01 12:00:00" transitTime="00"/>
> <ProductFacilityAssoc productId="GZ-8544" facilityId ="RegionalWarehouse2" facilityIdTo="StoreWarehouse2" facilityAssocTypeId="BACKUP_FACILITY" sequenceNum="01" fromDate="2001-01-01 12:00:00" transitTime="00"/>
> <ProductFacilityAssoc productId="GZ-8544" facilityId ="WebStoreWarehouse" facilityIdTo="RegionalWarehouse2" facilityAssocTypeId="BACKUP_FACILITY" sequenceNum="01" fromDate="2001-01-01 12:00:00" transitTime="03"/>
> <ProductFacilityAssoc productId="GZ-8544" facilityId ="WebStoreWarehouse" facilityIdTo="StoreWarehouse2" facilityAssocTypeId="BACKUP_FACILITY" sequenceNum="01" fromDate="2001-01-01 12:00:00" transitTime="03"/>{code}
> {*}Migration for an existing database{*}, before loading the seed data:
> {code:sql}
> UPDATE PRODUCT_FACILITY_ASSOC SET FACILITY_ASSOC_TYPE_ID = 'BACKUP_FACILITY'
> WHERE FACILITY_ASSOC_TYPE_ID = 'BACKUP_WAREHOUSE';
> DELETE FROM FACILITY_ASSOC_TYPE WHERE FACILITY_ASSOC_TYPE_ID = 'BACKUP_WAREHOUSE';{code}
> h3. New entities
> ||Entity||Purpose||Key||
> |FacilityGroupAssoc|A lane between two Facility Groups: every member of the destination group may draw on every member of the source group, in the source group's member order. The two can be the same group, which makes its members peers.|facilityGroupId (source), facilityGroupIdTo, facilityGroupAssocTypeId, fromDate; plus thruDate, sequenceNum (lane priority)|
> |FacilityGroupAssocType|Type of a group lane; BACKUP_FACILITY.|facilityGroupAssocTypeId|
> |FacilityAssoc|The transit time of one direction of one Facility pair, and the lane of a Facility no group lane reaches.|facilityId (source), facilityIdTo, facilityAssocTypeId, fromDate; plus thruDate, sequenceNum, transitTime (whole days of carriage; empty means zero)|
> {code:xml}
> <!-- applications/datamodel/entitydef/product-entitymodel.xml -->
> <entity entity-name="FacilityGroupAssoc" package-name="org.apache.ofbiz.product.facility" title="Which facility groups may source from which">
> <description>A supply lane between two facility groups: every member of the destination group may draw on every
> member of the source group, in the source group's member order. The two may be the same group, which makes
> its members peers. Direction is source-first, matching FacilityAssoc and InventoryTransfer.</description>
> <field name="facilityGroupId" type="id"><description>The supplying (source) group.</description></field>
> <field name="facilityGroupIdTo" type="id"><description>The replenished (destination) group.</description></field>
> <field name="facilityGroupAssocTypeId" type="id"/>
> <field name="fromDate" type="date-time"/>
> <field name="thruDate" type="date-time"/>
> <field name="sequenceNum" type="numeric"><description>Lane priority among the lanes into a destination group; lanes sharing a number form one tier.</description></field>
> <prim-key field="facilityGroupId"/>
> <prim-key field="facilityGroupIdTo"/>
> <prim-key field="facilityGroupAssocTypeId"/>
> <prim-key field="fromDate"/>
> <relation type="one" fk-name="FACGRPASSOC_FROM" title="From" rel-entity-name="FacilityGroup">
> <key-map field-name="facilityGroupId"/>
> </relation>
> <relation type="one" fk-name="FACGRPASSOC_TO" title="To" rel-entity-name="FacilityGroup">
> <key-map field-name="facilityGroupIdTo" rel-field-name="facilityGroupId"/>
> </relation>
> <relation type="one" fk-name="FACGRPASSOC_TYPE" rel-entity-name="FacilityGroupAssocType">
> <key-map field-name="facilityGroupAssocTypeId"/>
> </relation>
> <index name="FACGRPASSOC_TO_IDX">
> <index-field name="facilityGroupIdTo"/>
> </index>
> </entity>
> <entity entity-name="FacilityGroupAssocType" package-name="org.apache.ofbiz.product.facility" title="Kind of association between two facility groups">
> <field name="facilityGroupAssocTypeId" type="id"/>
> <field name="description" type="description"/>
> <prim-key field="facilityGroupAssocTypeId"/>
> </entity>
> <entity entity-name="FacilityAssoc" package-name="org.apache.ofbiz.product.facility" title="Transit between two facilities, and the lanes of a facility no group lane reaches">
> <description>One direction of one facility pair. Its transitTime is the carriage time MRP uses for the pair,
> whatever allowed it; empty, or no row, means zero. It is also a supply lane in its own right, ranked by
> sequenceNum, but only for a destination no FacilityGroupAssoc lane reaches.</description>
> <field name="facilityId" type="id"><description>The supplying (source) facility.</description></field>
> <field name="facilityIdTo" type="id"><description>The replenished (destination) facility.</description></field>
> <field name="facilityAssocTypeId" type="id"/>
> <field name="fromDate" type="date-time"/>
> <field name="thruDate" type="date-time"/>
> <field name="sequenceNum" type="numeric"><description>Rank when the row serves as a lane.</description></field>
> <field name="transitTime" type="numeric"><description>Whole days of carriage in this direction, exclusive of the
> source's own days-to-ship. Empty means zero.</description></field>
> <prim-key field="facilityId"/>
> <prim-key field="facilityIdTo"/>
> <prim-key field="facilityAssocTypeId"/>
> <prim-key field="fromDate"/>
> <relation type="one" fk-name="FACASSOC_FAC" title="From" rel-entity-name="Facility">
> <key-map field-name="facilityId"/>
> </relation>
> <relation type="one" fk-name="FACASSOC_FACTO" title="To" rel-entity-name="Facility">
> <key-map field-name="facilityIdTo" rel-field-name="facilityId"/>
> </relation>
> <relation type="one" fk-name="FACASSOC_TYPE" rel-entity-name="FacilityAssocType">
> <key-map field-name="facilityAssocTypeId"/>
> </relation>
> </entity>{code}
> h3. New fields
> * FacilityGroup.facilityGroupLevel: where the group sits in the network, 0 being the most upstream.
> * Requirement.facilityIdFrom: the source of a TRANSFER_REQUIREMENT; facilityId keeps its usual meaning, i.e., the Facility the Requirement is raised for.
> * Requirement.isXferResidual: Y on a transfer that asks its source to plan the quantity rather than give surplus it already holds.
> * MrpEvent.facilityIdFrom: the source on a transfer receipt event.
> * Indexes REQ_XFER_FROM_IDX on Requirement and PRFACASSOC_TO_IDX on ProductFacilityAssoc.
> {code:xml}
> <!-- applications/manufacturing/entitydef/entitymodel.xml -->
> <extend-entity entity-name="FacilityGroup">
> <field name="facilityGroupLevel" type="numeric"><description>Where the group sits in the supply network: 0 is the
> most upstream. Sets the order a group run plans its members in, highest first, and which lanes are allowed.
> Empty for a group that is not part of the network's shape.</description></field>
> </extend-entity>
> <extend-entity entity-name="MrpEvent">
> <field name="facilityIdFrom" type="id"><description>For inter-facility transfer events, the SOURCE facility
> supplying this receipt. Null for every other event type.</description></field>
> </extend-entity>
> <extend-entity entity-name="Requirement">
> <field name="facilityIdFrom" type="id"><description>For a TRANSFER_REQUIREMENT, the SOURCE facility asked to supply
> it. facilityId keeps its usual meaning - the facility the requirement is raised FOR. Null for every other type.</description></field>
> <field name="isXferResidual" type="indicator"><description>Y on a transfer requirement that asks its source to plan
> the quantity - the remainder the lanes could not cover, sent to a source by MRS_XFER_RESID_ENUM - rather than to
> give surplus it already holds.</description></field>
> <!-- The supplying facility's own run collects its outbound obligations by this column. -->
> <index name="REQ_XFER_FROM_IDX">
> <index-field name="facilityIdFrom"/>
> <index-field name="statusId"/>
> <index-field name="requirementTypeId"/>
> </index>
> </extend-entity>
> <extend-entity entity-name="ProductFacilityAssoc">
> <!-- Lane lookups run destination-first: "which facilities supply the one I am planning". -->
> <index name="PRFACASSOC_TO_IDX">
> <index-field name="facilityIdTo"/>
> <index-field name="productId"/>
> </index>
> </extend-entity>{code}
> h3. Removed fields
> {code:xml}
> <!-- MrpEvent (manufacturing-entitymodel.xml) and Requirement (order-entitymodel.xml) -->
> <field name="facilityIdTo" type="id"></field>{code}
> h3. Seed data
> MrpTransferTypeData.xml adds only what transfer needs; no Planning Rule gets seeded, so nothing switches on by an upgrade.
> {code:xml}
> <!-- applications/manufacturing/data/MrpTransferTypeData.xml -->
> <entity-engine-xml>
> <!-- Transfer's own reference data. PROP_TRANSFER_RECP and the TRANSFER_REQUIREMENT requirement type ship with the
> release; the demand types, the first three rule settings and the idle-capacity values ship with OFBIZ-13628
> (MrpRuleTypeData.xml). Only what transfer adds is here. -->
> <!-- The lane types. FacilityAssocType's replaces BACKUP_WAREHOUSE (design 3.7) and types facility and product
> lanes; FacilityGroupAssocType's is new and types the group lanes. Seed, not demo: any network needs them. -->
> <FacilityAssocType facilityAssocTypeId="BACKUP_FACILITY" description="Replenishment lane between two facilities"/>
> <FacilityGroupAssocType facilityGroupAssocTypeId="BACKUP_FACILITY" description="Replenishment lane between two facility groups"/>
> <!-- Committed documents from an earlier cycle, and stock already moving: the same distinction as
> PROP_PUR_O_RECP against PROD_REQ_RECP. -->
> <MrpEventType mrpEventTypeId="TRANSFER_REQ_RECP" description="Approved Transfer Requirement receipt"/>
> <MrpEventType mrpEventTypeId="TRANSFER_REQ_REQ" description="Approved Transfer Requirement demand at the supplying facility"/>
> <MrpEventType mrpEventTypeId="INV_XFER_RECP" description="Inventory Transfer in flight - scheduled receipt"/>
> <MrpEventType mrpEventTypeId="INV_XFER_REQ" description="Inventory Transfer in flight - outbound demand"/>
> <!-- The slot OFBIZ-13628 left free between approved production run requirements and minimum stock. -->
> <Enumeration description="Inter-facility Transfer" enumCode="MRP_DMD_INV_TRANSFER" enumId="MRP_DMD_INV_TRANSFER" enumTypeId="MRP_DEMAND_TYPE" sequenceId="40"/>
> <Enumeration description="Generate inter-facility transfer requirements (Y/N, absent means N)" enumCode="MRS_GEN_XFER_REQ" enumId="MRS_GEN_XFER_REQ" enumTypeId="MRP_RULE_SETTING" sequenceId="40"/>
> <Enumeration description="A transfer keeps the source's minimum stock (Y/N, absent means Y)" enumCode="MRS_XFER_KEEP_MINSTK" enumId="MRS_XFER_KEEP_MINSTK" enumTypeId="MRP_RULE_SETTING" sequenceId="50"/>
> <Enumeration description="Where the remainder the lanes cannot cover is planned (value is an MRP_XFER_RESIDUAL id)" enumCode="MRS_XFER_RESID_ENUM" enumId="MRS_XFER_RESID_ENUM" enumTypeId="MRP_RULE_SETTING" sequenceId="70"/>
> <!-- Values for MRS_XFER_RESID_ENUM. DEST is what the engine did before the setting existed, and what absent means. -->
> <EnumerationType description="MRP Transfer Residual" enumTypeId="MRP_XFER_RESIDUAL" hasTable="N"/>
> <Enumeration description="Make or buy the remainder at the destination" enumCode="MRP_RESID_DEST" enumId="MRP_RESID_DEST" enumTypeId="MRP_XFER_RESIDUAL" sequenceId="10"/>
> <Enumeration description="Send the remainder, whole, to the last source on the destination's lanes, which plans it" enumCode="MRP_RESID_SOURCE" enumId="MRP_RESID_SOURCE" enumTypeId="MRP_XFER_RESIDUAL" sequenceId="20"/>
> </entity-engine-xml>{code}
> h3. Demo data
> MrpTransferDemoData.xml (ext-demo): a distribution center and four stores around the manufacturing demo plant, the levelled groups MFGX_PLANTS (0), MFGX_DCS (1) and MFGX_STORES (2), the group MFGX_NETWORK for a network run, the four group lanes, transit times, and two example rules. The store rule is eligible for MFGX_STORES and for Store 4's own group only, and MFGX_NETWORK carries no rule, so loading the demo changes no existing Facility's default rule.
> h2. Services and Screens
> *Planning Rule settings* (validated when a rule gets saved):
> ||Setting||Value||Absent means||Read from||
> |MRS_GEN_XFER_REQ|Y / N|N, i.e., no transfers proposed|the destination's rule|
> |MRS_XFER_KEEP_MINSTK|Y / N|Y, i.e., the source keeps its Minimum Stock|the source's rule|
> |MRS_XFER_RESID_ENUM|MRP_RESID_DEST / MRP_RESID_SOURCE|MRP_RESID_DEST|the destination's rule|
> *executeMrp* keeps its parameters. A Facility Group run resolves the network once and hands it, along with the rules chosen for its members, to each member's run.
> {*}Requirements written{*}: a TRANSFER_REQUIREMENT in REQ_PROPOSED for the destination (facilityId), from its source (facilityIdFrom), required by the need date, starting on the ship date, with isLate, idealRequiredByDate, mrpRuleId and isXferResidual, and one PROP_TRANSFER_RECP event at the destination dated at the achievable arrival.
> {*}Run messages{*}: a lane against the levels, a pair used with no transit time, a member with no level that is a lane's source, a remainder with no candidate, and an approved transfer its source cannot fully give. A Facility with two levels, or an event that would merge two facilities, fails the run naming the cause.
> {*}Requirement screens{*}: Find Requirement gets filters for the source Facility, the residual marker, and Late and Short; the list and the edit form show the source Facility and the residual marker. Requirement.facilityId stays editable, as re-pointing it is how a planner acts on a remainder that got planned at the Facility that needed it.
> h2. Use cases
> The cases run on a network of a plant, a distribution center (DC) and four stores, with the run on a Monday morning. The DC holds 200 of a skateboard against a Minimum Stock of 50 with one day to ship, and Store 2 holds 60 against 40. Lanes into the stores: their peers first (priority 10), then the DC (20), then the plant (30); into the DC, the plant. Transit: DC to each store 2 days, Store 2 to Store 1 8 days, plant to DC 3 days, plant to each store 5 days, and none between Store 3 and the other stores. Store 4 belongs to no levelled group and has its own lane from the DC.
> *CASE-1 — A shortfall is sourced from the DC.*
> *Setup:* Store 1 holds none and has a Sales Order for 40 due Wednesday; transfers on.
> *Expected:* one TRANSFER_REQUIREMENT for 40 at Store 1 from the DC, Late, since the DC's legs take three days, and nothing bought or made at Store 1.
> *Why:* stock the network holds would get used before anything more gets bought or made.
> *CASE-2 — A source that cannot cover all of it is passed over.*
> *Setup:* Store 1 short 100 in ten days; Store 2 can spare 20, the DC 150.
> *Expected:* one transfer of 100 from the DC; nothing drawn from Store 2.
> *Why:* one shortfall, one source, one Requirement to approve and ship.
> *CASE-3 — A candidate in time beats a slower one listed first.*
> *Setup:* Store 1 short 100 needed Friday; Store 2 can spare 100 over its eight-day pair, the DC 150 with three days of legs.
> *Expected:* the DC supplies it, on time.
> *Why:* lane priority ranks the candidates that can meet the date, and never makes a store wait for a slow peer.
> *CASE-4 — When none is in time, the earliest arrival wins.*
> *Setup:* as CASE-3 but needed Tuesday.
> *Expected:* the DC supplies it, Late, with idealRequiredByDate Tuesday.
> *Why:* once it is late whatever happens, the planner needs the smallest delay.
> *CASE-5 — What cannot be covered whole gets bought whole.*
> *Setup:* Store 1 wants 180; the DC can spare 150.
> *Expected:* no transfer and none of the DC's stock claimed; the 180 gets made or bought at Store 1.
> *Why:* the old cascade would have moved 150 and bought 30, with the batch waiting for the later of the two.
> *CASE-6 — The DC's own order comes first.*
> *Setup:* the DC has its own Sales Order for 120 due later within the horizon, and Store 1 wants 100.
> *Expected:* no transfer, as the DC can spare only 30; an order of the DC's beyond the horizon would not stop it.
> *Why:* the DC's own run would serve its customer first, so promising the stock to Store 1 would only leave the approved transfer short later.
> *CASE-7 — A receipt counts from the day it lands.*
> *Setup:* the DC holds 40 against 10 with an approved purchase of 200 landing Thursday; Store 1 needs 100 Friday.
> *Expected:* the transfer ships Thursday, after the receipt lands, Late, rather than being refused or dated before the stock exists.
> *Why:* netting demand without counting receipts would make a source with plenty arriving look empty.
> *CASE-8 — A claim made earlier in the run reduces what the next store finds.*
> *Setup:* Stores 1 and 3 each want 100; the DC can spare 150; one network run.
> *Expected:* Store 1, planned first, gets 100; Store 3 finds 50 and makes or buys its 100. The proposal is never demand in the DC's own plan.
> *Why:* a proposal claims stock, but it is not an obligation until it gets approved.
> *CASE-9 — A proposal from an earlier run is never a claim.*
> *Setup:* as CASE-8, with an unapproved proposal from last run claiming all of the DC's 150 for Store 3.
> *Expected:* the same result as CASE-8.
> *Why:* the group run wipes its members' proposals before it plans the first one, so a claim nobody acted on cannot strand stock.
> *CASE-10 — The source's rule decides its floor.*
> *Setup:* as CASE-8, with the DC's own rule at MRS_XFER_KEEP_MINSTK = N.
> *Expected:* the DC can spare 200 and covers both stores.
> *Why:* the buffer is the source's, so the source's rule decides it, not the store's.
> *CASE-11 — A store draws only on peers planned after it.*
> *Setup:* Store 2 short 30 with Store 3 able to spare 40; separately, Store 3 short 30 with Store 2 able to spare 60.
> *Expected:* Store 2 gets 30 from Store 3; Store 3 gets nothing from Store 2 and makes or buys its 30.
> *Why:* a Facility drawing only on facilities planned after it is what makes loops impossible.
> *CASE-12 — The planning order follows the levels.*
> *Setup:* run MFGX_NETWORK, and again with its sequence reversed.
> *Expected:* Store 4, Stores 1 to 3 in the run group's order, the DC, then the plant, both times.
> *Why:* every in-run source has to be planned after the destinations drawing on it.
> *CASE-13 — The remainder goes whole to the last source, which plans it.*
> *Setup:* a bought part; Store 1 wants 100, the DC can spare 30; the stores' rule at MRP_RESID_SOURCE.
> *Expected:* one residual transfer for 100 from the DC, Short, and nothing else at Store 1; planned later in the same run, the DC serves 30 and buys 70.
> *Why:* the store names the source, and the source's own plan decides how much to make or buy.
> *CASE-14 — The last source sources its own shortfall through its own lanes.*
> *Setup:* the plant's lane to the stores expired; Store 1 short 60 with Store 2, Store 3 and the DC able to spare 10 each, and the plant 100.
> *Expected:* the DC takes the 60 as a residual, serves 10, and sources its own 50 from the plant through the DC's lane.
> *Why:* a Facility searches only its own lanes and never inherits a source's lanes.
> *CASE-15 — Approved and moving transfers count at both ends, with proposals off.*
> *Setup:* MRS_GEN_XFER_REQ = N, an approved transfer from the DC to Store 1 and an Inventory Transfer in flight.
> *Expected:* no new proposal; both counted as supply at Store 1 and as demand at the DC.
> *Why:* hiding commitments would have the store buy again for stock already coming.
> *CASE-16 — An approved transfer larger than the source's surplus.*
> *Setup:* the DC's own run with an approved transfer for 300 against 200 on hand.
> *Expected:* the transfer gets recorded Short by 100, and the DC raises nothing for it.
> *Why:* a source never buys or makes for somebody else's surplus transfer.
> *CASE-17 — A transfer explodes nothing.*
> *Setup:* the DC short of decks, covered whole by the plant.
> *Expected:* no veneer demand and no machine time at the DC.
> *Why:* a transfer is a single-item document.
> *CASE-18 — Without a rule nothing changes.*
> *Setup:* lanes and stock fully configured, no rule switching transfers on.
> *Expected:* no transfer proposed, and the plan is the one OFBIZ-13462 makes today.
> *Why:* loading the new reference data must not change an existing installation's plans.
> *CASE-19 — An event never merges two facilities.*
> *Setup:* a write of the DC's quantity into an event key Store 1 already holds.
> *Expected:* the run stops, naming both facilities, and the store's event is unchanged.
> *Why:* a plan claiming a store holds the network's stock is far worse than a run that stops.
> *CASE-20 — A Facility with two levels stops the run.*
> *Setup:* Store 2 made a member of a second levelled group.
> *Expected:* the network run fails naming Store 2, before anything gets planned.
> *Why:* the level decides the planning order, and two levels would make it a guess.
> The complete list of cases, 78 in all, goes along with the design notes attached to this ticket, and each automated case is a test of the new mrptransfertests suite.
> h2. Out of scope
> * {*}Converting an approved Requirement into Inventory Transfers{*}, including partial fulfillment, shipping and receiving, which belongs in a ticket of its own.
> * *Lane maintenance screens* and their save-time validations (lanes against the levels, one level per Facility, a warning on a mixed peer group). Lanes get maintained as data for now, and the run reports what it ignores.
> * {*}Feedback when a source cannot honor an approved transfer{*}, as the destination does not get re-planned.
> * {*}The destination's receiving calendar{*}, as both legs get walked through the source's calendar.
> * *Aggregating proposals, ranking sources by cost, and net-change planning.*
> * {*}The Unconstrained mode{*}, which stays exactly as it is.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)