/******************************************************************************* * 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.jobshopmgt; import java.math.BigDecimal; import java.sql.Timestamp; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.UtilMisc; 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.entity.util.EntityUtil; import org.apache.ofbiz.manufacturing.techdata.TechDataServices; import org.apache.ofbiz.service.GenericServiceException; import org.apache.ofbiz.service.LocalDispatcher; /** * ProductionRun Object used by the Jobshop management OFBiz components, * this object is used to find or updated an existing ProductionRun. * */ public class ProductionRun { public static final String module = ProductionRun.class.getName(); public static final String resource = "ManufacturingUiLabels"; protected GenericValue productionRun; // WorkEffort (PROD_ORDER_HEADER) protected GenericValue productionRunProduct; // WorkEffortGoodStandard (type: PRUN_PROD_DELIV) protected GenericValue productProduced; // Product (from WorkEffortGoodStandard of type: PRUN_PROD_DELIV) protected BigDecimal quantity; // the estimatedQuantity protected Timestamp estimatedStartDate; protected Timestamp estimatedCompletionDate; protected String productionRunName; protected String description; protected GenericValue currentStatus; protected List<GenericValue> productionRunComponents; protected List<GenericValue> productionRunRoutingTasks; protected LocalDispatcher dispatcher; /** * indicate if quantity or estimatedStartDate has been modified and * estimatedCompletionDate not yet recalculated with recalculateEstimatedCompletionDate() method. */ private boolean updateCompletionDate = false; /** * indicate if quantity has been modified, used for store() method to update appropriate entity. */ private boolean quantityIsUpdated = false; public ProductionRun(String productionRunId, Delegator delegator, LocalDispatcher dispatcher) { try { if (! UtilValidate.isEmpty(productionRunId)) { this.dispatcher = dispatcher; GenericValue workEffort = EntityQuery.use(delegator).from("WorkEffort").where("workEffortId", productionRunId).queryOne(); if (workEffort != null) { // If this is a task, get the parent production run if (workEffort.getString("workEffortTypeId") != null && "PROD_ORDER_TASK".equals(workEffort.getString("workEffortTypeId"))) { workEffort = EntityQuery.use(delegator).from("WorkEffort").where("workEffortId", workEffort.getString("workEffortParentId")).queryOne(); } } this.productionRun = workEffort; if (exist()) { this.estimatedStartDate = productionRun.getTimestamp("estimatedStartDate"); this.estimatedCompletionDate = productionRun.getTimestamp("estimatedCompletionDate"); this.productionRunName = productionRun.getString("workEffortName"); this.description = productionRun.getString("description"); } } } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } } /** * test if the productionRun exist. * @return true if it exist false otherwise. **/ public boolean exist() { return productionRun != null; } /** * get the ProductionRun GenericValue . * @return the ProductionRun GenericValue **/ public GenericValue getGenericValue() { return productionRun; } /** * Store the modified ProductionRun object in the database. * <ul> * <li>store the the productionRun header</li> * <li> the productProduced related data</li> * <li> the listRoutingTask related data</li> * <li> the productComponent list related data</li> * </ul> * @return true if success false otherwise **/ public boolean store() { if (exist()) { if (updateCompletionDate) { this.estimatedCompletionDate = recalculateEstimatedCompletionDate(); } productionRun.set("estimatedStartDate",this.estimatedStartDate); productionRun.set("estimatedCompletionDate",this.estimatedCompletionDate); productionRun.set("workEffortName",this.productionRunName); productionRun.set("description",this.description); try { if (quantityIsUpdated) { productionRun.set("quantityToProduce",(BigDecimal) this.quantity); productionRunProduct.set("estimatedQuantity",this.quantity.doubleValue()); productionRunProduct.store(); quantityIsUpdated = false; } productionRun.store(); if (productionRunRoutingTasks != null) { for (Iterator<GenericValue> iter = productionRunRoutingTasks.iterator(); iter.hasNext();) { GenericValue routingTask = iter.next(); routingTask.store(); } } if (productionRunComponents != null) { for (Iterator<GenericValue> iter = productionRunComponents.iterator(); iter.hasNext();) { GenericValue component = iter.next(); component.store(); } } } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); return false; } return true; } return false; } /** * get the Product GenericValue corresponding to the productProduced. * In the same time this method read the quantity property from SGBD * @return the productProduced related object **/ public GenericValue getProductProduced() { if (exist()) { if (productProduced == null) { try { List<GenericValue> productionRunProducts = productionRun.getRelated("WorkEffortGoodStandard", UtilMisc.toMap("workEffortGoodStdTypeId", "PRUN_PROD_DELIV"), null, false); this.productionRunProduct = EntityUtil.getFirst(productionRunProducts); quantity = productionRunProduct.getBigDecimal("estimatedQuantity"); productProduced = productionRunProduct.getRelatedOne("Product", true); } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } } return productProduced; } return null; } /** * get the quantity property. * @return the quantity property **/ public BigDecimal getQuantity() { if (exist()) { if (quantity == null) getProductProduced(); return quantity; } else return null; } /** * set the quantity property and recalculated the productComponent quantity. * @param newQuantity the new quantity to be set **/ public void setQuantity(BigDecimal newQuantity) { if (quantity == null) getProductProduced(); BigDecimal previousQuantity = quantity, componentQuantity; this.quantity = newQuantity; this.quantityIsUpdated = true; this.updateCompletionDate = true; if (productionRunComponents == null) getProductionRunComponents(); for (Iterator<GenericValue> iter = productionRunComponents.iterator(); iter.hasNext();) { GenericValue component = iter.next(); componentQuantity = component.getBigDecimal("estimatedQuantity"); component.set("estimatedQuantity", componentQuantity.divide(previousQuantity, 10, BigDecimal.ROUND_HALF_UP).multiply(newQuantity).doubleValue()); } } /** * get the estimatedStartDate property. * @return the estimatedStartDate property **/ public Timestamp getEstimatedStartDate() { return (exist()? this.estimatedStartDate: null); } /** * set the estimatedStartDate property. * @param estimatedStartDate set the estimatedStartDate property **/ public void setEstimatedStartDate(Timestamp estimatedStartDate) { this.estimatedStartDate = estimatedStartDate; this.updateCompletionDate = true; } /** * get the estimatedCompletionDate property. * @return the estimatedCompletionDate property **/ public Timestamp getEstimatedCompletionDate() { if (exist()) { if (updateCompletionDate) { this.estimatedCompletionDate = recalculateEstimatedCompletionDate(); } return this.estimatedCompletionDate; } else return null; } /** * set the estimatedCompletionDate property without any control or calculation. * usage productionRun.setEstimatedCompletionDate(productionRun.recalculateEstimatedCompletionDate(priority); * @param estimatedCompletionDate set the estimatedCompletionDate property **/ public void setEstimatedCompletionDate(Timestamp estimatedCompletionDate) { this.estimatedCompletionDate = estimatedCompletionDate; } /** * Recalculate the estimatedCompletionDate property. * Use the quantity and the estimatedStartDate properties as entries parameters. * Read the listRoutingTask and for each recalculated and update the estimatedStart and endDate in the object. * No store in the database is done. * @param priority give the routingTask start point to recalculated * @return the estimatedCompletionDate calculated **/ public Timestamp recalculateEstimatedCompletionDate(Long priority, Timestamp startDate) { if (exist()) { getProductionRunRoutingTasks(); if (quantity == null) getQuantity(); Timestamp endDate=null; for (Iterator<GenericValue> iter = productionRunRoutingTasks.iterator(); iter.hasNext();) { GenericValue routingTask = iter.next(); if (priority.compareTo(routingTask.getLong("priority")) <= 0) { // Calculate the estimatedCompletionDate long totalTime = ProductionRun.getEstimatedTaskTime(routingTask, quantity, dispatcher); endDate = TechDataServices.addForward(TechDataServices.getTechDataCalendar(routingTask),startDate, totalTime); // update the routingTask routingTask.set("estimatedStartDate",startDate); routingTask.set("estimatedCompletionDate",endDate); startDate = endDate; } } return endDate; } else { return null; } } /** * call recalculateEstimatedCompletionDate(0,estimatedStartDate), so recalculated for all the routing tasks. */ public Timestamp recalculateEstimatedCompletionDate() { this.updateCompletionDate = false; return recalculateEstimatedCompletionDate(Long.valueOf(0), estimatedStartDate); } /** * get the productionRunName property. * @return the productionRunName property **/ public String getProductionRunName() { if (exist()) return this.productionRunName; else return null; } public void setProductionRunName(String name) { this.productionRunName = name; } /** * get the description property. * @return the description property **/ public String getDescription() { if (exist()) return productionRun.getString("description"); else return null; } public void setDescription(String description) { this.description = description; } /** * get the GenericValue currentStatus. * @return the currentStatus related object **/ public GenericValue getCurrentStatus() { if (exist()) { if (currentStatus == null) { try { currentStatus = productionRun.getRelatedOne("CurrentStatusItem", true); } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } } return currentStatus; } return null; } /** * get the list of all the productionRunComponents as a list of GenericValue. * @return the productionRunComponents related object **/ public List<GenericValue> getProductionRunComponents() { if (exist()) { if (productionRunComponents == null) { if (productionRunRoutingTasks == null) this.getProductionRunRoutingTasks(); if (productionRunRoutingTasks != null) { try { productionRunComponents = new LinkedList<GenericValue>(); GenericValue routingTask; for (Iterator<GenericValue> iter = productionRunRoutingTasks.iterator(); iter.hasNext();) { routingTask = iter.next(); productionRunComponents.addAll(routingTask.getRelated("WorkEffortGoodStandard", UtilMisc.toMap("workEffortGoodStdTypeId", "PRUNT_PROD_NEEDED"),null, false)); } } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } } } return productionRunComponents; } return null; } /** * get the list of all the productionRunRoutingTasks as a list of GenericValue. * @return the productionRunRoutingTasks related object **/ public List<GenericValue> getProductionRunRoutingTasks() { if (exist()) { if (productionRunRoutingTasks == null) { try { productionRunRoutingTasks = productionRun.getRelated("ChildWorkEffort",UtilMisc.toMap("workEffortTypeId","PROD_ORDER_TASK"),UtilMisc.toList("priority"), false); } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } } return productionRunRoutingTasks; } return null; } /** * get the list of all the productionRunRoutingTasks as a list of GenericValue. * @return the productionRunRoutingTasks related object **/ public GenericValue getLastProductionRunRoutingTask() { if (exist()) { if (productionRunRoutingTasks == null) { try { productionRunRoutingTasks = productionRun.getRelated("ChildWorkEffort",UtilMisc.toMap("workEffortTypeId","PROD_ORDER_TASK"),UtilMisc.toList("priority"), false); } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } } return (UtilValidate.isNotEmpty(productionRunRoutingTasks) ? productionRunRoutingTasks.get(productionRunRoutingTasks.size() - 1): null); } return null; } /** * clear list of all the productionRunRoutingTasks to force re-reading at the next need. * This method is used when the routingTasks ordering is changed. **/ public void clearRoutingTasksList() { this.productionRunRoutingTasks = null; } /* * FIXME: the two getEstimatedTaskTime(...) methods will be removed and * implemented in the "getEstimatedTaskTime" service. */ public static long getEstimatedTaskTime(GenericValue task, BigDecimal quantity, LocalDispatcher dispatcher) { return getEstimatedTaskTime(task, quantity, null, null, dispatcher); } public static long getEstimatedTaskTime(GenericValue task, BigDecimal quantity, String productId, String routingId, LocalDispatcher dispatcher) { if (quantity == null) { quantity = BigDecimal.ONE; } if (task == null) return 0; double setupTime = 0; double taskTime = 1; double totalTaskTime = 0; if (task.get("estimatedSetupMillis") != null) { setupTime = task.getDouble("estimatedSetupMillis").doubleValue(); } if (task.get("estimatedMilliSeconds") != null) { taskTime = task.getDouble("estimatedMilliSeconds").doubleValue(); } totalTaskTime = (setupTime + taskTime * quantity.doubleValue()); if (task.get("estimateCalcMethod") != null) { String serviceName = null; try { GenericValue genericService = task.getRelatedOne("CustomMethod", false); if (genericService != null && genericService.getString("customMethodName") != null) { serviceName = genericService.getString("customMethodName"); // call the service // and put the value in totalTaskTime Map<String, Object> estimateCalcServiceMap = UtilMisc.<String, Object>toMap("workEffort", task, "quantity", quantity, "productId", productId, "routingId", routingId); Map<String, Object> serviceContext = UtilMisc.<String, Object>toMap("arguments", estimateCalcServiceMap); Map<String, Object> resultService = dispatcher.runSync(serviceName, serviceContext); totalTaskTime = ((BigDecimal)resultService.get("totalTime")).doubleValue(); } } catch (GenericServiceException exc) { Debug.logError(exc, "Problem calling the customMethod service " + serviceName); } catch (Exception exc) { Debug.logError(exc, "Problem calling the customMethod service " + serviceName); } } return (long) totalTaskTime; } public boolean isUpdateCompletionDate() { return updateCompletionDate; } }