/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. *******************************************************************************/ package org.apache.ofbiz.manufacturing.bom; import java.math.BigDecimal; import java.sql.Timestamp; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntityException; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.order.order.OrderReadHelper; import org.apache.ofbiz.service.DispatchContext; import org.apache.ofbiz.service.GenericServiceException; import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ServiceUtil; /** Bills of Materials' services implementation. * These services are useful when dealing with product's * bills of materials. */ public class BOMServices { public static final String module = BOMServices.class.getName(); public static final String resource = "ManufacturingUiLabels"; /** Returns the product's low level code (llc) i.e. the maximum depth * in which the productId can be found in any of the * bills of materials of bomType type. * If the bomType input field is not passed then the depth is searched for all the bom types and the lowest depth is returned. * @param dctx the dispatch context * @param context the context * @return returns the product's low level code (llc) i.e. the maximum depth */ public static Map<String, Object> getMaxDepth(DispatchContext dctx, Map<String, ? extends Object> context) { Map<String, Object> result = new HashMap<String, Object>(); Delegator delegator = dctx.getDelegator(); String productId = (String) context.get("productId"); String fromDateStr = (String) context.get("fromDate"); String bomType = (String) context.get("bomType"); Locale locale = (Locale) context.get("locale"); Date fromDate = null; if (UtilValidate.isNotEmpty(fromDateStr)) { try { fromDate = Timestamp.valueOf(fromDateStr); } catch (Exception e) { } } if (fromDate == null) { fromDate = new Date(); } List<String> bomTypes = new LinkedList<String>(); if (bomType == null) { try { List<GenericValue> bomTypesValues = EntityQuery.use(delegator).from("ProductAssocType") .where("parentTypeId", "PRODUCT_COMPONENT").queryList(); for (GenericValue bomTypesValue : bomTypesValues) { bomTypes.add(bomTypesValue.getString("productAssocTypeId")); } } catch (GenericEntityException gee) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorRunningMaxDethAlgorithm", UtilMisc.toMap("errorString", gee.getMessage()), locale)); } } else { bomTypes.add(bomType); } int depth = 0; int maxDepth = 0; try { for (String oneBomType : bomTypes) { depth = BOMHelper.getMaxDepth(productId, oneBomType, fromDate, delegator); if (depth > maxDepth) { maxDepth = depth; } } } catch (GenericEntityException gee) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorRunningMaxDethAlgorithm", UtilMisc.toMap("errorString", gee.getMessage()), locale)); } result.put("depth", Long.valueOf(maxDepth)); return result; } /** Updates the product's low level code (llc) * Given a product id, computes and updates the product's low level code (field billOfMaterialLevel in Product entity). * It also updates the llc of all the product's descendants. * For the llc only the manufacturing bom ("MANUF_COMPONENT") is considered. * @param dctx the distach context * @param context the context * @return the results of the updates the product's low level code */ public static Map<String, Object> updateLowLevelCode(DispatchContext dctx, Map<String, ? extends Object> context) { Map<String, Object> result = new HashMap<String, Object>(); Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); String productId = (String) context.get("productIdTo"); Boolean alsoComponents = (Boolean) context.get("alsoComponents"); Locale locale = (Locale) context.get("locale"); if (alsoComponents == null) { alsoComponents = Boolean.TRUE; } Boolean alsoVariants = (Boolean) context.get("alsoVariants"); if (alsoVariants == null) { alsoVariants = Boolean.TRUE; } Long llc = null; try { GenericValue product = EntityQuery.use(delegator).from("Product").where("productId", productId).queryOne(); Map<String, Object> depthResult = dispatcher.runSync("getMaxDepth", UtilMisc.toMap("productId", productId, "bomType", "MANUF_COMPONENT")); llc = (Long)depthResult.get("depth"); // If the product is a variant of a virtual, then the billOfMaterialLevel cannot be // lower than the billOfMaterialLevel of the virtual product. List<GenericValue> virtualProducts = EntityQuery.use(delegator).from("ProductAssoc") .where("productIdTo", productId, "productAssocTypeId", "PRODUCT_VARIANT") .filterByDate().queryList(); int virtualMaxDepth = 0; for (GenericValue oneVirtualProductAssoc : virtualProducts) { int virtualDepth = 0; GenericValue virtualProduct = EntityQuery.use(delegator).from("Product").where("productId", oneVirtualProductAssoc.getString("productId")).queryOne(); if (virtualProduct.get("billOfMaterialLevel") != null) { virtualDepth = virtualProduct.getLong("billOfMaterialLevel").intValue(); } else { virtualDepth = 0; } if (virtualDepth > virtualMaxDepth) { virtualMaxDepth = virtualDepth; } } if (virtualMaxDepth > llc.intValue()) { llc = Long.valueOf(virtualMaxDepth); } product.set("billOfMaterialLevel", llc); product.store(); if (alsoComponents.booleanValue()) { Map<String, Object> treeResult = dispatcher.runSync("getBOMTree", UtilMisc.toMap("productId", productId, "bomType", "MANUF_COMPONENT")); BOMTree tree = (BOMTree)treeResult.get("tree"); List<BOMNode> products = new LinkedList<BOMNode>(); tree.print(products, llc.intValue()); for (int i = 0; i < products.size(); i++) { BOMNode oneNode = products.get(i); GenericValue oneProduct = oneNode.getProduct(); int lev = 0; if (oneProduct.get("billOfMaterialLevel") != null) { lev = oneProduct.getLong("billOfMaterialLevel").intValue(); } if (lev < oneNode.getDepth()) { oneProduct.set("billOfMaterialLevel", Long.valueOf(oneNode.getDepth())); oneProduct.store(); } } } if (alsoVariants.booleanValue()) { List<GenericValue> variantProducts = EntityQuery.use(delegator).from("ProductAssoc") .where("productId", productId, "productAssocTypeId", "PRODUCT_VARIANT") .filterByDate().queryList(); for (GenericValue oneVariantProductAssoc : variantProducts) { GenericValue variantProduct = EntityQuery.use(delegator).from("Product").where("productId", oneVariantProductAssoc.getString("productId")).queryOne(); variantProduct.set("billOfMaterialLevel", llc); variantProduct.store(); } } } catch (GenericEntityException|GenericServiceException ge) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorRunningUpdateLowLevelCode", UtilMisc.toMap("errorString", ge.getMessage()), locale)); } catch (Exception e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorRunningUpdateLowLevelCode", UtilMisc.toMap("errorString", e.getMessage()), locale)); } result.put("lowLevelCode", llc); return result; } /** Updates the product's low level code (llc) for all the products in the Product entity. * For the llc only the manufacturing bom ("MANUF_COMPONENT") is considered. * @param dctx the distach context * @param context the context * @return the results of the updates the product's low level code */ public static Map<String, Object> initLowLevelCode(DispatchContext dctx, Map<String, ? extends Object> context) { Map<String, Object> result = new HashMap<String, Object>(); Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); Locale locale = (Locale) context.get("locale"); try { List<GenericValue> products = EntityQuery.use(delegator).from("Product").orderBy("isVirtual DESC").queryList(); Long zero = Long.valueOf(0); List<GenericValue> allProducts = new LinkedList<GenericValue>(); for (GenericValue product : products) { product.set("billOfMaterialLevel", zero); allProducts.add(product); } delegator.storeAll(allProducts); Debug.logInfo("Low Level Code set to 0 for all the products", module); for (GenericValue product : products) { try { Map<String, Object> depthResult = dispatcher.runSync("updateLowLevelCode", UtilMisc.<String, Object>toMap("productIdTo", product.getString("productId"), "alsoComponents", Boolean.valueOf(false), "alsoVariants", Boolean.valueOf(false))); Debug.logInfo("Product [" + product.getString("productId") + "] Low Level Code [" + depthResult.get("lowLevelCode") + "]", module); } catch (Exception exc) { Debug.logWarning(exc.getMessage(), module); } } // FIXME: also all the variants llc should be updated? } catch (GenericEntityException e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorRunningInitLowLevelCode", UtilMisc.toMap("errorString", e.getMessage()), locale)); } catch (Exception e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorRunningInitLowLevelCode", UtilMisc.toMap("errorString", e.getMessage()), locale)); } return result; } /** Returns the ProductAssoc generic value for a duplicate productIdKey * ancestor if present, null otherwise. * Useful to avoid loops when adding new assocs (components) * to a bill of materials. * @param dctx the distach context * @param context the context * @return returns the ProductAssoc generic value for a duplicate productIdKey ancestor if present */ public static Map<String, Object> searchDuplicatedAncestor(DispatchContext dctx, Map<String, ? extends Object> context) { Map<String, Object> result = new HashMap<String, Object>(); Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue)context.get("userLogin"); Locale locale = (Locale) context.get("locale"); String productId = (String) context.get("productId"); String productIdKey = (String) context.get("productIdTo"); Timestamp fromDate = (Timestamp) context.get("fromDate"); String bomType = (String) context.get("productAssocTypeId"); if (fromDate == null) { fromDate = Timestamp.valueOf((new Date()).toString()); } GenericValue duplicatedProductAssoc = null; try { duplicatedProductAssoc = BOMHelper.searchDuplicatedAncestor(productId, productIdKey, bomType, fromDate, delegator, dispatcher, userLogin); } catch (GenericEntityException gee) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorRunningDuplicatedAncestorSearch", UtilMisc.toMap("errorString", gee.getMessage()), locale)); } result.put("duplicatedProductAssoc", duplicatedProductAssoc); return result; } /** It reads the product's bill of materials, * if necessary configures it, and it returns * an object (see {@link BOMTree} * and {@link BOMNode}) that represents a * configured bill of material tree. * Useful for tree traversal (breakdown, explosion, implosion). * @param dctx the distach context * @param context the context * @return return the bill of material tree */ public static Map<String, Object> getBOMTree(DispatchContext dctx, Map<String, ? extends Object> context) { Map<String, Object> result = new HashMap<String, Object>(); Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue)context.get("userLogin"); String productId = (String) context.get("productId"); String fromDateStr = (String) context.get("fromDate"); String bomType = (String) context.get("bomType"); Integer type = (Integer) context.get("type"); BigDecimal quantity = (BigDecimal) context.get("quantity"); BigDecimal amount = (BigDecimal) context.get("amount"); Locale locale = (Locale) context.get("locale"); if (type == null) { type = Integer.valueOf(0); } Date fromDate = null; if (UtilValidate.isNotEmpty(fromDateStr)) { try { fromDate = Timestamp.valueOf(fromDateStr); } catch (Exception e) { } } if (fromDate == null) { fromDate = new Date(); } BOMTree tree = null; try { tree = new BOMTree(productId, bomType, fromDate, type.intValue(), delegator, dispatcher, userLogin); } catch (GenericEntityException gee) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorCreatingBillOfMaterialsTree", UtilMisc.toMap("errorString", gee.getMessage()), locale)); } if (tree != null && quantity != null) { tree.setRootQuantity(quantity); } if (tree != null && amount != null) { tree.setRootAmount(amount); } result.put("tree", tree); return result; } /** It reads the product's bill of materials, * if necessary configures it, and it returns its (possibly configured) components in * a List of {@link BOMNode}). * @param dctx the distach context * @param context the context * @return return the list of manufacturing components */ public static Map<String, Object> getManufacturingComponents(DispatchContext dctx, Map<String, ? extends Object> context) { Map<String, Object> result = new HashMap<String, Object>(); Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue)context.get("userLogin"); String productId = (String) context.get("productId"); BigDecimal quantity = (BigDecimal) context.get("quantity"); BigDecimal amount = (BigDecimal) context.get("amount"); String fromDateStr = (String) context.get("fromDate"); Boolean excludeWIPs = (Boolean) context.get("excludeWIPs"); Locale locale = (Locale) context.get("locale"); if (quantity == null) { quantity = BigDecimal.ONE; } if (amount == null) { amount = BigDecimal.ZERO; } Date fromDate = null; if (UtilValidate.isNotEmpty(fromDateStr)) { try { fromDate = Timestamp.valueOf(fromDateStr); } catch (Exception e) { } } if (fromDate == null) { fromDate = new Date(); } if (excludeWIPs == null) { excludeWIPs = Boolean.TRUE; } // // Components // BOMTree tree = null; List<BOMNode> components = new LinkedList<BOMNode>(); try { tree = new BOMTree(productId, "MANUF_COMPONENT", fromDate, BOMTree.EXPLOSION_SINGLE_LEVEL, delegator, dispatcher, userLogin); tree.setRootQuantity(quantity); tree.setRootAmount(amount); tree.print(components, excludeWIPs.booleanValue()); if (components.size() > 0) components.remove(0); } catch (GenericEntityException gee) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorCreatingBillOfMaterialsTree", UtilMisc.toMap("errorString", gee.getMessage()), locale)); } // // Product routing // String workEffortId = null; try { Map<String, Object> routingInMap = UtilMisc.toMap("productId", productId, "ignoreDefaultRouting", "Y", "userLogin", userLogin); Map<String, Object> routingOutMap = dispatcher.runSync("getProductRouting", routingInMap); GenericValue routing = (GenericValue)routingOutMap.get("routing"); if (routing == null) { // try to find a routing linked to the virtual product routingInMap = UtilMisc.toMap("productId", tree.getRoot().getProduct().getString("productId"), "userLogin", userLogin); routingOutMap = dispatcher.runSync("getProductRouting", routingInMap); routing = (GenericValue)routingOutMap.get("routing"); } if (routing != null) { workEffortId = routing.getString("workEffortId"); } } catch (GenericServiceException gse) { Debug.logWarning(gse.getMessage(), module); } if (workEffortId != null) { result.put("workEffortId", workEffortId); } result.put("components", components); // also return a componentMap (useful in scripts and simple language code) List<Map<String, Object>> componentsMap = new LinkedList<Map<String,Object>>(); for (BOMNode node : components) { Map<String, Object> componentMap = new HashMap<String, Object>(); componentMap.put("product", node.getProduct()); componentMap.put("quantity", node.getQuantity()); componentsMap.add(componentMap); } result.put("componentsMap", componentsMap); return result; } public static Map<String, Object> getNotAssembledComponents(DispatchContext dctx, Map<String, ? extends Object> context) { Map<String, Object> result = new HashMap<String, Object>(); Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); String productId = (String) context.get("productId"); BigDecimal quantity = (BigDecimal) context.get("quantity"); BigDecimal amount = (BigDecimal) context.get("amount"); String fromDateStr = (String) context.get("fromDate"); GenericValue userLogin = (GenericValue)context.get("userLogin"); Locale locale = (Locale) context.get("locale"); if (quantity == null) { quantity = BigDecimal.ONE; } if (amount == null) { amount = BigDecimal.ZERO; } Date fromDate = null; if (UtilValidate.isNotEmpty(fromDateStr)) { try { fromDate = Timestamp.valueOf(fromDateStr); } catch (Exception e) { } } if (fromDate == null) { fromDate = new Date(); } BOMTree tree = null; List<BOMNode> components = new LinkedList<BOMNode>(); List<BOMNode> notAssembledComponents = new LinkedList<BOMNode>(); try { tree = new BOMTree(productId, "MANUF_COMPONENT", fromDate, BOMTree.EXPLOSION_MANUFACTURING, delegator, dispatcher, userLogin); tree.setRootQuantity(quantity); tree.setRootAmount(amount); tree.print(components); } catch (GenericEntityException gee) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorCreatingBillOfMaterialsTree", UtilMisc.toMap("errorString", gee.getMessage()), locale)); } for (BOMNode oneComponent : components) { if (!oneComponent.isManufactured()) { notAssembledComponents.add(oneComponent); } } result.put("notAssembledComponents" , notAssembledComponents); return result; } // --------------------------------------------- // Service for the Product (Shipment) component // public static Map<String, Object> createShipmentPackages(DispatchContext dctx, Map<String, ? extends Object> context) { Map<String, Object> result = new HashMap<String, Object>(); Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); Locale locale = (Locale) context.get("locale"); GenericValue userLogin = (GenericValue)context.get("userLogin"); String shipmentId = (String) context.get("shipmentId"); try { List<GenericValue> packages = EntityQuery.use(delegator).from("ShipmentPackage").where("shipmentId", shipmentId).queryList(); if (UtilValidate.isNotEmpty(packages)) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomPackageAlreadyFound", locale)); } } catch (GenericEntityException gee) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorLoadingShipmentPackages", locale)); } // ShipmentItems are loaded List<GenericValue> shipmentItems = null; try { shipmentItems = EntityQuery.use(delegator).from("ShipmentItem").where("shipmentId", shipmentId).queryList(); } catch (GenericEntityException gee) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorLoadingShipmentItems", locale)); } Map<String, Object> orderReadHelpers = new HashMap<String, Object>(); Map<String, Object> partyOrderShipments = new HashMap<String, Object>(); for (GenericValue shipmentItem : shipmentItems) { // Get the OrderShipments GenericValue orderShipment = null; try { orderShipment = EntityQuery.use(delegator).from("OrderShipment") .where("shipmentId", shipmentId, "shipmentItemSeqId", shipmentItem.get("shipmentItemSeqId")) .queryFirst(); } catch (GenericEntityException e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingPackageConfiguratorError", locale)); } if (orderShipment != null && !orderReadHelpers.containsKey(orderShipment.getString("orderId"))) { orderReadHelpers.put(orderShipment.getString("orderId"), new OrderReadHelper(delegator, orderShipment.getString("orderId"))); } OrderReadHelper orderReadHelper = (OrderReadHelper)orderReadHelpers.get(orderShipment.getString("orderId")); if (orderReadHelper != null) { Map<String, Object> orderShipmentReadMap = UtilMisc.toMap("orderShipment", orderShipment, "orderReadHelper", orderReadHelper); String partyId = (orderReadHelper.getPlacingParty() != null? orderReadHelper.getPlacingParty().getString("partyId"): null); // FIXME: is it the customer? if (partyId != null) { if (!partyOrderShipments.containsKey(partyId)) { List<Map<String, Object>> orderShipmentReadMapList = new LinkedList<Map<String,Object>>(); partyOrderShipments.put(partyId, orderShipmentReadMapList); } List<Map<String, Object>> orderShipmentReadMapList = UtilGenerics.checkList(partyOrderShipments.get(partyId)); orderShipmentReadMapList.add(orderShipmentReadMap); } } } // For each party: try to expand the shipment item products // (search for components that needs to be packaged). for (Map.Entry<String, Object> partyOrderShipment : partyOrderShipments.entrySet()) { List<Map<String, Object>> orderShipmentReadMapList = UtilGenerics.checkList(partyOrderShipment.getValue()); for (int i = 0; i < orderShipmentReadMapList.size(); i++) { Map<String, Object> orderShipmentReadMap = UtilGenerics.checkMap(orderShipmentReadMapList.get(i)); GenericValue orderShipment = (GenericValue)orderShipmentReadMap.get("orderShipment"); OrderReadHelper orderReadHelper = (OrderReadHelper)orderShipmentReadMap.get("orderReadHelper"); GenericValue orderItem = orderReadHelper.getOrderItem(orderShipment.getString("orderItemSeqId")); // getProductsInPackages Map<String, Object> serviceContext = new HashMap<String, Object>(); serviceContext.put("productId", orderItem.getString("productId")); serviceContext.put("quantity", orderShipment.getBigDecimal("quantity")); Map<String, Object> resultService = null; try { resultService = dispatcher.runSync("getProductsInPackages", serviceContext); } catch (GenericServiceException e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingPackageConfiguratorError", locale)); } List<BOMNode> productsInPackages = UtilGenerics.checkList(resultService.get("productsInPackages")); if (productsInPackages.size() == 1) { BOMNode root = productsInPackages.get(0); String rootProductId = (root.getSubstitutedNode() != null? root.getSubstitutedNode().getProduct().getString("productId"): root.getProduct().getString("productId")); if (orderItem.getString("productId").equals(rootProductId)) { productsInPackages = null; } } if (productsInPackages != null && productsInPackages.size() == 0) { productsInPackages = null; } if (UtilValidate.isNotEmpty(productsInPackages)) { orderShipmentReadMap.put("productsInPackages", productsInPackages); } } } // Group together products and components // of the same box type. Map<String, GenericValue> boxTypes = new HashMap<String, GenericValue>(); for (Map.Entry<String, Object> partyOrderShipment : partyOrderShipments.entrySet()) { Map<String, List<Map<String, Object>>> boxTypeContent = new HashMap<String, List<Map<String,Object>>>(); List<Map<String, Object>> orderShipmentReadMapList = UtilGenerics.checkList(partyOrderShipment.getValue()); for (int i = 0; i < orderShipmentReadMapList.size(); i++) { Map<String, Object> orderShipmentReadMap = UtilGenerics.checkMap(orderShipmentReadMapList.get(i)); GenericValue orderShipment = (GenericValue)orderShipmentReadMap.get("orderShipment"); OrderReadHelper orderReadHelper = (OrderReadHelper)orderShipmentReadMap.get("orderReadHelper"); List<BOMNode> productsInPackages = UtilGenerics.checkList(orderShipmentReadMap.get("productsInPackages")); if (productsInPackages != null) { // there are subcomponents: // this is a multi package shipment item for (int j = 0; j < productsInPackages.size(); j++) { BOMNode component = productsInPackages.get(j); Map<String, Object> boxTypeContentMap = new HashMap<String, Object>(); boxTypeContentMap.put("content", orderShipmentReadMap); boxTypeContentMap.put("componentIndex", Integer.valueOf(j)); GenericValue product = component.getProduct(); String boxTypeId = product.getString("shipmentBoxTypeId"); if (boxTypeId != null) { if (!boxTypes.containsKey(boxTypeId)) { GenericValue boxType = null; try { boxType = EntityQuery.use(delegator).from("ShipmentBoxType").where("shipmentBoxTypeId", boxTypeId).queryOne(); } catch (GenericEntityException e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingPackageConfiguratorError", locale)); } boxTypes.put(boxTypeId, boxType); List<Map<String, Object>> box = new LinkedList<Map<String,Object>>(); boxTypeContent.put(boxTypeId, box); } List<Map<String, Object>> boxTypeContentList = UtilGenerics.checkList(boxTypeContent.get(boxTypeId)); boxTypeContentList.add(boxTypeContentMap); } } } else { // no subcomponents, the product has its own package: // this is a single package shipment item Map<String, Object> boxTypeContentMap = new HashMap<String, Object>(); boxTypeContentMap.put("content", orderShipmentReadMap); GenericValue orderItem = orderReadHelper.getOrderItem(orderShipment.getString("orderItemSeqId")); GenericValue product = null; try { product = orderItem.getRelatedOne("Product", false); } catch (GenericEntityException e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingPackageConfiguratorError", locale)); } String boxTypeId = product.getString("shipmentBoxTypeId"); if (boxTypeId != null) { if (!boxTypes.containsKey(boxTypeId)) { GenericValue boxType = null; try { boxType = EntityQuery.use(delegator).from("ShipmentBoxType").where("shipmentBoxTypeId", boxTypeId).queryOne(); } catch (GenericEntityException e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingPackageConfiguratorError", locale)); } boxTypes.put(boxTypeId, boxType); List<Map<String, Object>> box = new LinkedList<Map<String,Object>>(); boxTypeContent.put(boxTypeId, box); } List<Map<String, Object>> boxTypeContentList = UtilGenerics.checkList(boxTypeContent.get(boxTypeId)); boxTypeContentList.add(boxTypeContentMap); } } } // The packages and package contents are created. for (Map.Entry<String, List<Map<String, Object>>> boxTypeContentEntry : boxTypeContent.entrySet()) { String boxTypeId = boxTypeContentEntry.getKey(); List<Map<String, Object>> contentList = UtilGenerics.checkList(boxTypeContentEntry.getValue()); GenericValue boxType = boxTypes.get(boxTypeId); BigDecimal boxWidth = boxType.getBigDecimal("boxLength"); BigDecimal totalWidth = BigDecimal.ZERO; if (boxWidth == null) { boxWidth = BigDecimal.ZERO; } String shipmentPackageSeqId = null; for (int i = 0; i < contentList.size(); i++) { Map<String, Object> contentMap = UtilGenerics.checkMap(contentList.get(i)); Map<String, Object> content = UtilGenerics.checkMap(contentMap.get("content")); OrderReadHelper orderReadHelper = (OrderReadHelper)content.get("orderReadHelper"); List<BOMNode> productsInPackages = UtilGenerics.checkList(content.get("productsInPackages")); GenericValue orderShipment = (GenericValue)content.get("orderShipment"); GenericValue product = null; BigDecimal quantity = BigDecimal.ZERO; boolean subProduct = contentMap.containsKey("componentIndex"); if (subProduct) { // multi package Integer index = (Integer)contentMap.get("componentIndex"); BOMNode component = productsInPackages.get(index.intValue()); product = component.getProduct(); quantity = component.getQuantity(); } else { // single package GenericValue orderItem = orderReadHelper.getOrderItem(orderShipment.getString("orderItemSeqId")); try { product = orderItem.getRelatedOne("Product", false); } catch (GenericEntityException e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingPackageConfiguratorError", locale)); } quantity = orderShipment.getBigDecimal("quantity"); } BigDecimal productDepth = product.getBigDecimal("shippingDepth"); if (productDepth == null) { productDepth = product.getBigDecimal("productDepth"); } if (productDepth == null) { productDepth = BigDecimal.ONE; } BigDecimal firstMaxNumOfProducts = boxWidth.subtract(totalWidth).divide(productDepth, 0, BigDecimal.ROUND_FLOOR); if (firstMaxNumOfProducts.compareTo(BigDecimal.ZERO) == 0) firstMaxNumOfProducts = BigDecimal.ONE; // BigDecimal maxNumOfProducts = boxWidth.divide(productDepth, 0, BigDecimal.ROUND_FLOOR); if (maxNumOfProducts.compareTo(BigDecimal.ZERO) == 0) maxNumOfProducts = BigDecimal.ONE; BigDecimal remQuantity = quantity; boolean isFirst = true; while (remQuantity.compareTo(BigDecimal.ZERO) > 0) { BigDecimal maxQuantity = BigDecimal.ZERO; if (isFirst) { maxQuantity = firstMaxNumOfProducts; isFirst = false; } else { maxQuantity = maxNumOfProducts; } BigDecimal qty = (remQuantity.compareTo(maxQuantity) < 0 ? remQuantity : maxQuantity); // If needed, create the package if (shipmentPackageSeqId == null) { try { Map<String, Object> resultService = dispatcher.runSync("createShipmentPackage", UtilMisc.<String, Object>toMap("shipmentId", orderShipment.getString("shipmentId"), "shipmentBoxTypeId", boxTypeId, "userLogin", userLogin)); shipmentPackageSeqId = (String)resultService.get("shipmentPackageSeqId"); } catch (GenericServiceException e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingPackageConfiguratorError", locale)); } totalWidth = BigDecimal.ZERO; } try { Map<String, Object> inputMap = null; if (subProduct) { inputMap = UtilMisc.toMap("shipmentId", orderShipment.getString("shipmentId"), "shipmentPackageSeqId", shipmentPackageSeqId, "shipmentItemSeqId", orderShipment.getString("shipmentItemSeqId"), "subProductId", product.getString("productId"), "userLogin", userLogin, "subProductQuantity", qty); } else { inputMap = UtilMisc.toMap("shipmentId", orderShipment.getString("shipmentId"), "shipmentPackageSeqId", shipmentPackageSeqId, "shipmentItemSeqId", orderShipment.getString("shipmentItemSeqId"), "userLogin", userLogin, "quantity", qty); } dispatcher.runSync("createShipmentPackageContent", inputMap); } catch (GenericServiceException e) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingPackageConfiguratorError", locale)); } totalWidth = totalWidth.add(qty.multiply(productDepth)); if (qty.compareTo(maxQuantity) == 0) shipmentPackageSeqId = null; remQuantity = remQuantity.subtract(qty); } } } } return result; } /** It reads the product's bill of materials, * if necessary configures it, and it returns its (possibly configured) components in * a List of {@link BOMNode}). * @param dctx the distach context * @param context the context * @return returns the list of products in packages */ public static Map<String, Object> getProductsInPackages(DispatchContext dctx, Map<String, ? extends Object> context) { Map<String, Object> result = new HashMap<String, Object>(); Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue)context.get("userLogin"); Locale locale = (Locale) context.get("locale"); String productId = (String) context.get("productId"); BigDecimal quantity = (BigDecimal) context.get("quantity"); String fromDateStr = (String) context.get("fromDate"); if (quantity == null) { quantity = BigDecimal.ONE; } Date fromDate = null; if (UtilValidate.isNotEmpty(fromDateStr)) { try { fromDate = Timestamp.valueOf(fromDateStr); } catch (Exception e) { } } if (fromDate == null) { fromDate = new Date(); } // // Components // BOMTree tree = null; List<BOMNode> components = new LinkedList<BOMNode>(); try { tree = new BOMTree(productId, "MANUF_COMPONENT", fromDate, BOMTree.EXPLOSION_MANUFACTURING, delegator, dispatcher, userLogin); tree.setRootQuantity(quantity); tree.getProductsInPackages(components); } catch (GenericEntityException gee) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ManufacturingBomErrorCreatingBillOfMaterialsTree", UtilMisc.toMap("errorString", gee.getMessage()), locale)); } result.put("productsInPackages", components); return result; } }