Author: oandreyev Date: 2009-04-02 11:51:40 -0700 (Thu, 02 Apr 2009) New Revision: 11480 Modified: versions/1.0/trunk/hot-deploy/opentaps-common/src/common/org/opentaps/common/manufacturing/bom/BomServices.java versions/1.0/trunk/hot-deploy/purchasing/src/org/opentaps/purchasing/mrp/OpentapsMrpServices.java versions/1.0/trunk/hot-deploy/purchasing/src/org/opentaps/purchasing/mrp/UtilMrp.java versions/1.0/trunk/hot-deploy/warehouse/src/org/opentaps/warehouse/manufacturing/ProductionRunServices.java Log: Further changes to implement routing selection based on product quantity Modified: versions/1.0/trunk/hot-deploy/opentaps-common/src/common/org/opentaps/common/manufacturing/bom/BomServices.java =================================================================== --- versions/1.0/trunk/hot-deploy/opentaps-common/src/common/org/opentaps/common/manufacturing/bom/BomServices.java 2009-04-02 18:35:09 UTC (rev 11479) +++ versions/1.0/trunk/hot-deploy/opentaps-common/src/common/org/opentaps/common/manufacturing/bom/BomServices.java 2009-04-02 18:51:40 UTC (rev 11480) @@ -206,6 +206,9 @@ if (routing == null) { // try to find a routing linked to the virtual product routingInMap = UtilMisc.toMap("productId", tree.getRoot().getProduct().getString("productId"), "userLogin", userLogin); + if (quantity != null) { + routingInMap.put("quantity", quantity); + } routingOutMap = dispatcher.runSync("getProductRouting", routingInMap); routing = (GenericValue) routingOutMap.get("routing"); } Modified: versions/1.0/trunk/hot-deploy/purchasing/src/org/opentaps/purchasing/mrp/OpentapsMrpServices.java =================================================================== --- versions/1.0/trunk/hot-deploy/purchasing/src/org/opentaps/purchasing/mrp/OpentapsMrpServices.java 2009-04-02 18:35:09 UTC (rev 11479) +++ versions/1.0/trunk/hot-deploy/purchasing/src/org/opentaps/purchasing/mrp/OpentapsMrpServices.java 2009-04-02 18:51:40 UTC (rev 11480) @@ -41,6 +41,7 @@ import java.math.RoundingMode; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; @@ -266,10 +267,10 @@ mrpInventoryEventDetailInput = UtilMisc.toMap("orderId", genericResult.getString("orderId"), "orderItemSeqId", genericResult.getString("orderItemSeqId")); MrpInventoryEventServices.createOrUpdateMrpInventoryEvent(parameters, eventQuantityTmp, null, genericResult.getString("orderId") + "-" + genericResult.getString("orderItemSeqId"), requiredByDate.before(now), mrpInventoryEventDetailInput, delegator); } + // ---------------------------------------- // Loads all the approved requirements // ---------------------------------------- - initInventoryEventPlanForApprovedRequirements(facilityId, mrpRunProductIds, now, receiptEventBufferMilliseconds, delegator); // ------------------------------------------------------- @@ -884,7 +885,7 @@ * @return a <code>Map</code> value */ @SuppressWarnings("unchecked") - public static Map runMrp(DispatchContext ctx, Map context) { + public static Map<String, ?> runMrp(DispatchContext ctx, Map<String, Object> context) { GenericDelegator delegator = ctx.getDelegator(); LocalDispatcher dispatcher = ctx.getDispatcher(); @@ -908,7 +909,7 @@ // validate supplier specific run String supplierPartyId = (String) context.get("supplierPartyId"); if (UtilValidate.isNotEmpty(supplierPartyId)) { - List productIds = UtilMrp.getProductIdsFromSupplier(supplierPartyId, delegator); + List<String> productIds = UtilMrp.getProductIdsFromSupplier(supplierPartyId, delegator); if (UtilValidate.isEmpty(productIds)) { return ServiceUtil.returnError("Supplier does not have associated products."); } @@ -917,10 +918,10 @@ // construct List of facilityIds to run MRP for based on their sequence in FacilityGroupMember List<String> facilityIds = null; if (UtilValidate.isEmpty(facilityGroupId)) { - facilityIds = UtilMisc.toList(facilityId); + facilityIds = Arrays.asList(facilityId); } else { - List facilities = delegator.findByAnd("FacilityGroupMember", UtilMisc.toList(new EntityExpr("facilityGroupId", EntityOperator.EQUALS, facilityGroupId), - EntityUtil.getFilterByDateExpr()), UtilMisc.toList("sequenceNum")); + List<GenericValue> facilities = delegator.findByAnd("FacilityGroupMember", Arrays.asList(new EntityExpr("facilityGroupId", EntityOperator.EQUALS, facilityGroupId), + EntityUtil.getFilterByDateExpr()), Arrays.asList("sequenceNum")); facilityIds = EntityUtil.getFieldListFromEntityList(facilities, "facilityId", true); } @@ -930,10 +931,10 @@ // run the MRP for each facility ModelService runMrpForFacility = ctx.getModelService("opentaps.runMrpForFacility"); - Map serviceParams = runMrpForFacility.makeValid(context, "IN"); + Map<String, Object> serviceParams = runMrpForFacility.makeValid(context, "IN"); for (String mrpFacilityId : facilityIds) { serviceParams.put("facilityId", mrpFacilityId); - Map tmpResult = dispatcher.runSync("opentaps.runMrpForFacility", serviceParams, UtilCommon.SEC_IN_2_HOURS, false); // run in same transaction + Map<String, ?> tmpResult = dispatcher.runSync("opentaps.runMrpForFacility", serviceParams, UtilCommon.SEC_IN_2_HOURS, false); // run in same transaction if (ServiceUtil.isError(tmpResult) || ServiceUtil.isFailure(tmpResult)) { return tmpResult; } @@ -957,7 +958,7 @@ * @return a <code>Map</code> value */ @SuppressWarnings("unchecked") - public static Map runMrpForFacility(DispatchContext ctx, Map context) { + public static Map<String, ?> runMrpForFacility(DispatchContext ctx, Map context) { GenericDelegator delegator = ctx.getDelegator(); LocalDispatcher dispatcher = ctx.getDispatcher(); Locale locale = (Locale) context.get("locale"); @@ -1025,7 +1026,7 @@ boolean isBuilt = false; GenericValue routing = null; - Map parameters = null; + Map<String, Object> parameters = null; List listInventoryEventForMRP = null; ListIterator iteratorListInventoryEventForMRP = null; GenericValue inventoryEventForMRP = null; @@ -1045,7 +1046,7 @@ Debug.logInfo("Running MRP with warehouse facility [" + facilityId + "] and current timestamp of [" + now + "] and default years offset [" + defaultYearsOffset + "] and inventory receipt event buffer of [" + receiptEventBufferMilliseconds + "] ms", MODULE); - // Initialisation of the InventoryEventPlanned table, This table will contain the products we want to buy or build. + // Initialization of the InventoryEventPlanned table, This table will contain the products we want to buy or build. parameters = UtilMisc.toMap("facilityId", facilityId, "reInitialize", reinitializeInventoryEvents, "defaultYearsOffset", defaultYearsOffset, "now", now, "receiptEventBufferMilliseconds", receiptEventBufferMilliseconds, "userLogin", userLogin); // MRP run can be supplier, productStore, productStoreGroup or product specific @@ -1055,11 +1056,11 @@ parameters.put("productId", mrpTargetProductId); parameters.put("percentageOfSalesForecast", percentageOfSalesForecast); - Map result = dispatcher.runSync("opentaps.initInventoryEventPlanned", parameters, UtilCommon.SEC_IN_2_HOURS, false); // use same transaction + Map<String, ?> result = dispatcher.runSync("opentaps.initInventoryEventPlanned", parameters, UtilCommon.SEC_IN_2_HOURS, false); // use same transaction int bomLevel = 0; do { // AG23012008 - if this is a supplier or product specific MRP run then filter by the associated productIds - List mrpRunProductIds = (List) result.get("mrpRunProductIds"); + List<String> mrpRunProductIds = (List<String>) result.get("mrpRunProductIds"); //get the products from the InventoryEventPlanned table for the current billOfMaterialLevel (ie. BOM) listInventoryEventForMRP = getInventoryEventPlanned(facilityId, productStoreId, productStoreGroupId, mrpRunProductIds, bomLevel, delegator); @@ -1179,6 +1180,7 @@ routing = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", routingId)); } else { routing = null; + continue; } if (components != null && components.size() > 0) { BomNode node = ((BomNode) components.get(0)).getParentNode(); @@ -1186,9 +1188,10 @@ } else { isBuilt = false; } + // ##################################################### // calculate the ProposedOrder requirementStartDate and update the requirementStartDate object property. - Map routingTaskStartDate = proposedOrder.calculateStartDate(daysToShip, routing, delegator, dispatcher, userLogin); + Map<String, Timestamp> routingTaskStartDate = proposedOrder.calculateStartDate(daysToShip, routing, delegator, dispatcher, userLogin); if (isBuilt) { // process the product components processBomComponent(product, facilityId, proposedOrder.getQuantity(), proposedOrder.getRequirementStartDate(), now, routingTaskStartDate, components, timeZone, locale); @@ -1209,7 +1212,7 @@ } for (GenericValue mrpInventoryEventDetail : mrpInventoryEventDetails) { boolean skipAllocation = false; - HashMap orderRequirementCommitmentInput = new HashMap(); + Map<String, String> orderRequirementCommitmentInput = new HashMap<String, String>(); orderRequirementCommitmentInput.put("orderId", mrpInventoryEventDetail.getString("orderId")); orderRequirementCommitmentInput.put("orderItemSeqId", mrpInventoryEventDetail.getString("orderItemSeqId")); orderRequirementCommitmentInput.put("requirementId", requirementId); @@ -1269,10 +1272,12 @@ } // Debug.logInfo("event date [" + eventDate + "] plannedEventDate [" + plannedEventDate + "]", MODULE); - Map eventMap = UtilMisc.toMap("productId", product.getString("productId"), + Map<String, ?> eventMap = UtilMisc.toMap( + "productId", product.getString("productId"), "eventDate", UtilCommon.laterOf(plannedEventDate, now), "inventoryEventPlanTypeId", inventoryEventPlanTypeId, - "facilityId", facilityId); + "facilityId", facilityId + ); // TODO: Should this be shifted forward as well as it's a positive inventory event? // Debug.logInfo("about to create inventory event " + eventMap, MODULE); MrpInventoryEventServices.createOrUpdateMrpInventoryEvent(eventMap, new Double(proposedOrder.getQuantity()), new Double(initialQoh + proposedOrder.getQuantity()), eventName, proposedOrder.getRequirementStartDate().before(now), null, delegator); Modified: versions/1.0/trunk/hot-deploy/purchasing/src/org/opentaps/purchasing/mrp/UtilMrp.java =================================================================== --- versions/1.0/trunk/hot-deploy/purchasing/src/org/opentaps/purchasing/mrp/UtilMrp.java 2009-04-02 18:35:09 UTC (rev 11479) +++ versions/1.0/trunk/hot-deploy/purchasing/src/org/opentaps/purchasing/mrp/UtilMrp.java 2009-04-02 18:51:40 UTC (rev 11480) @@ -41,6 +41,7 @@ import org.ofbiz.entity.GenericDelegator; import org.ofbiz.entity.GenericEntityException; import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.condition.EntityCondition; import org.ofbiz.entity.condition.EntityConditionList; import org.ofbiz.entity.condition.EntityExpr; import org.ofbiz.entity.condition.EntityOperator; @@ -52,6 +53,7 @@ import org.opentaps.common.util.UtilCommon; import java.math.BigDecimal; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -88,18 +90,20 @@ return productStoreIds; } - public static List getProductIdsFromSupplier(String supplierPartyId, GenericDelegator delegator) throws GenericEntityException { - List productIds = null; + @SuppressWarnings("unchecked") + public static List<String> getProductIdsFromSupplier(String supplierPartyId, GenericDelegator delegator) throws GenericEntityException { + List<String> productIds = null; if (UtilValidate.isNotEmpty(supplierPartyId)) { - List conditions = UtilMisc.toList( new EntityExpr( "partyId" , EntityOperator.EQUALS , supplierPartyId ) ) ; + List<EntityCondition> conditions = UtilMisc.toList(new EntityExpr( "partyId" , EntityOperator.EQUALS , supplierPartyId)); conditions.add(EntityUtil.getFilterByDateExpr("availableFromDate", "availableThruDate")); - List fieldsToSelect = UtilMisc.toList( "productId" ); - List products = delegator.findByCondition("SupplierProduct", - new EntityConditionList(conditions, EntityOperator.AND), - null, - fieldsToSelect, - null, - UtilCommon.DISTINCT_READ_OPTIONS); + List<String> fieldsToSelect = Arrays.asList("productId"); + List<GenericValue> products = delegator.findByCondition("SupplierProduct", + new EntityConditionList(conditions, EntityOperator.AND), + null, + fieldsToSelect, + null, + UtilCommon.DISTINCT_READ_OPTIONS + ); productIds = EntityUtil.getFieldListFromEntityList(products, "productId", true); } return productIds; Modified: versions/1.0/trunk/hot-deploy/warehouse/src/org/opentaps/warehouse/manufacturing/ProductionRunServices.java =================================================================== --- versions/1.0/trunk/hot-deploy/warehouse/src/org/opentaps/warehouse/manufacturing/ProductionRunServices.java 2009-04-02 18:35:09 UTC (rev 11479) +++ versions/1.0/trunk/hot-deploy/warehouse/src/org/opentaps/warehouse/manufacturing/ProductionRunServices.java 2009-04-02 18:51:40 UTC (rev 11480) @@ -1563,16 +1563,25 @@ try { // find the routing from the WEGS - GenericValue routingWegs = getProductRouting(productId, workEffortId, applicableDate, quantity, delegator); + // There are three options: + // routinWegsId equals to + // 1. DEFAULT_ROUTING: routing isn't found, use default one + // 2. null: routing isn't found, we don't need default routing, return null + // 3. a String that is workEffortId + String routingWegsId = getProductRouting(productId, workEffortId, applicableDate, quantity, ignoreDefaultRouting, delegator); // find the routing WorkEffort GenericValue routing = null; - if (routingWegs != null) { - // find the related WorkEffort - routing = routingWegs.getRelatedOne("WorkEffort"); - } else if (!ignoreDefaultRouting) { - // use for the default routing - routing = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", "DEFAULT_ROUTING")); + if (UtilValidate.isNotEmpty(routingWegsId)) { + if ("DEFAULT_ROUTING".equals(routingWegsId)) { + if (!ignoreDefaultRouting) { + // use default routing + routing = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", "DEFAULT_ROUTING")); + } + } else { + // find the WorkEffort + routing = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", routingWegsId)); + } } // find the associated tasks, ordered by sequence @@ -1597,7 +1606,7 @@ } @SuppressWarnings("unchecked") - private static GenericValue getProductRouting(String productId, String workEffortId, Timestamp applicableDate, Double quantity, GenericDelegator delegator) throws GenericEntityException { + private static String getProductRouting(String productId, String workEffortId, Timestamp applicableDate, Double quantity, boolean ignoreDefRouting, GenericDelegator delegator) throws GenericEntityException { // find active routings for the productId and workEffortId (optional) List<EntityExpr> conditions = UtilMisc.toList( new EntityExpr("productId", EntityOperator.EQUALS, productId), @@ -1630,6 +1639,48 @@ routings = delegator.findByAnd("WorkEffortGoodStandard", conditions); } + // check routings for quantity + if (quantity != null) { + List<GenericValue> routingsWithQuantities = + EntityUtil.filterByCondition(routings, + new EntityConditionList( + Arrays.asList( + new EntityExpr("minQuantity", EntityOperator.NOT_EQUAL, null), + new EntityExpr("maxQuantity", EntityOperator.NOT_EQUAL, null)), EntityOperator.OR) + ); + if (UtilValidate.isNotEmpty(routingsWithQuantities)) { + // there are routings that have mixQuantity or maxQuantity on value + List<GenericValue> matchedRoutings = FastList.newInstance(); + for (GenericValue routing : routingsWithQuantities) { + Double minQuantity = routing.getDouble("minQuantity"); + Double maxQuantity = routing.getDouble("maxQuantity"); + if (minQuantity != null && maxQuantity != null) { + if (quantity.compareTo(minQuantity) >= 0 && quantity.compareTo(maxQuantity) <= 0) { + matchedRoutings.add(routing); + continue; + } + } else if (minQuantity != null) { + if (quantity.compareTo(minQuantity) >= 0) { + matchedRoutings.add(routing); + continue; + } + } else if (maxQuantity != null) { + if (quantity.compareTo(maxQuantity) <= 0) { + matchedRoutings.add(routing); + continue; + } + } + } + + // doesn't return any routing if we have WEGS with quantities but nothing match given quantity + if (UtilValidate.isEmpty(matchedRoutings)) { + return null; + } + + routings = matchedRoutings; + } + } + // if more than one routing is found, try to figure which one is best suited: // if no workEffortId was given then we are probably looking for a default routing, so filter out routings involved with special BOMs // else if a workEffortId was given take the first one found (TODO: discriminate using quantity to produce) @@ -1639,48 +1690,6 @@ Debug.logWarning("Found more than one routing applicable for product [" + productId + "], workEffortId [" + workEffortId + "] and date [" + applicableDate + "], using the first one found.", MODULE); } else { - if (quantity != null) { - // remaining routings should be coordinated with quantity. - List<GenericValue> routingsWithQuantities = - EntityUtil.filterByCondition(routings, - new EntityConditionList( - Arrays.asList( - new EntityExpr("minQuantity", EntityOperator.NOT_EQUAL, null), - new EntityExpr("maxQuantity", EntityOperator.NOT_EQUAL, null)), EntityOperator.OR) - ); - if (UtilValidate.isNotEmpty(routingsWithQuantities)) { - // there are routings that have mixQuantity or maxQuantity on value - List<GenericValue> matchedRoutings = FastList.newInstance(); - for (GenericValue routing : routingsWithQuantities) { - Double minQuantity = routing.getDouble("minQuantity"); - Double maxQuantity = routing.getDouble("maxQuantity"); - if (minQuantity != null && maxQuantity != null) { - if (quantity.compareTo(minQuantity) >= 0 && quantity.compareTo(maxQuantity) <= 0) { - matchedRoutings.add(routing); - continue; - } - } else if (minQuantity != null) { - if (quantity.compareTo(minQuantity) >= 0) { - matchedRoutings.add(routing); - continue; - } - } else if (maxQuantity != null) { - if (quantity.compareTo(maxQuantity) <= 0) { - matchedRoutings.add(routing); - continue; - } - } - } - - // doesn't return any routing if we have WEGS with quantities but nothing match given quantity - if (UtilValidate.isEmpty(matchedRoutings)) { - return null; - } - - routings = matchedRoutings; - } - } - // if no workEffortId was given, remove routings that are involved in alternate BOMs as we are looking for a default routing List<GenericValue> alternateBomRoutings = delegator.findByAnd("ProductAssoc", UtilMisc.toMap("productId", productId, "productAssocTypeId", "MANUF_COMPONENT")); List<String> alternateBomRoutingIds = EntityUtil.getFieldListFromEntityList(alternateBomRoutings, "specificRoutingWorkEffortId", true); @@ -1691,18 +1700,20 @@ Debug.logWarning("Found more than one routing with no special BOM applicable for product [" + productId + "] and date [" + applicableDate + "], using the first one found.", MODULE); } if (routingsNoSpecialBom.size() >= 1) { - return routingsNoSpecialBom.get(0); + return routingsNoSpecialBom.get(0).getString("workEffortId"); } // else all the found routings have special BOMs, well just return the first one of the list Debug.logWarning("Found more than one routing applicable for product [" + productId + "] and date [" + applicableDate + "], using the first one found.", MODULE); } } - return EntityUtil.getFirst(routings); + + GenericValue routingProductLink = EntityUtil.getFirst(routings); + return (routingProductLink != null ? routingProductLink.getString("workEffortId") : "DEFAULT_ROUTING"); } /** - * Retrieves the disassembly template for a product, otherwise a default disassembly template. + * Retrieves the disassemble template for a product, otherwise a default disassemble template. * * @param dctx a <code>DispatchContext</code> value * @param context a <code>Map</code> value @@ -2022,9 +2033,8 @@ serviceContext.put("workEffortId", productionRunId); serviceContext.put("actualCompletionDate", UtilDateTime.nowTimestamp()); serviceContext.put("userLogin", userLogin); - Map resultService = null; try { - resultService = dispatcher.runSync("updateWorkEffort", serviceContext); + dispatcher.runSync("updateWorkEffort", serviceContext); } catch (GenericServiceException e) { Debug.logError(e, "Problem calling the updateWorkEffort service", MODULE); return UtilMessage.createAndLogServiceError("ManufacturingProductionRunStatusNotChanged", locale, MODULE); |