This is an automated email from the ASF dual-hosted git repository.
diveshdut pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
The following commit(s) were added to refs/heads/trunk by this push:
new 84858b529d OFBIZ-13459: Preserve facility context on MRP events
84858b529d is described below
commit 84858b529deef5f9b214dc5129242436c0735a69
Author: Divesh Dutta <[email protected]>
AuthorDate: Fri Jul 24 19:07:16 2026 +0530
OFBIZ-13459: Preserve facility context on MRP events
## Jira Ticket:
https://issues.apache.org/jira/browse/OFBIZ-13459
## Summary
This PR preserves known facility context on generated `MrpEvent` rows
when the OFBiz MRP engine already has reliable facility information
available from source data or the current run context.
The goal is to improve planner traceability without changing MRP
calculation behavior, `MrpEvent` identity, or `MrpEvent` merge
semantics.
## Problem
Today, some MRP events are created without `facilityId` even when the
engine already knows the relevant warehouse or manufacturing facility.
That makes planner-facing event interpretation harder, especially for:
- product-plus-facility event timelines
- proposed supply traceability
- exception analysis
- API/UI consumers that need concrete facility context on each event
## What This PR Changes
This PR preserves facility context for these event paths when the engine
already has it:
- `SALES_ORDER_SHIP`
- `PROD_REQ_RECP`
- `PUR_ORDER_RECP`
- `MANUF_ORDER_REQ`
- `MANUF_ORDER_RECP`
- `REQUIRED_MRP`
- `MRP_REQUIREMENT`
- `PROP_PUR_O_RECP`
- `PROP_MANUF_O_RECP`
- `ERROR`
It also adds a conservative merge-side backfill:
- if an existing `MrpEvent` row has an empty `facilityId`
- and a later matching update provides a non-empty `facilityId`
- then the missing facility is filled in
- existing facility values are not overwritten
## Important Non-Goals
This PR does not:
- change MRP quantity calculation
- change `MrpEvent` primary key
- change `MrpEvent` merge semantics
- force facility assignment for organization-level signals
- change late-flag behavior
- include run-log or planner API work
## Facility Group Note
When MRP is launched with `facilityGroupId`, current code resolves the
run into concrete facilities before event creation.
This PR preserves those resolved concrete facilities on generated
`MrpEvent` rows. It does not stamp the facility group id itself.
## Why Event-Level Facility Still Matters
Run-level provenance and event-level facility serve different purposes.
A run may have an overall planning context, but individual events can
still belong to different concrete facilities within that run context,
especially across warehouse-oriented and manufacturing-oriented event
types.
---
.../mrp/InventoryEventPlannedServices.java | 3 ++
.../ofbiz/manufacturing/mrp/MrpServices.java | 53 +++++++++++++++-------
2 files changed, 39 insertions(+), 17 deletions(-)
diff --git
a/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/mrp/InventoryEventPlannedServices.java
b/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/mrp/InventoryEventPlannedServices.java
index 578e0be67f..ae61c06f6a 100644
---
a/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/mrp/InventoryEventPlannedServices.java
+++
b/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/mrp/InventoryEventPlannedServices.java
@@ -84,6 +84,9 @@ public class InventoryEventPlannedServices {
if (isLate) {
mrpEvent.put("isLate", "Y");
}
+ if (UtilValidate.isEmpty(mrpEvent.getString("facilityId")) &&
UtilValidate.isNotEmpty(facilityId)) {
+ mrpEvent.put("facilityId", facilityId);
+ }
mrpEvent.store();
}
}
diff --git
a/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/mrp/MrpServices.java
b/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/mrp/MrpServices.java
index 574d41f47c..2b11362c14 100644
---
a/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/mrp/MrpServices.java
+++
b/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/mrp/MrpServices.java
@@ -207,7 +207,8 @@ public class MrpServices {
}
parameters = UtilMisc.toMap("mrpId", mrpId, "productId",
productId, "eventDate", requiredByDate, "mrpEventTypeId", "SALES_ORDER_SHIP");
try {
-
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
eventQuantityTmp, null,
+ String salesOrderFacilityId =
genericResult.getString("facilityId");
+
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
eventQuantityTmp, salesOrderFacilityId,
genericResult.getString("orderId") + "-" +
genericResult.getString("orderItemSeqId"), false, delegator);
} catch (GenericEntityException e) {
return
ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ManufacturingMrpEventProblemInitializing", UtilMisc.toMap(
@@ -239,8 +240,9 @@ public class MrpServices {
parameters = UtilMisc.toMap("mrpId", mrpId, "productId",
productId, "eventDate", estimatedShipDate, "mrpEventTypeId", "PROD_REQ_RECP");
try {
-
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
eventQuantityTmp, null, genericResult.getString("requirementId"),
- false, delegator);
+ String requirementFacilityId =
genericResult.getString("facilityId");
+
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
eventQuantityTmp, requirementFacilityId,
+ genericResult.getString("requirementId"), false,
delegator);
} catch (GenericEntityException e) {
return
ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ManufacturingMrpEventProblemInitializing", UtilMisc.toMap(
"mrpEventTypeId", "PROD_REQ_RECP"), locale));
@@ -317,7 +319,8 @@ public class MrpServices {
parameters = UtilMisc.toMap("mrpId", mrpId, "productId",
productId, "eventDate", estimatedShipDate, "mrpEventTypeId",
"PUR_ORDER_RECP");
-
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
shipGroupQuantity, null,
+ String purchaseOrderFacilityId =
UtilValidate.isNotEmpty(facilityId) ? facilityId : null;
+
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
shipGroupQuantity, purchaseOrderFacilityId,
genericResult.getString("orderId") + "-" +
genericResult.getString("orderItemSeqId"), false, delegator);
} catch (GenericEntityException e) {
return
ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ManufacturingMrpEventProblemInitializing", UtilMisc.toMap(
@@ -359,7 +362,9 @@ public class MrpServices {
"MANUF_ORDER_REQ");
String eventName =
(UtilValidate.isEmpty(genericResult.getString("workEffortParentId")) ?
genericResult.getString("workEffortId")
: genericResult.getString("workEffortParentId") + "-"
+ genericResult.getString("workEffortId"));
-
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
eventQuantityTmp, null, eventName, false, delegator);
+ String productionFacilityId =
genericResult.getString("facilityId");
+
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
eventQuantityTmp, productionFacilityId, eventName, false,
+ delegator);
}
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ManufacturingMrpEventProblemInitializing", UtilMisc.toMap(
@@ -403,8 +408,9 @@ public class MrpServices {
parameters = UtilMisc.toMap("mrpId", mrpId, "productId",
productId, "eventDate", estimatedShipDate, "mrpEventTypeId",
"MANUF_ORDER_RECP");
-
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
eventQuantityTmp, null, genericResult.getString("workEffortId"),
- false, delegator);
+ String productionFacilityId =
genericResult.getString("facilityId");
+
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
eventQuantityTmp, productionFacilityId,
+ genericResult.getString("workEffortId"), false,
delegator);
}
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ManufacturingMrpEventProblemInitializing", UtilMisc.toMap(
@@ -445,7 +451,8 @@ public class MrpServices {
}
parameters = UtilMisc.toMap("mrpId", mrpId, "productId",
productId, "eventDate", now, "mrpEventTypeId", "REQUIRED_MRP");
try {
-
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
BigDecimal.ZERO, null, null, false, delegator);
+ String productFacilityId =
genericResult.getString("facilityId");
+
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
BigDecimal.ZERO, productFacilityId, null, false, delegator);
} catch (GenericEntityException e) {
return
ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ManufacturingMrpEventProblemInitializing", UtilMisc.toMap(
"mrpEventTypeId", "REQUIRED_MRP"), locale));
@@ -545,7 +552,7 @@ public class MrpServices {
}
} catch (GenericServiceException e) {
Debug.logError(e, "Error calling
getProductInventoryAvailableByFacility service", MODULE);
- logMrpError(mrpId, productId, "Unable to count inventory",
delegator);
+ logMrpError(mrpId, productId, facilityId, "Unable to count
inventory", delegator);
return BigDecimal.ZERO;
}
return ((BigDecimal) resultMap.get("quantityOnHandTotal"));
@@ -587,16 +594,26 @@ public class MrpServices {
}
public static void logMrpError(String mrpId, String productId, String
errorMessage, Delegator delegator) {
- logMrpError(mrpId, productId, UtilDateTime.nowTimestamp(),
errorMessage, delegator);
+ logMrpError(mrpId, productId, null, UtilDateTime.nowTimestamp(),
errorMessage, delegator);
+ }
+
+ public static void logMrpError(String mrpId, String productId, String
facilityId, String errorMessage, Delegator delegator) {
+ logMrpError(mrpId, productId, facilityId, UtilDateTime.nowTimestamp(),
errorMessage, delegator);
}
public static void logMrpError(String mrpId, String productId, Timestamp
eventDate, String errorMessage, Delegator delegator) {
+ logMrpError(mrpId, productId, null, eventDate, errorMessage,
delegator);
+ }
+
+ public static void logMrpError(String mrpId, String productId, String
facilityId, Timestamp eventDate, String errorMessage,
+ Delegator delegator) {
try {
if (UtilValidate.isNotEmpty(productId) &&
UtilValidate.isNotEmpty(errorMessage)) {
GenericValue inventoryEventError =
delegator.makeValue("MrpEvent", UtilMisc.toMap("productId", productId,
"mrpId", mrpId,
"eventDate", eventDate,
"mrpEventTypeId", "ERROR",
+ "facilityId", facilityId,
"eventName", errorMessage));
delegator.createOrStore(inventoryEventError);
}
@@ -617,7 +634,7 @@ public class MrpServices {
*/
public static void processBomComponent(String mrpId, GenericValue product,
BigDecimal eventQuantity, Timestamp startDate,
- Map<String, Object>
routingTaskStartDate, List<BOMNode> listComponent) {
+ Map<String, Object>
routingTaskStartDate, List<BOMNode> listComponent, String facilityId) {
// TODO : change the return type to boolean to be able to test if all
is ok or if it have had a exception
Delegator delegator = product.getDelegator();
@@ -636,11 +653,12 @@ public class MrpServices {
parameters.put("mrpEventTypeId", "MRP_REQUIREMENT");
BigDecimal componentEventQuantity = node.getQuantity();
try {
-
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
componentEventQuantity.negate(), null, product.get(
+
InventoryEventPlannedServices.createOrUpdateMrpEvent(parameters,
componentEventQuantity.negate(), facilityId, product.get(
"productId") + ": " + eventDate, false,
delegator);
} catch (GenericEntityException e) {
Debug.logError("Error : findOne(\"MrpEvent\",
parameters) =" + parameters + "--" + e.getMessage(), MODULE);
- logMrpError(mrpId,
node.getProduct().getString("productId"), "Unable to create event
(processBomComponent)", delegator);
+ logMrpError(mrpId,
node.getProduct().getString("productId"), facilityId,
+ "Unable to create event
(processBomComponent)", delegator);
}
}
}
@@ -905,7 +923,7 @@ public class MrpServices {
if (isBuilt) {
// process the product components
processBomComponent(mrpId, product,
proposedOrder.getQuantity(), proposedOrder.getRequirementStartDate(),
- routingTaskStartDate, components);
+ routingTaskStartDate, components,
manufacturingFacilityId);
}
// create the ProposedOrder (only if the product is
warehouse managed), and the MrpEvent associated
String requirementId = null;
@@ -913,8 +931,8 @@ public class MrpServices {
requirementId = proposedOrder.create(ctx,
userLogin);
}
if (UtilValidate.isEmpty(productFacility) && !isBuilt)
{
- logMrpError(mrpId, productId, now, "No
ProductFacility record for [" + facilityId + "]; no requirement created.",
- delegator);
+ logMrpError(mrpId, productId, facilityId, now, "No
ProductFacility record for [" + facilityId
+ + "]; no requirement created.", delegator);
}
String eventName = null;
if (UtilValidate.isNotEmpty(requirementId)) {
@@ -924,8 +942,9 @@ public class MrpServices {
"mrpId", mrpId,
"eventDate", eventDate,
"mrpEventTypeId", (isBuilt ?
"PROP_MANUF_O_RECP" : "PROP_PUR_O_RECP"));
+ String eventFacilityId = isBuilt ?
manufacturingFacilityId : facilityId;
try {
-
InventoryEventPlannedServices.createOrUpdateMrpEvent(eventMap,
proposedOrder.getQuantity(), null,
+
InventoryEventPlannedServices.createOrUpdateMrpEvent(eventMap,
proposedOrder.getQuantity(), eventFacilityId,
eventName,
(proposedOrder.getRequirementStartDate().compareTo(now) < 0), delegator);
} catch (GenericEntityException e) {
return
ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ManufacturingMrpCreateOrUpdateEvent",