| /******************************************************************************* |
| * 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.util.Date; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javolution.util.FastList; |
| |
| import org.ofbiz.base.util.UtilMisc; |
| 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.entity.util.EntityUtil; |
| import org.ofbiz.product.store.ProductStoreWorker; |
| import org.ofbiz.service.LocalDispatcher; |
| |
| /** It represents an (in-memory) bill of materials (in which each |
| * component is an BOMNode) |
| * Useful for tree traversal (breakdown, explosion, implosion). |
| */ |
| public class BOMTree { |
| public static final int EXPLOSION = 0; |
| public static final int EXPLOSION_SINGLE_LEVEL = 1; |
| public static final int EXPLOSION_MANUFACTURING = 2; |
| public static final int IMPLOSION = 3; |
| |
| protected LocalDispatcher dispatcher = null; |
| protected Delegator delegator = null; |
| |
| BOMNode root; |
| BigDecimal rootQuantity; |
| BigDecimal rootAmount; |
| Date inDate; |
| String bomTypeId; |
| GenericValue inputProduct; |
| |
| /** Creates a new instance of BOMTree by reading downward |
| * the productId's bill of materials (explosion). |
| * If virtual products are found, it tries to configure them by running |
| * the Product Configurator. |
| * @param productId The product for which we want to get the bom. |
| * @param bomTypeId The bill of materials type (e.g. manufacturing, engineering, ...) |
| * @param inDate Validity date (if null, today is used). |
| * |
| * @param delegator The delegator used. |
| * @throws GenericEntityException If a db problem occurs. |
| * |
| */ |
| public BOMTree(String productId, String bomTypeId, Date inDate, Delegator delegator, LocalDispatcher dispatcher, GenericValue userLogin) throws GenericEntityException { |
| this(productId, bomTypeId, inDate, EXPLOSION, delegator, dispatcher, userLogin); |
| } |
| |
| /** Creates a new instance of BOMTree by reading |
| * the productId's bill of materials (upward or downward). |
| * If virtual products are found, it tries to configure them by running |
| * the Product Configurator. |
| * @param productId The product for which we want to get the bom. |
| * @param bomTypeId The bill of materials type (e.g. manufacturing, engineering, ...) |
| * @param inDate Validity date (if null, today is used). |
| * |
| * @param type if equals to EXPLOSION, a downward visit is performed (explosion); |
| * if equals to EXPLOSION_SINGLE_LEVEL, a single level explosion is performed; |
| * if equals to EXPLOSION_MANUFACTURING, a downward visit is performed (explosion), including only the product that needs manufacturing; |
| * if equals to IMPLOSION an upward visit is done (implosion); |
| * |
| * @param delegator The delegator used. |
| * @throws GenericEntityException If a db problem occurs. |
| * |
| */ |
| public BOMTree(String productId, String bomTypeId, Date inDate, int type, Delegator delegator, LocalDispatcher dispatcher, GenericValue userLogin) throws GenericEntityException { |
| // If the parameters are not valid, return. |
| if (productId == null || bomTypeId == null || delegator == null || dispatcher == null) return; |
| // If the date is null, set it to today. |
| if (inDate == null) inDate = new Date(); |
| |
| this.delegator = delegator; |
| this.dispatcher = dispatcher; |
| |
| inputProduct = EntityQuery.use(delegator).from("Product").where("productId", productId).queryOne(); |
| |
| String productIdForRules = productId; |
| // The selected product features are loaded |
| List<GenericValue> productFeaturesAppl = EntityQuery.use(delegator).from("ProductFeatureAppl") |
| .where("productId", productId, |
| "productFeatureApplTypeId", "STANDARD_FEATURE") |
| .queryList(); |
| List<GenericValue> productFeatures = FastList.newInstance(); |
| GenericValue oneProductFeatureAppl = null; |
| for (int i = 0; i < productFeaturesAppl.size(); i++) { |
| oneProductFeatureAppl = productFeaturesAppl.get(i); |
| productFeatures.add(oneProductFeatureAppl.getRelatedOne("ProductFeature", false)); |
| } |
| // If the product is manufactured as a different product, |
| // load the new product |
| GenericValue manufacturedAsProduct = manufacturedAsProduct(productId, inDate); |
| // We load the information about the product that needs to be manufactured |
| // from Product entity |
| GenericValue product = EntityQuery.use(delegator).from("Product") |
| .where("productId", (manufacturedAsProduct != null? manufacturedAsProduct.getString("productIdTo"): productId)) |
| .queryOne(); |
| if (product == null) return; |
| BOMNode originalNode = new BOMNode(product, dispatcher, userLogin); |
| originalNode.setTree(this); |
| // If the product hasn't a bill of materials we try to retrieve |
| // the bill of materials of its virtual product (if the current |
| // product is variant). |
| if (!hasBom(product, inDate)) { |
| List<GenericValue> virtualProducts = product.getRelated("AssocProductAssoc", UtilMisc.toMap("productAssocTypeId", "PRODUCT_VARIANT"), null, false); |
| virtualProducts = EntityUtil.filterByDate(virtualProducts, inDate); |
| GenericValue virtualProduct = EntityUtil.getFirst(virtualProducts); |
| if (virtualProduct != null) { |
| // If the virtual product is manufactured as a different product, |
| // load the new product |
| productIdForRules = virtualProduct.getString("productId"); |
| manufacturedAsProduct = manufacturedAsProduct(virtualProduct.getString("productId"), inDate); |
| product = EntityQuery.use(delegator).from("Product") |
| .where("productId", (manufacturedAsProduct != null? manufacturedAsProduct.getString("productIdTo"): virtualProduct.get("productId"))) |
| .queryOne(); |
| } |
| } |
| if (product == null) return; |
| try { |
| root = new BOMNode(product, dispatcher, userLogin); |
| root.setTree(this); |
| root.setProductForRules(productIdForRules); |
| root.setSubstitutedNode(originalNode); |
| if (type == IMPLOSION) { |
| root.loadParents(bomTypeId, inDate, productFeatures); |
| } else { |
| root.loadChildren(bomTypeId, inDate, productFeatures, type); |
| } |
| } catch (GenericEntityException gee) { |
| root = null; |
| } |
| this.bomTypeId = bomTypeId; |
| this.inDate = inDate; |
| rootQuantity = BigDecimal.ONE; |
| rootAmount = BigDecimal.ZERO; |
| } |
| |
| public GenericValue getInputProduct() { |
| return inputProduct; |
| } |
| |
| private GenericValue manufacturedAsProduct(String productId, Date inDate) throws GenericEntityException { |
| return EntityQuery.use(delegator).from("ProductAssoc") |
| .where("productId", productId, |
| "productAssocTypeId", "PRODUCT_MANUFACTURED") |
| .filterByDate(inDate).queryFirst(); |
| } |
| |
| private boolean hasBom(GenericValue product, Date inDate) throws GenericEntityException { |
| List<GenericValue> children = product.getRelated("MainProductAssoc", UtilMisc.toMap("productAssocTypeId", bomTypeId), null, false); |
| children = EntityUtil.filterByDate(children, inDate); |
| return UtilValidate.isNotEmpty(children); |
| } |
| |
| /** It tells if the current (in-memory) tree representing |
| * a product's bill of materials is completely configured |
| * or not. |
| * @return true if no virtual nodes (products) are present in the tree. |
| * |
| */ |
| public boolean isConfigured() { |
| List<BOMNode> notConfiguredParts = FastList.newInstance(); |
| root.isConfigured(notConfiguredParts); |
| return (notConfiguredParts.size() == 0); |
| } |
| |
| /** Getter for property rootQuantity. |
| * @return Value of property rootQuantity. |
| * |
| */ |
| public BigDecimal getRootQuantity() { |
| return rootQuantity; |
| } |
| |
| /** Setter for property rootQuantity. |
| * @param rootQuantity New value of property rootQuantity. |
| * |
| */ |
| public void setRootQuantity(BigDecimal rootQuantity) { |
| this.rootQuantity = rootQuantity; |
| } |
| |
| /** Getter for property rootAmount. |
| * @return Value of property rootAmount. |
| * |
| */ |
| public BigDecimal getRootAmount() { |
| return rootAmount; |
| } |
| |
| /** Setter for property rootAmount. |
| * @param rootAmount New value of property rootAmount. |
| * |
| */ |
| public void setRootAmount(BigDecimal rootAmount) { |
| this.rootAmount = rootAmount; |
| } |
| |
| /** Getter for property root. |
| * @return Value of property root. |
| * |
| */ |
| public BOMNode getRoot() { |
| return root; |
| } |
| |
| /** Getter for property inDate. |
| * @return Value of property inDate. |
| * |
| */ |
| public Date getInDate() { |
| return inDate; |
| } |
| |
| /** Getter for property bomTypeId. |
| * @return Value of property bomTypeId. |
| * |
| */ |
| public String getBomTypeId() { |
| return bomTypeId; |
| } |
| |
| /** It visits the in-memory tree that represents a bill of materials |
| * and it collects info of its nodes in the StringBuffer. |
| * Method used for debug purposes. |
| * @param sb The StringBuffer used to collect tree info. |
| */ |
| public void print(StringBuffer sb) { |
| if (root != null) { |
| root.print(sb, getRootQuantity(), 0); |
| } |
| } |
| |
| /** It visits the in-memory tree that represents a bill of materials |
| * and it collects info of its nodes in the List. |
| * Method used for bom breakdown (explosion/implosion). |
| * @param arr The List used to collect tree info. |
| * @param initialDepth The depth of the root node. |
| */ |
| public void print(List<BOMNode> arr, int initialDepth) { |
| print(arr, initialDepth, true); |
| } |
| |
| public void print(List<BOMNode> arr, int initialDepth, boolean excludeWIPs) { |
| if (root != null) { |
| root.print(arr, getRootQuantity(), initialDepth, excludeWIPs); |
| } |
| } |
| |
| /** It visits the in-memory tree that represents a bill of materials |
| * and it collects info of its nodes in the List. |
| * Method used for bom breakdown (explosion/implosion). |
| * @param arr The List used to collect tree info. |
| */ |
| public void print(List<BOMNode> arr) { |
| print(arr, 0, false); |
| } |
| |
| public void print(List<BOMNode> arr, boolean excludeWIPs) { |
| print(arr, 0, excludeWIPs); |
| } |
| |
| /** It visits the in-memory tree that represents a bill of materials |
| * and it collects info of its nodes in the Map. |
| * Method used for bom summarized explosion. |
| * @param quantityPerNode The Map that will contain the summarized quantities per productId. |
| */ |
| public void sumQuantities(Map<String, BOMNode> quantityPerNode) { |
| if (root != null) { |
| root.sumQuantity(quantityPerNode); |
| } |
| } |
| |
| /** It visits the in-memory tree that represents a bill of materials |
| * and it collects all the productId it contains. |
| * @return List containing all the tree's productId. |
| */ |
| public List<String> getAllProductsId() { |
| List<BOMNode> nodeArr = FastList.newInstance(); |
| List<String> productsId = FastList.newInstance(); |
| print(nodeArr); |
| for (int i = 0; i < nodeArr.size(); i++) { |
| productsId.add((nodeArr.get(i)).getProduct().getString("productId")); |
| } |
| return productsId; |
| } |
| |
| /** It visits the in-memory tree that represents a bill of materials |
| * and it creates a manufacturing order for each of the nodes that needs |
| * to be manufactured. |
| * @param facilityId the facility id |
| * @param date the context date |
| * @param workEffortName the work effort name |
| * @param description the description |
| * @param routingId the routing id |
| * @param orderId the order id |
| * @param orderItemSeqId the order item id |
| * @param shipGroupSeqId the shipment group item id |
| * @param shipmentId the shipment id delegator used |
| * @param userLogin the GenericValue object of userLogin |
| * @return returns the work effort id |
| * @throws GenericEntityException If a db problem occurs. |
| */ |
| public String createManufacturingOrders(String facilityId, Date date, String workEffortName, String description, String routingId, String orderId, String orderItemSeqId, String shipGroupSeqId, String shipmentId, GenericValue userLogin) throws GenericEntityException { |
| String workEffortId = null; |
| if (root != null) { |
| if (UtilValidate.isEmpty(facilityId)) { |
| if (orderId != null) { |
| GenericValue order = EntityQuery.use(delegator).from("OrderHeader").where("orderId", orderId).queryOne(); |
| String productStoreId = order.getString("productStoreId"); |
| if (productStoreId != null) { |
| GenericValue productStore = ProductStoreWorker.getProductStore(productStoreId, delegator); |
| if (productStore != null) { |
| facilityId = productStore.getString("inventoryFacilityId"); |
| } |
| } |
| |
| } |
| if (facilityId == null && shipmentId != null) { |
| GenericValue shipment = EntityQuery.use(delegator).from("Shipment").where("shipmentId", shipmentId).queryOne(); |
| facilityId = shipment.getString("originFacilityId"); |
| } |
| } |
| Map<String, Object> tmpMap = root.createManufacturingOrder(facilityId, date, workEffortName, description, routingId, orderId, orderItemSeqId, shipGroupSeqId, shipmentId, true, true); |
| workEffortId = (String)tmpMap.get("productionRunId"); |
| } |
| return workEffortId; |
| } |
| |
| public void getProductsInPackages(List<BOMNode> arr) { |
| if (root != null) { |
| root.getProductsInPackages(arr, getRootQuantity(), 0, false); |
| } |
| } |
| |
| } |