| /******************************************************************************* |
| * 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.mrp; |
| |
| import java.math.BigDecimal; |
| import java.sql.Timestamp; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| 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.entity.Delegator; |
| import org.apache.ofbiz.entity.GenericEntityException; |
| import org.apache.ofbiz.entity.GenericValue; |
| import org.apache.ofbiz.entity.util.EntityUtil; |
| import org.apache.ofbiz.manufacturing.bom.BOMNode; |
| import org.apache.ofbiz.manufacturing.bom.BOMTree; |
| import org.apache.ofbiz.manufacturing.jobshopmgt.ProductionRun; |
| import org.apache.ofbiz.manufacturing.techdata.TechDataServices; |
| import org.apache.ofbiz.service.DispatchContext; |
| import org.apache.ofbiz.service.GenericServiceException; |
| import org.apache.ofbiz.service.LocalDispatcher; |
| |
| /** |
| * Proposed Order Object generated by the MRP process or other re-Order process |
| * |
| */ |
| public class ProposedOrder { |
| |
| public static final String module = ProposedOrder.class.getName(); |
| public static final String resource = "ManufacturingUiLabels"; |
| |
| protected GenericValue product; |
| protected boolean isBuilt; |
| protected String productId; |
| protected String facilityId; |
| protected String manufacturingFacilityId; |
| protected String mrpName; |
| protected Timestamp requiredByDate; |
| protected Timestamp requirementStartDate; |
| protected BigDecimal quantity; |
| |
| |
| public ProposedOrder(GenericValue product, String facilityId, String manufacturingFacilityId, boolean isBuilt, Timestamp requiredByDate, BigDecimal quantity) { |
| this.product = product; |
| this.productId = product.getString("productId"); |
| this.facilityId = facilityId; |
| this.manufacturingFacilityId = manufacturingFacilityId; |
| this.isBuilt = isBuilt; |
| this.requiredByDate = requiredByDate; |
| this.quantity = quantity; |
| this.requirementStartDate = null; |
| } |
| /** |
| * get the quantity property. |
| * @return the quantity property |
| **/ |
| public BigDecimal getQuantity() { |
| return quantity; |
| } |
| /** |
| * get the requirementStartDate property. |
| * @return the quantity property |
| **/ |
| public Timestamp getRequirementStartDate() { |
| return requirementStartDate; |
| } |
| /** |
| * calculate the ProposedOrder requirementStartDate and update the requirementStartDate property. |
| * <ul> |
| * <li>For the build product, |
| * <ul> |
| * <li>read the routing associated to the product,</li> |
| * <li>read the routingTask associated to the routing</li> |
| * <li> step by step calculate from the endDate the startDate</li> |
| * </ul> |
| * </li> |
| * <li>For the bought product, the first ProductFacility.daysToShip is used to calculated the startDate</li> |
| * </ul> |
| * @return |
| * <ul> |
| * <li>if ProposedOrder.isBuild a Map with all the routingTaskId as keys and estimatedStartDate as value.</li> |
| * <li>else null.</li> |
| * </ul> |
| **/ |
| public Map<String, Object> calculateStartDate(int daysToShip, GenericValue routing, Delegator delegator, LocalDispatcher dispatcher, GenericValue userLogin) { |
| Map<String, Object> result = null; |
| Timestamp endDate = (Timestamp)requiredByDate.clone(); |
| Timestamp startDate = endDate; |
| long timeToShip = daysToShip * 8 * 60 * 60 * 1000; |
| if (isBuilt) { |
| List<GenericValue> listRoutingTaskAssoc = null; |
| if (routing == null) { |
| try { |
| Map<String, Object> routingInMap = UtilMisc.<String, Object>toMap("productId", product.getString("productId"), |
| "ignoreDefaultRouting", "Y", "userLogin", userLogin); |
| Map<String, Object> routingOutMap = dispatcher.runSync("getProductRouting", routingInMap); |
| routing = (GenericValue)routingOutMap.get("routing"); |
| listRoutingTaskAssoc = UtilGenerics.checkList(routingOutMap.get("tasks")); |
| if (routing == null) { |
| // try to find a routing linked to the virtual product |
| BOMTree tree = null; |
| List<BOMNode> components = new LinkedList<BOMNode>(); |
| try { |
| tree = new BOMTree(product.getString("productId"), "MANUF_COMPONENT", requiredByDate, BOMTree.EXPLOSION_SINGLE_LEVEL, delegator, dispatcher, userLogin); |
| tree.setRootQuantity(quantity); |
| tree.print(components, true); |
| if (components.size() > 0) components.remove(0); |
| } catch (Exception exc) { |
| Debug.logWarning(exc.getMessage(), module); |
| tree = null; |
| } |
| if (tree != null && tree.getRoot() != null && tree.getRoot().getProduct() != null) { |
| routingInMap = UtilMisc.toMap("productId", tree.getRoot().getProduct().getString("productId"), "userLogin", userLogin); |
| routingOutMap = dispatcher.runSync("getProductRouting", routingInMap); |
| routing = (GenericValue)routingOutMap.get("routing"); |
| } |
| } |
| } catch (GenericServiceException gse) { |
| Debug.logWarning(gse.getMessage(), module); |
| } |
| } |
| if (routing != null) { |
| result = new HashMap<String, Object>(); |
| //Looks for all the routingTask (ordered by inversed (begin from the end) sequence number) |
| if (listRoutingTaskAssoc == null) { |
| try { |
| Map<String, Object> routingTasksInMap = UtilMisc.<String, Object>toMap("workEffortId", routing.getString("workEffortId"), |
| "userLogin", userLogin); |
| Map<String, Object> routingTasksOutMap = dispatcher.runSync("getRoutingTaskAssocs", routingTasksInMap); |
| listRoutingTaskAssoc = UtilGenerics.checkList(routingTasksOutMap.get("routingTaskAssocs")); |
| } catch (GenericServiceException gse) { |
| Debug.logWarning(gse.getMessage(), module); |
| } |
| } |
| } |
| if (listRoutingTaskAssoc != null) { |
| for (int i = 1; i <= listRoutingTaskAssoc.size(); i++) { |
| GenericValue routingTaskAssoc = listRoutingTaskAssoc.get(listRoutingTaskAssoc.size() - i); |
| if (EntityUtil.isValueActive(routingTaskAssoc, endDate)) { |
| GenericValue routingTask = null; |
| try { |
| routingTask = routingTaskAssoc.getRelatedOne("ToWorkEffort", true); |
| } catch (GenericEntityException e) { |
| Debug.logError(e.getMessage(), module); |
| } |
| // Calculate the estimatedStartDate |
| long totalTime = ProductionRun.getEstimatedTaskTime(routingTask, quantity, dispatcher); |
| if (i == listRoutingTaskAssoc.size()) { |
| // add the daysToShip at the end of the routing |
| totalTime += timeToShip; |
| } |
| startDate = TechDataServices.addBackward(TechDataServices.getTechDataCalendar(routingTask),endDate, totalTime); |
| // record the routingTask with the startDate associated |
| result.put(routingTask.getString("workEffortId"),startDate); |
| endDate = startDate; |
| } |
| } |
| } else { |
| // routing is null |
| Debug.logError("No routing found for product = "+ product.getString("productId"), module); |
| } |
| } else { |
| // the product is purchased |
| // TODO: REVIEW this code |
| try { |
| GenericValue techDataCalendar = product.getDelegator().findOne("TechDataCalendar", UtilMisc.toMap("calendarId", "SUPPLIER"), true); |
| startDate = TechDataServices.addBackward(techDataCalendar, endDate, timeToShip); |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "Error : reading SUPPLIER TechDataCalendar: " + e.getMessage(), module); |
| } |
| } |
| requirementStartDate = startDate; |
| return result; |
| } |
| |
| |
| /** |
| * calculate the ProposedOrder quantity and update the quantity property. |
| * Read the first ProductFacility.reorderQuantity and calculate the quantity : if (quantity < reorderQuantity) quantity = reorderQuantity; |
| **/ |
| // FIXME: facilityId |
| public void calculateQuantityToSupply(BigDecimal reorderQuantity, BigDecimal minimumStock, ListIterator<GenericValue> listIterIEP) { |
| // TODO : use a better algorithm using Order management cost et Product Stock cost to calculate the re-order quantity |
| // the variable listIterIEP will be used for that |
| if (quantity.compareTo(reorderQuantity) < 0) { |
| quantity = reorderQuantity; |
| } |
| } |
| |
| /** |
| * create a ProposedOrder in the Requirement Entity calling the createRequirement service. |
| * @param ctx The DispatchContext used to call service to create the Requirement Entity record. |
| * @return String the requirementId |
| **/ |
| public String create(DispatchContext ctx, GenericValue userLogin) { |
| if ("WIP".equals(product.getString("productTypeId"))) { |
| // No requirements for Work In Process products |
| return null; |
| } |
| LocalDispatcher dispatcher = ctx.getDispatcher(); |
| Delegator delegator = ctx.getDelegator(); |
| Map<String, Object> parameters = UtilMisc.<String, Object>toMap("userLogin", userLogin); |
| if (isBuilt) { |
| try { |
| List<BOMNode> bom = new LinkedList<BOMNode>(); |
| BOMTree tree = new BOMTree(productId, "MANUF_COMPONENT", null, BOMTree.EXPLOSION_MANUFACTURING, delegator, dispatcher, userLogin); |
| tree.setRootQuantity(quantity); |
| tree.print(bom); |
| requirementStartDate = tree.getRoot().getStartDate(manufacturingFacilityId, requiredByDate, true); |
| } catch (Exception e) { |
| Debug.logError(e,"Error : computing the requirement start date. " + e.getMessage(), module); |
| } |
| } |
| parameters.put("productId", productId); |
| parameters.put("statusId", "REQ_PROPOSED"); |
| parameters.put("facilityId", (isBuilt? manufacturingFacilityId: facilityId)); |
| parameters.put("requiredByDate", requiredByDate); |
| parameters.put("requirementStartDate", requirementStartDate); |
| parameters.put("quantity", quantity); |
| parameters.put("requirementTypeId", (isBuilt? "INTERNAL_REQUIREMENT" : "PRODUCT_REQUIREMENT")); |
| if (mrpName != null) { |
| parameters.put("description", "MRP_" + mrpName); |
| } else { |
| parameters.put("description", "Automatically generated by MRP"); |
| } |
| try { |
| Map<String, Object> result = dispatcher.runSync("createRequirement", parameters); |
| return (String) result.get("requirementId"); |
| } catch (GenericServiceException e) { |
| Debug.logError(e,"Error : createRequirement with parameters = "+parameters+"--"+e.getMessage(), module); |
| return null; |
| } |
| } |
| |
| public void setMrpName(String mrpName) { |
| this.mrpName = mrpName; |
| } |
| } |