Author: sichen
Date: Tue Dec 4 11:03:31 2007
New Revision: 601039
URL: http://svn.apache.org/viewvc?rev=601039&view=rev
Log:
Refactor ATP requirements based on minimum stock so that the service can be run
on order status change rather than on inventory reservation.
Modified:
ofbiz/trunk/applications/order/servicedef/secas.xml
ofbiz/trunk/applications/order/servicedef/services_requirement.xml
ofbiz/trunk/applications/order/src/org/ofbiz/order/requirement/RequirementServices.java
Modified: ofbiz/trunk/applications/order/servicedef/secas.xml
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/servicedef/secas.xml?rev=601039&r1=601038&r2=601039&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/servicedef/secas.xml (original)
+++ ofbiz/trunk/applications/order/servicedef/secas.xml Tue Dec 4 11:03:31 2007
@@ -282,15 +282,15 @@
</eca>
<eca service="reserveOrderItemInventory" event="commit">
<condition field-name="quantity" value="0" operator="greater"
type="Double"/>
- <action service="createRequirementFromItemATP" mode="sync"
run-as-user="system"/>
<action service="checkCreateStockRequirementAtp" mode="sync"
run-as-user="system"/>
</eca>
- <!-- create the automatic requirements for sales orders but only if the
status changes from created to approved -->
+ <!-- create the automatic and ATP requirements for sales orders but only
if the status changes from created to approved -->
<eca service="changeOrderStatus" event="commit" run-on-error="false">
<condition field-name="oldStatusId" operator="equals"
value="ORDER_CREATED"/>
<condition field-name="statusId" operator="equals"
value="ORDER_APPROVED"/>
<condition field-name="orderTypeId" operator="equals"
value="SALES_ORDER"/>
<action service="createAutoRequirementsForOrder" mode="sync"/>
+ <action service="createATPRequirementsForOrder" mode="sync"/>
</eca>
<!-- WorkEffort -->
Modified: ofbiz/trunk/applications/order/servicedef/services_requirement.xml
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/servicedef/services_requirement.xml?rev=601039&r1=601038&r2=601039&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/servicedef/services_requirement.xml
(original)
+++ ofbiz/trunk/applications/order/servicedef/services_requirement.xml Tue Dec
4 11:03:31 2007
@@ -186,5 +186,14 @@
</description>
<attribute name="orderId" type="String" mode="IN" optional="false"/>
</service>
+ <service name="createATPRequirementsForOrder" engine="java"
+ location="org.ofbiz.order.requirement.RequirementServices"
invoke="createATPRequirementsForOrder" auth="true">
+ <description>
+ Creates requirements for any products with requirementMethodEnumId
PRODRQM_ATP in the given sales order when
+ the ATP falls below or is below the minimum stock for the order
facility. ProductFacility.minimumStock must
+ be configured for requirements to be generated.
ProductFacility.reorderQuantity is not currently supported.
+ </description>
+ <attribute name="orderId" type="String" mode="IN" optional="false"/>
+ </service>
</services>
Modified:
ofbiz/trunk/applications/order/src/org/ofbiz/order/requirement/RequirementServices.java
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/requirement/RequirementServices.java?rev=601039&r1=601038&r2=601039&view=diff
==============================================================================
---
ofbiz/trunk/applications/order/src/org/ofbiz/order/requirement/RequirementServices.java
(original)
+++
ofbiz/trunk/applications/order/src/org/ofbiz/order/requirement/RequirementServices.java
Tue Dec 4 11:03:31 2007
@@ -232,5 +232,93 @@
}
return ServiceUtil.returnSuccess();
}
+
+ // note that this service is designed to work only when a sales order
status changes from CREATED -> APPROVED because HOLD -> APPROVED is too complex
+ public static Map createATPRequirementsForOrder(DispatchContext ctx, Map
context) {
+ GenericDelegator delegator = ctx.getDelegator();
+ LocalDispatcher dispatcher = ctx.getDispatcher();
+ GenericValue userLogin = (GenericValue) context.get("userLogin");
+
+ /*
+ * The strategy in this service is to begin making requirements when
the product falls below the
+ * ProductFacility.minimumStock. Because the minimumStock is an upper
bound, the quantity to be required
+ * is either that required to bring the ATP back up to the
minimumStock level or the amount ordered,
+ * whichever is less.
+ *
+ * If there is a way to support reorderQuantity without losing the
order item -> requirement association data,
+ * then this service should be updated.
+ *
+ * The result is that this service generates many small requirements
when stock levels are low for a product,
+ * which is perfectly fine since the system is capable of creating POs
in bulk from aggregate requirements.
+ * The only concern would be a UI to manage numerous requirements with
ease, preferrably by aggregating
+ * on productId.
+ */
+ String orderId = (String) context.get("orderId");
+ try {
+ GenericValue order = delegator.findByPrimaryKey("OrderHeader",
UtilMisc.toMap("orderId", orderId));
+ GenericValue productStore =
order.getRelatedOneCache("ProductStore");
+ String facilityId = productStore.getString("inventoryFacilityId");
+ List orderItems = order.getRelated("OrderItem");
+ for (Iterator iter = orderItems.iterator(); iter.hasNext(); ) {
+ GenericValue item = (GenericValue) iter.next();
+ GenericValue product = item.getRelatedOne("Product");
+ if (product == null) continue;
+ if (!
"PRODRQM_ATP".equals(product.get("requirementMethodEnumId"))) continue;
+
+ Double quantity = item.getDouble("quantity");
+ Double cancelQuantity = item.getDouble("cancelQuantity");
+ double ordered = quantity.doubleValue() - (cancelQuantity ==
null ? 0.0 : cancelQuantity.doubleValue());
+ if (ordered <= 0.0) continue;
+
+ // get the minimum stock for this facility (don't do anything
if not configured)
+ GenericValue productFacility =
delegator.findByPrimaryKey("ProductFacility", UtilMisc.toMap("facilityId",
facilityId, "productId", product.get("productId")));
+ if (productFacility == null ||
productFacility.get("minimumStock") == null) continue;
+ double minimumStock =
productFacility.getDouble("minimumStock").doubleValue();
+
+ // get the facility ATP for product, which should be updated
for this item's reservation
+ Map results =
dispatcher.runSync("getInventoryAvailableByFacility",
UtilMisc.toMap("userLogin", userLogin, "productId", product.get("productId"),
"facilityId", facilityId));
+ if (ServiceUtil.isError(results)) return results;
+ double atp = ((Double)
results.get("availableToPromiseTotal")).doubleValue(); // safe since this is a
required OUT param
+
+ // the minimum stock is an upper bound, therefore we either
require up to the minimum stock or the input required quantity, whichever is
less
+ double shortfall = minimumStock - atp;
+ double required = Math.min(ordered, shortfall);
+ if (required <= 0.0) continue;
+
+ // count all current requirements for this product
+ double requirementQty = 0.0;
+ List conditions = UtilMisc.toList(
+ new EntityExpr("facilityId", EntityOperator.EQUALS,
facilityId),
+ new EntityExpr("productId", EntityOperator.EQUALS,
product.get("productId")),
+ new EntityExpr("requirementTypeId",
EntityOperator.EQUALS, "PRODUCT_REQUIREMENT"),
+ new EntityExpr("statusId", EntityOperator.NOT_EQUAL,
"REQ_ORDERED"),
+ new EntityExpr("statusId", EntityOperator.NOT_EQUAL,
"REQ_REJECTED")
+ );
+ List requirements = delegator.findByAnd("Requirement",
conditions);
+ for (Iterator riter = requirements.iterator();
riter.hasNext(); ) {
+ GenericValue requirement = (GenericValue) riter.next();
+ requirementQty += (requirement.get("quantity") == null ?
0.0 : requirement.getDouble("quantity").doubleValue());
+ }
+
+ // if we the existing requirements are not enough, then create
a new requirement for the difference
+ required -= requirementQty;
+ if (required <= 0.0) continue;
+
+ Map input = UtilMisc.toMap("userLogin", userLogin,
"facilityId", facilityId, "productId", product.get("productId"), "quantity",
new Double(required), "requirementTypeId", "PRODUCT_REQUIREMENT");
+ results = dispatcher.runSync("createRequirement", input);
+ if (ServiceUtil.isError(results)) return results;
+ String requirementId = (String) results.get("requirementId");
+
+ input = UtilMisc.toMap("userLogin", userLogin, "orderId",
order.get("orderId"), "orderItemSeqId", item.get("orderItemSeqId"),
"requirementId", requirementId, "quantity", new Double(required));
+ results =
dispatcher.runSync("createOrderRequirementCommitment", input);
+ if (ServiceUtil.isError(results)) return results;
+ }
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ } catch (GenericServiceException e) {
+ Debug.logError(e, module);
+ }
+ return ServiceUtil.returnSuccess();
+ }
}