| /******************************************************************************* |
| * 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.ofbiz.manufacturing.bom; |
| |
| import java.math.BigDecimal; |
| import java.sql.Timestamp; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import javolution.util.FastList; |
| import javolution.util.FastMap; |
| |
| import org.ofbiz.base.util.Debug; |
| import org.ofbiz.base.util.UtilGenerics; |
| import org.ofbiz.base.util.UtilMisc; |
| import org.ofbiz.base.util.UtilProperties; |
| import org.ofbiz.base.util.UtilValidate; |
| import org.ofbiz.entity.Delegator; |
| import org.ofbiz.entity.GenericEntityException; |
| import org.ofbiz.entity.GenericValue; |
| import org.ofbiz.entity.util.EntityQuery; |
| import org.ofbiz.order.order.OrderReadHelper; |
| import org.ofbiz.service.DispatchContext; |
| import org.ofbiz.service.GenericServiceException; |
| import org.ofbiz.service.LocalDispatcher; |
| import org.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 = FastMap.newInstance(); |
| 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 = FastList.newInstance(); |
| 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 = FastMap.newInstance(); |
| 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 = FastList.newInstance(); |
| 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 (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 = FastMap.newInstance(); |
| 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 = FastList.newInstance(); |
| 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 (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 = FastMap.newInstance(); |
| 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 = FastMap.newInstance(); |
| 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 = FastMap.newInstance(); |
| 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 = FastList.newInstance(); |
| 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 = FastList.newInstance(); |
| for (BOMNode node : components) { |
| Map<String, Object> componentMap = FastMap.newInstance(); |
| 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 = FastMap.newInstance(); |
| 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 = FastList.newInstance(); |
| List<BOMNode> notAssembledComponents = FastList.newInstance(); |
| 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 = FastMap.newInstance(); |
| 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.isEmpty(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 = FastMap.newInstance(); |
| Map<String, Object> partyOrderShipments = FastMap.newInstance(); |
| 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 = FastList.newInstance(); |
| 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 = FastMap.newInstance(); |
| 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 = FastMap.newInstance(); |
| for (Map.Entry<String, Object> partyOrderShipment : partyOrderShipments.entrySet()) { |
| Map<String, List<Map<String, Object>>> boxTypeContent = FastMap.newInstance(); |
| 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 = FastMap.newInstance(); |
| 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 = FastList.newInstance(); |
| 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 = FastMap.newInstance(); |
| 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 = FastList.newInstance(); |
| 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 = FastMap.newInstance(); |
| 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 = FastList.newInstance(); |
| 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; |
| } |
| |
| } |