/*********************************************************************** * This file is part of iDempiere ERP Open Source * * http://www.idempiere.org * * * * Copyright (C) Contributors * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * * Contributors: * * - hengsin * **********************************************************************/ package org.adempiere.base.event.delegate; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import org.adempiere.base.annotation.ModelEventTopic; import org.adempiere.base.annotation.EventTopicDelegate; import org.adempiere.base.event.annotations.ModelEventDelegate; import org.adempiere.base.event.annotations.doc.BeforeComplete; import org.compiere.model.MInOut; import org.compiere.model.MInOutLine; import org.compiere.model.MProduct; import org.compiere.model.MProduction; import org.compiere.model.MProductionLine; import org.compiere.model.MStorageOnHand; import org.compiere.process.ProcessInfo; import org.compiere.util.Env; import org.compiere.util.Msg; import org.compiere.util.Util; import org.compiere.wf.MWorkflow; import org.eevolution.model.MPPProductBOM; import org.eevolution.model.MPPProductBOMLine; import org.osgi.service.event.Event; /** * Event delegate to implement auto produce for shipment * @author hengsin */ @EventTopicDelegate @ModelEventTopic(modelClass = MInOut.class) public class AutoProduceEventDelegate extends ModelEventDelegate { /** * @param po * @param event */ public AutoProduceEventDelegate(MInOut po, Event event) { super(po, event); } @BeforeComplete public void onBeforeComplete() { MInOut mInOut = getModel(); boolean isGenerateProduction = (!MInOut.MOVEMENTTYPE_CustomerReturns.equals(mInOut.getMovementType()) && !MInOut.MOVEMENTTYPE_VendorReturns.equals(mInOut.getMovementType())); if (mInOut.isSOTrx() && isGenerateProduction) { String msg = processShipment(mInOut); if (msg != null) throw new RuntimeException (msg); } } /** * Process shipment and create production document for auto produce products (if insufficient on hand). * @param mInOut * @return error message or null */ private String processShipment(MInOut mInOut) { // Auto produce if on hand is not sufficient to fulfill order Map qtyUsedMap = new HashMap(); for (MInOutLine line : mInOut.getLines(false)) { MProduct product = new MProduct(mInOut.getCtx(),line.getM_Product_ID(), mInOut.get_TrxName()); if (!product.isBOM() || !product.isVerified() || !product.isAutoProduce() || !product.isStocked()) continue; // Find on Hand of product BigDecimal qtyOnHand = MStorageOnHand.getQtyOnHand(line.getM_Product_ID(),line.getM_Warehouse_ID(), line.getM_AttributeSetInstance_ID(), mInOut.get_TrxName()); String key = mInOut.getM_InOut_ID() + "_" + line.getM_Product_ID(); BigDecimal qty = qtyUsedMap.get(key); if (qty == null) { qty = line.getQtyEntered(); } else { qtyOnHand = qtyOnHand.subtract(qty); qty = qty.add(line.getQtyEntered()); } qtyUsedMap.put(key, qty); if (qtyOnHand.signum() < 0) qtyOnHand = BigDecimal.ZERO; if (qtyOnHand != null && qtyOnHand.compareTo(line.getQtyEntered()) >= 0) { continue; } else { String msg = createProduction(mInOut,line,line.getQtyEntered(),qtyOnHand,line.getM_Product_ID(),new int[]{0},qtyUsedMap); if( msg!=null ) return msg; else { line.setIsAutoProduce(true); line.saveEx(); } } } return null; } /** * Create and complete production document * @param mInOut * @param mInOutLine * @param qtyEntered * @param qtyOnHand * @param endProductID * @param productionCount * @param qtyUsedMap * @return error message or null */ private String createProduction(MInOut mInOut, MInOutLine mInOutLine, BigDecimal qtyEntered, BigDecimal qtyOnHand, int endProductID, int[] productionCount, Map qtyUsedMap) { String description=Msg.getElement(Env.getCtx(), "M_InOut_ID", true) + " " + mInOut.getDocumentNo(); int M_Locator_ID=0; if (endProductID == mInOutLine.getM_Product_ID()) { M_Locator_ID = mInOutLine.getM_Locator_ID(); } else { MProduct endProduct = MProduct.get(Env.getCtx(), endProductID); M_Locator_ID = endProduct.getM_Locator_ID() > 0 ? endProduct.getM_Locator_ID() : mInOutLine.getM_Locator_ID(); } BigDecimal productionQty = qtyOnHand.signum() > 0 ? qtyEntered.subtract(qtyOnHand) : qtyEntered; // Create production MProduction production = new MProduction(mInOut.getCtx(),0, mInOut.get_TrxName()); production.setDescription(description); production.setAD_Org_ID(mInOut.getAD_Org_ID()); String productionName = Msg.getElement(Env.getCtx(), "M_InOut_ID", true) + " " + mInOut.getDocumentNo(); if (productionCount[0] > 0) { productionName = productionName + " #"+(productionCount[0]+1); } else { productionName = productionName + " #1"; } production.setName(productionName); production.setMovementDate(mInOut.getMovementDate()); production.setPosted(false); production.setProcessed(false); production.setM_Product_ID(endProductID); production.setM_Locator_ID(M_Locator_ID); production.setProductionQty(productionQty); production.setIsCreated(MProduction.ISCREATED_No); production.setIsUseProductionPlan(false); if (endProductID == mInOutLine.getM_Product_ID()) { production.setM_InOutLine_ID(mInOutLine.getM_InOutLine_ID()); } production.saveEx(); // Create line for end product MProductionLine productionline = new MProductionLine(mInOut.getCtx(),0, mInOut.get_TrxName()); productionline.setM_Production_ID(production.getM_Production_ID()); productionline.setLine(10); productionline.setAD_Org_ID(mInOut.getAD_Org_ID()); productionline.setM_Product_ID(endProductID); productionline.setMovementQty(productionQty); productionline.setQtyUsed(BigDecimal.ZERO); productionline.setM_Locator_ID(M_Locator_ID); productionline.setDescription(Msg.getElement(Env.getCtx(), "M_InOutLine_ID", true) + " #" + mInOutLine.getLine()); productionline.setIsEndProduct(true); productionline.saveEx(); MPPProductBOM bom = MPPProductBOM.getDefault(MProduct.get(Env.getCtx(), endProductID), mInOut.get_TrxName()); if (bom == null) return "Shipment: "+ mInOut.getDocumentNo() + " Line: " + mInOutLine.getLine() +" doesn't contain a valid BOM"; MPPProductBOMLine[] bomLines = bom.getLines(); int lineNo = 10; for (MPPProductBOMLine bomLine : bomLines) { BigDecimal bomQty = bomLine.getQtyBOM(); MProduct component = new MProduct(mInOut.getCtx(), bomLine.getM_Product_ID(), mInOut.get_TrxName()); lineNo = lineNo + 10; // Create Line productionline = new MProductionLine(mInOut.getCtx(), 0, mInOut.get_TrxName()); productionline.setM_Production_ID(production.getM_Production_ID()); productionline.setLine(lineNo); productionline.setM_Product_ID(bomLine.getM_Product_ID()); productionline.setAD_Org_ID(mInOut.getAD_Org_ID()); productionline.setQtyUsed(productionQty.multiply(bomQty)); M_Locator_ID = MStorageOnHand.getM_Locator_ID(mInOut.getM_Warehouse_ID(), bomLine.getM_Product_ID(), -1, productionQty.multiply(bomQty), mInOut.get_TrxName()); M_Locator_ID = M_Locator_ID > 0 ? M_Locator_ID : (component.getM_Locator_ID() > 0 ? component.getM_Locator_ID() : production.getM_Locator_ID()); productionline.setM_Locator_ID(M_Locator_ID); productionline.setIsEndProduct(false); productionline.saveEx(); if (component.isBOM() && component.isVerified()) { BigDecimal onHandComponent = MStorageOnHand.getQtyOnHand(bomLine.getM_Product_ID(),mInOutLine.getM_Warehouse_ID(),0, mInOut.get_TrxName()); String key = mInOut.getM_InOut_ID() + "_" + bomLine.getM_Product_ID(); BigDecimal qty = qtyUsedMap.get(key); if (qty != null) onHandComponent = onHandComponent.subtract(qty); // insufficient on hand for component if (onHandComponent.compareTo(productionQty.multiply(bomQty)) < 0) { boolean produceNestedBOM = component.isBOM() && component.isVerified() && component.isAutoProduce(); if (produceNestedBOM) { productionCount[0] = productionCount[0]+1; String error = createProduction(mInOut,mInOutLine,productionQty.multiply(bomQty),onHandComponent,bomLine.getM_Product_ID(),productionCount,qtyUsedMap); if (!Util.isEmpty(error)) { return error; } } } } } // Indicate that lines have been created production.setIsCreated(MProduction.ISCREATED_Yes); production.saveEx(); //complete the production ProcessInfo pi = MWorkflow.runDocumentActionWorkflow(production, "CO"); if (pi.isError()) { StringBuilder msgError = new StringBuilder(); if (!Util.isEmpty(pi.getSummary())) msgError.append(pi.getSummary()); if (!Util.isEmpty(production.getProcessMsg())) msgError.append(" - " ).append(production.getProcessMsg()); if (msgError.length() == 0) // unlikely to arrive here, but we need to ensure that issues is raised msgError.append("Error completing auto-produce production"); return msgError.toString(); } return null; } }