/*********************************************************************** * 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. * **********************************************************************/ package org.compiere.model; import java.io.File; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.logging.Level; import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.BackDateTrxNotAllowedException; import org.adempiere.exceptions.PeriodClosedException; import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; import org.compiere.util.AdempiereUserError; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.TimeUtil; import org.compiere.util.Util; public class MProduction extends X_M_Production implements DocAction { /** * generated serial id */ private static final long serialVersionUID = -1185737281702437745L; /** Log */ protected static CLogger m_log = CLogger.getCLogger (MProduction.class); protected int lineno; protected int count; /** * UUID based Constructor * @param ctx Context * @param M_Production_UU UUID key * @param trxName Transaction */ public MProduction(Properties ctx, String M_Production_UU, String trxName) { super(ctx, M_Production_UU, trxName); if (Util.isEmpty(M_Production_UU)) setInitialDefaults(); } /** * @param ctx * @param M_Production_ID * @param trxName */ public MProduction(Properties ctx, int M_Production_ID, String trxName) { this (ctx, M_Production_ID, trxName, (String[]) null); } /** * @param ctx * @param M_Production_ID * @param trxName * @param virtualColumns */ public MProduction(Properties ctx, int M_Production_ID, String trxName, String... virtualColumns) { super(ctx, M_Production_ID, trxName, virtualColumns); if (M_Production_ID == 0) setInitialDefaults(); } /** * Set the initial defaults for a new record */ private void setInitialDefaults() { setDocStatus(DOCSTATUS_Drafted); setDocAction (DOCACTION_Prepare); } /** * @param ctx * @param rs * @param trxName */ public MProduction(Properties ctx, ResultSet rs, String trxName) { super(ctx, rs, trxName); } /** * Create new production with client, organization and date promised (as movement date) from order line. * @param line order line */ public MProduction( MOrderLine line ) { super( line.getCtx(), 0, line.get_TrxName()); setAD_Client_ID(line.getAD_Client_ID()); setAD_Org_ID(line.getAD_Org_ID()); setMovementDate( line.getDatePromised() ); } /** * Create new production for project line * @param line project line */ public MProduction( MProjectLine line ) { super( line.getCtx(), 0, line.get_TrxName()); MProject project = new MProject(line.getCtx(), line.getC_Project_ID(), line.get_TrxName()); MWarehouse wh = new MWarehouse(line.getCtx(), project.getM_Warehouse_ID(), line.get_TrxName()); MLocator M_Locator = null; int M_Locator_ID = 0; if (wh != null) { M_Locator = wh.getDefaultLocator(); M_Locator_ID = M_Locator.getM_Locator_ID(); } setAD_Client_ID(line.getAD_Client_ID()); setAD_Org_ID(line.getAD_Org_ID()); setM_Product_ID(line.getM_Product_ID()); setProductionQty(line.getPlannedQty()); setM_Locator_ID(M_Locator_ID); setDescription(project.getValue()+"_"+project.getName()+" Line: "+line.getLine()+" (project)"); setC_Project_ID(line.getC_Project_ID()); setC_BPartner_ID(project.getC_BPartner_ID()); setC_Campaign_ID(project.getC_Campaign_ID()); setAD_OrgTrx_ID(project.getAD_OrgTrx_ID()); setC_Activity_ID(project.getC_Activity_ID()); setC_ProjectPhase_ID(line.getC_ProjectPhase_ID()); setC_ProjectTask_ID(line.getC_ProjectTask_ID()); setMovementDate( Env.getContextAsDate(p_ctx, Env.DATE)); } @Override public String completeIt() { // Re-Check if (!m_justPrepared) { String status = prepareIt(); m_justPrepared = false; if (!DocAction.STATUS_InProgress.equals(status)) return status; } // Set the definite document number after completed (if needed) setDefiniteDocumentNo(); m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_BEFORE_COMPLETE); if (m_processMsg != null) return DocAction.STATUS_Invalid; StringBuilder errors = new StringBuilder(); int processed = 0; if (!isUseProductionPlan()) { MProductionLine[] lines = getLines(); //IDEMPIERE-3107 Check if End Product in Production Lines exist if(!isHaveEndProduct(lines)) { m_processMsg = "Production does not contain End Product"; return DocAction.STATUS_Invalid; } errors.append(processLines(lines)); if (errors.length() > 0) { m_processMsg = errors.toString(); return DocAction.STATUS_Invalid; } processed = processed + lines.length; } else { Query planQuery = new Query(Env.getCtx(), I_M_ProductionPlan.Table_Name, "M_ProductionPlan.M_Production_ID=?", get_TrxName()); List plans = planQuery.setParameters(getM_Production_ID()).list(); for(MProductionPlan plan : plans) { MProductionLine[] lines = plan.getLines(); //IDEMPIERE-3107 Check if End Product in Production Lines exist if(!isHaveEndProduct(lines)) { m_processMsg = String.format("Production plan (line %1$d id %2$d) does not contain End Product", plan.getLine(), plan.get_ID()); return DocAction.STATUS_Invalid; } if (lines.length > 0) { errors.append(processLines(lines)); if (errors.length() > 0) { m_processMsg = errors.toString(); return DocAction.STATUS_Invalid; } processed = processed + lines.length; } plan.setProcessed(true); plan.saveEx(); } } // User Validation String valid = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_AFTER_COMPLETE); if (valid != null) { m_processMsg = valid; return DocAction.STATUS_Invalid; } setProcessed(true); setDocAction(DOCACTION_Close); return DocAction.STATUS_Completed; } /** * Set the definite document number after completed */ protected void setDefiniteDocumentNo() { MDocType dt = MDocType.get(getCtx(), getC_DocType_ID()); if (dt.isOverwriteDateOnComplete()) { if (this.getProcessedOn().signum() == 0) { setMovementDate(TimeUtil.getDay(0)); MPeriod.testPeriodOpen(getCtx(), getMovementDate(), getC_DocType_ID(), getAD_Org_ID()); MAcctSchema.testBackDateTrxAllowed(getCtx(), getMovementDate(), get_TrxName()); } } if (dt.isOverwriteSeqOnComplete()) { if (this.getProcessedOn().signum() == 0) { String value = DB.getDocumentNo(getC_DocType_ID(), get_TrxName(), true, this); if (value != null) setDocumentNo(value); } } } /** * @param lines * @return true if one of the production is an end product line (IsEndProduct=Y). */ private boolean isHaveEndProduct(MProductionLine[] lines) { for(MProductionLine line : lines) { if(line.isEndProduct()) return true; } return false; } /** * Process production lines - create material transaction * @param lines * @return error message or empty string */ protected Object processLines(MProductionLine[] lines) { StringBuilder errors = new StringBuilder(); for ( int i = 0; i list = new ArrayList(); String sql = "SELECT pl.M_ProductionLine_ID " + "FROM M_ProductionLine pl " + "WHERE pl.M_Production_ID = ? " + "ORDER BY pl.Line, pl.M_ProductionLine_ID "; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement(sql, get_TrxName()); pstmt.setInt(1, get_ID()); rs = pstmt.executeQuery(); while (rs.next()) list.add( new MProductionLine( getCtx(), rs.getInt(1), get_TrxName() ) ); } catch (SQLException ex) { throw new AdempiereException("Unable to load production lines", ex); } finally { DB.close(rs, pstmt); rs = null; pstmt = null; } MProductionLine[] retValue = new MProductionLine[list.size()]; list.toArray(retValue); return retValue; } /** * Delete all production lines * @param trxName */ public void deleteLines(String trxName) { for (MProductionLine line : getLines()) { line.deleteEx(true); } }// deleteLines /** * Create production lines * @param mustBeStocked true to verify BOM component has sufficient on hand * @return number of line created */ public int createLines(boolean mustBeStocked) { return createLines(mustBeStocked, 0); } /** * Create production line * @param mustBeStocked true to verify BOM component has sufficient on hand * @param PP_Product_BOM_ID optional BOM ID. 0 to use default product BOM * @return number of line created */ public int createLines(boolean mustBeStocked, int PP_Product_BOM_ID) { lineno = 100; count = 0; // product to be produced MProduct finishedProduct = new MProduct(getCtx(), getM_Product_ID(), get_TrxName()); MProductionLine line = new MProductionLine( this ); line.setLine( lineno ); line.setM_Product_ID( finishedProduct.get_ID() ); line.setM_Locator_ID( getM_Locator_ID() ); line.setMovementQty( getProductionQty()); line.setPlannedQty(getProductionQty()); line.saveEx(); count++; if (PP_Product_BOM_ID > 0) { setPP_Product_BOM_ID(PP_Product_BOM_ID); saveEx(); } createLines(mustBeStocked, finishedProduct, getProductionQty(), PP_Product_BOM_ID); return count; } /** * Create production line * @param mustBeStocked true to verify BOM component has sufficient on hand * @param finishedProduct end product * @param requiredQty * @param PP_Product_BOM_ID optional BOM ID. 0 to use default product BOM * @return number of line created */ protected int createLines(boolean mustBeStocked, MProduct finishedProduct, BigDecimal requiredQty, int PP_Product_BOM_ID) { int defaultLocator = 0; MLocator finishedLocator = MLocator.get(getCtx(), getM_Locator_ID()); int M_Warehouse_ID = finishedLocator.getM_Warehouse_ID(); // products used in production String sql = " SELECT bl.M_Product_ID, bl.QtyBOM" + " FROM PP_Product_BOMLine bl" + " JOIN PP_Product_BOM b ON b.PP_Product_BOM_ID = bl.PP_Product_BOM_ID " + " WHERE b.M_Product_ID=" + finishedProduct.getM_Product_ID() + " AND b.IsActive='Y' AND bl.IsActive='Y' "; if (PP_Product_BOM_ID > 0) { sql += " AND b.PP_Product_BOM_ID=" + PP_Product_BOM_ID; } else { sql += " AND b.BOMType='A' AND b.BOMUse='A' "; } sql += " ORDER BY bl.Line"; try (PreparedStatement pstmt = DB.prepareStatement(sql, get_TrxName());) { ResultSet rs = pstmt.executeQuery(); while (rs.next()) { lineno = lineno + 10; int BOMProduct_ID = rs.getInt(1); BigDecimal BOMQty = rs.getBigDecimal(2); BigDecimal BOMMovementQty = BOMQty.multiply(requiredQty); MProduct bomproduct = new MProduct(Env.getCtx(), BOMProduct_ID, get_TrxName()); if ( bomproduct.isBOM() && bomproduct.isPhantom() ) { createLines(mustBeStocked, bomproduct, BOMMovementQty, 0); } else { defaultLocator = bomproduct.getM_Locator_ID(); if ( defaultLocator == 0 ) defaultLocator = getM_Locator_ID(); if (!bomproduct.isStocked()) { MProductionLine BOMLine = null; BOMLine = new MProductionLine( this ); BOMLine.setLine( lineno ); BOMLine.setM_Product_ID( BOMProduct_ID ); BOMLine.setM_Locator_ID( defaultLocator ); BOMLine.setQtyUsed(BOMMovementQty ); BOMLine.setPlannedQty( BOMMovementQty ); BOMLine.saveEx(get_TrxName()); lineno = lineno + 10; count++; } else if (BOMMovementQty.signum() == 0) { MProductionLine BOMLine = null; BOMLine = new MProductionLine( this ); BOMLine.setLine( lineno ); BOMLine.setM_Product_ID( BOMProduct_ID ); BOMLine.setM_Locator_ID( defaultLocator ); BOMLine.setQtyUsed( BOMMovementQty ); BOMLine.setPlannedQty( BOMMovementQty ); BOMLine.saveEx(get_TrxName()); lineno = lineno + 10; count++; } else { // BOM stock info MStorageOnHand[] storages = null; MProduct usedProduct = MProduct.get(getCtx(), BOMProduct_ID); defaultLocator = usedProduct.getM_Locator_ID(); if ( defaultLocator == 0 ) defaultLocator = getM_Locator_ID(); if (usedProduct == null || usedProduct.get_ID() == 0) return 0; MClient client = MClient.get(getCtx()); MProductCategory pc = MProductCategory.get(getCtx(), usedProduct.getM_Product_Category_ID()); String MMPolicy = pc.getMMPolicy(); if (MMPolicy == null || MMPolicy.length() == 0) { MMPolicy = client.getMMPolicy(); } storages = MStorageOnHand.getWarehouse(getCtx(), M_Warehouse_ID, BOMProduct_ID, 0, null, MProductCategory.MMPOLICY_FiFo.equals(MMPolicy), true, 0, get_TrxName()); MProductionLine BOMLine = null; int prevLoc = -1; // Create lines from storage until qty is reached for (int sl = 0; sl < storages.length; sl++) { BigDecimal lineQty = storages[sl].getQtyOnHand(); if (lineQty.signum() != 0) { if (lineQty.compareTo(BOMMovementQty) > 0) lineQty = BOMMovementQty; int loc = storages[sl].getM_Locator_ID(); // same locator if (prevLoc == loc) { BOMLine.setQtyUsed(BOMLine.getQtyUsed() .add(lineQty)); BOMLine.setPlannedQty(BOMLine.getQtyUsed()); BOMLine.saveEx(get_TrxName()); } // otherwise create new line else { BOMLine = new MProductionLine( this ); BOMLine.setLine( lineno ); BOMLine.setM_Product_ID( BOMProduct_ID ); BOMLine.setM_Locator_ID( loc ); BOMLine.setQtyUsed( lineQty); BOMLine.setPlannedQty( lineQty); BOMLine.saveEx(get_TrxName()); lineno = lineno + 10; count++; } prevLoc = loc; // enough ? BOMMovementQty = BOMMovementQty.subtract(lineQty); if (BOMMovementQty.signum() == 0) break; } } // for available storages // fallback if (BOMMovementQty.signum() != 0 ) { if (!mustBeStocked) { // same locator if (prevLoc == defaultLocator) { BOMLine.setQtyUsed(BOMLine.getQtyUsed() .add(BOMMovementQty)); BOMLine.setPlannedQty(BOMLine.getQtyUsed()); BOMLine.saveEx(get_TrxName()); } // otherwise create new line else { BOMLine = new MProductionLine( this ); BOMLine.setLine( lineno ); BOMLine.setM_Product_ID( BOMProduct_ID ); BOMLine.setM_Locator_ID( defaultLocator ); BOMLine.setQtyUsed( BOMMovementQty); BOMLine.setPlannedQty( BOMMovementQty); BOMLine.saveEx(get_TrxName()); lineno = lineno + 10; count++; } } else { throw new AdempiereUserError("Not enough stock of " + BOMProduct_ID); } } } } } // for all bom products } catch (Exception e) { throw new AdempiereException("Failed to create production lines", e); } return count; } @Override protected boolean beforeDelete() { deleteLines(get_TrxName()); return true; } @Override public boolean processIt(String processAction) { m_processMsg = null; DocumentEngine engine = new DocumentEngine (this, getDocStatus()); return engine.processIt (processAction, getDocAction()); } /** Process Message */ protected String m_processMsg = null; /** Just Prepared Flag */ protected boolean m_justPrepared = false; @Override public boolean unlockIt() { if (log.isLoggable(Level.INFO)) log.info("unlockIt - " + toString()); setProcessing(false); return true; } @Override public boolean invalidateIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); setDocAction(DOCACTION_Prepare); return true; } @Override public String prepareIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_BEFORE_PREPARE); if (m_processMsg != null) return DocAction.STATUS_Invalid; // Std Period open? MPeriod.testPeriodOpen(getCtx(), getMovementDate(), getC_DocType_ID(), getAD_Org_ID()); MAcctSchema.testBackDateTrxAllowed(getCtx(), getMovementDate(), get_TrxName()); if ( getIsCreated().equals("N") ) { m_processMsg = "Not created"; return DocAction.STATUS_Invalid; } if (!isUseProductionPlan()) { m_processMsg = validateEndProduct(getM_Product_ID()); if (!Util.isEmpty(m_processMsg)) { return DocAction.STATUS_Invalid; } } else { Query planQuery = new Query(getCtx(), I_M_ProductionPlan.Table_Name, "M_ProductionPlan.M_Production_ID=?", get_TrxName()); List plans = planQuery.setParameters(getM_Production_ID()).list(); for(MProductionPlan plan : plans) { m_processMsg = validateEndProduct(plan.getM_Product_ID()); if (!Util.isEmpty(m_processMsg)) { return DocAction.STATUS_Invalid; } } } m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_AFTER_PREPARE); if (m_processMsg != null) return DocAction.STATUS_Invalid; m_justPrepared = true; if (!DOCACTION_Complete.equals(getDocAction())) setDocAction(DOCACTION_Complete); return DocAction.STATUS_InProgress; } /** * @param M_Product_ID * @return error message (if any) or null */ public String validateEndProduct(int M_Product_ID) { String msg = isBom(M_Product_ID); if (!Util.isEmpty(msg)) return msg; if (!costsOK(M_Product_ID)) { msg = "Excessive difference in standard costs"; if (MSysConfig.getBooleanValue(MSysConfig.MFG_ValidateCostsDifferenceOnCreate, false, getAD_Client_ID())) { return msg; } else { log.warning(msg); } } return null; } /** * Validate product has valid BOM * @param M_Product_ID * @return error message or null */ protected String isBom(int M_Product_ID) { String bom = DB.getSQLValueString(get_TrxName(), "SELECT isbom FROM M_Product WHERE M_Product_ID = ?", M_Product_ID); if ("N".compareTo(bom) == 0) { return "Attempt to create product line for Non Bill Of Materials"; } int materials = DB.getSQLValue(get_TrxName(), "SELECT count(bl.PP_Product_BOMLine_ID) FROM PP_Product_BOMLine bl JOIN PP_Product_BOM b ON b.PP_Product_BOM_ID = bl.PP_Product_BOM_ID WHERE b.M_Product_ID = ? " + " AND bl.IsActive='Y' AND b.IsActive='Y' AND b.BOMType='A' AND b.BOMUse='A' ", M_Product_ID ); if (materials == 0) { return "Attempt to create product line for Bill Of Materials with no BOM Components"; } return null; } /** * Cost validation for BOM product using standard costing method.
* - has cost
* - difference between current product cost and sum of component cost is less than 0.005. * @param M_Product_ID * @return true if cost is ok * @throws AdempiereUserError */ protected boolean costsOK(int M_Product_ID) throws AdempiereUserError { MProduct product = MProduct.get(getCtx(), M_Product_ID, get_TrxName()); String costingMethod=product.getCostingMethod(MClient.get(getCtx()).getAcctSchema()); // will not work if non-standard costing is used if (MAcctSchema.COSTINGMETHOD_StandardCosting.equals(costingMethod)) { String sql = "SELECT ABS(((cc.currentcostprice-(SELECT SUM(c.currentcostprice*bom.qtybom)" + " FROM m_cost c" + " INNER JOIN pp_product_bomline bom ON (c.m_product_id=bom.m_product_id AND bom.IsActive='Y')" + " JOIN pp_product_bom b ON (b.pp_product_bom_id = bom.pp_product_bom_id)" + " WHERE b.m_product_id = pp.m_product_id and b.bomuse='A' and b.bomtype='A' AND b.IsActive='Y')" + " )/cc.currentcostprice))" + " FROM m_product pp" + " INNER JOIN m_cost cc on (cc.m_product_id=pp.m_product_id)" + " INNER JOIN m_costelement ce ON (cc.m_costelement_id=ce.m_costelement_id)" + " WHERE cc.currentcostprice > 0 AND pp.M_Product_ID = ?" + " AND ce.costingmethod='S'"; BigDecimal costPercentageDiff = DB.getSQLValueBD(get_TrxName(), sql, M_Product_ID); if (costPercentageDiff == null) { costPercentageDiff = Env.ZERO; String msg = "Could not retrieve costs"; if (MSysConfig.getBooleanValue(MSysConfig.MFG_ValidateCostsOnCreate, false, getAD_Client_ID())) { throw new AdempiereUserError(msg); } else { log.warning(msg); } } if ( (costPercentageDiff.compareTo(new BigDecimal("0.005")))< 0 ) return true; return false; } return true; } @Override public boolean approveIt() { return true; } @Override public boolean rejectIt() { return true; } @Override public boolean voidIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); // Before Void m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_VOID); if (m_processMsg != null) return false; if (DOCSTATUS_Closed.equals(getDocStatus()) || DOCSTATUS_Reversed.equals(getDocStatus()) || DOCSTATUS_Voided.equals(getDocStatus())) { m_processMsg = "Document Closed: " + getDocStatus(); setDocAction(DOCACTION_None); return false; } // Not Processed if (DOCSTATUS_Drafted.equals(getDocStatus()) || DOCSTATUS_Invalid.equals(getDocStatus()) || DOCSTATUS_InProgress.equals(getDocStatus()) || DOCSTATUS_Approved.equals(getDocStatus()) || DOCSTATUS_NotApproved.equals(getDocStatus()) ) { setIsCreated("N"); if (!isUseProductionPlan()) { deleteLines(get_TrxName()); setProductionQty(BigDecimal.ZERO); } else { Query planQuery = new Query(Env.getCtx(), I_M_ProductionPlan.Table_Name, "M_ProductionPlan.M_Production_ID=?", get_TrxName()); List plans = planQuery.setParameters(getM_Production_ID()).list(); for(MProductionPlan plan : plans) { plan.deleteLines(get_TrxName()); plan.setProductionQty(BigDecimal.ZERO); plan.setProcessed(true); plan.saveEx(); } } } else { boolean accrual = false; try { MPeriod.testPeriodOpen(getCtx(), getMovementDate(), getC_DocType_ID(), getAD_Org_ID()); } catch (PeriodClosedException e) { accrual = true; } try { MAcctSchema.testBackDateTrxAllowed(getCtx(), getMovementDate(), get_TrxName()); } catch (BackDateTrxNotAllowedException e) { accrual = true; } if (accrual) return reverseAccrualIt(); else return reverseCorrectIt(); } if (getC_OrderLine_ID() > 0) setC_OrderLine_ID(0); // After Void m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_VOID); if (m_processMsg != null) return false; setProcessed(true); setDocAction(DOCACTION_None); return true; } @Override public boolean closeIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); // Before Close m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_CLOSE); if (m_processMsg != null) return false; setProcessed(true); setDocAction(DOCACTION_None); // After Close m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_CLOSE); if (m_processMsg != null) return false; return true; } @Override public boolean reverseCorrectIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); // Before reverseCorrect m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_REVERSECORRECT); if (m_processMsg != null) return false; MProduction reversal = reverse(false); if (reversal == null) return false; // After reverseCorrect m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_REVERSECORRECT); if (m_processMsg != null) return false; m_processMsg = reversal.getDocumentNo(); return true; } /** * Reverse this production document * @param accrual true to use current date, false to use this production's movement date * @return reversal production record */ protected MProduction reverse(boolean accrual) { Timestamp reversalDate = accrual ? Env.getContextAsDate(getCtx(), Env.DATE) : getMovementDate(); if (reversalDate == null) { reversalDate = new Timestamp(System.currentTimeMillis()); } if (getC_OrderLine_ID() > 0) setC_OrderLine_ID(0); MPeriod.testPeriodOpen(getCtx(), reversalDate, getC_DocType_ID(), getAD_Org_ID()); MAcctSchema.testBackDateTrxAllowed(getCtx(), reversalDate, get_TrxName()); MProduction reversal = null; reversal = copyFrom (reversalDate); StringBuilder msgadd = new StringBuilder("{->").append(getDocumentNo()).append(")"); reversal.addDescription(msgadd.toString()); reversal.setReversal_ID(getM_Production_ID()); reversal.saveEx(get_TrxName()); // Reverse Line Qty MProductionLine[] sLines = getLines(); MProductionLine[] tLines = reversal.getLines(); for (int i = 0; i < sLines.length; i++) { // We need to copy MA if (sLines[i].getM_AttributeSetInstance_ID() == 0) { MProductionLineMA mas[] = MProductionLineMA.get(getCtx(), sLines[i].get_ID(), get_TrxName()); for (int j = 0; j < mas.length; j++) { MProductionLineMA ma = new MProductionLineMA (tLines[i], mas[j].getM_AttributeSetInstance_ID(), mas[j].getMovementQty().negate(),mas[j].getDateMaterialPolicy()); ma.saveEx(get_TrxName()); } } } if (!reversal.processIt(DocAction.ACTION_Complete)) { m_processMsg = "Reversal ERROR: " + reversal.getProcessMsg(); return null; } reversal.closeIt(); reversal.setProcessing (false); reversal.setDocStatus(DOCSTATUS_Reversed); reversal.setDocAction(DOCACTION_None); reversal.saveEx(get_TrxName()); msgadd = new StringBuilder("(").append(reversal.getDocumentNo()).append("<-)"); addDescription(msgadd.toString()); setProcessed(true); setReversal_ID(reversal.getM_Production_ID()); setDocStatus(DOCSTATUS_Reversed); // may come from void setDocAction(DOCACTION_None); return reversal; } /** * Create new production from this production (including lines) * @param reversalDate movement date * @return new production record */ protected MProduction copyFrom(Timestamp reversalDate) { MProduction to = new MProduction(getCtx(), 0, get_TrxName()); PO.copyValues (this, to, getAD_Client_ID(), getAD_Org_ID()); to.set_ValueNoCheck ("DocumentNo", null); // to.setDocStatus (DOCSTATUS_Drafted); // Draft to.setDocAction(DOCACTION_Complete); to.setMovementDate(reversalDate); to.setIsComplete(false); to.setIsCreated("Y"); to.setProcessing(false); to.setProcessed(false); to.setIsUseProductionPlan(isUseProductionPlan()); if (isUseProductionPlan()) { to.saveEx(); Query planQuery = new Query(Env.getCtx(), I_M_ProductionPlan.Table_Name, "M_ProductionPlan.M_Production_ID=?", get_TrxName()); List fplans = planQuery.setParameters(getM_Production_ID()).list(); for(MProductionPlan fplan : fplans) { MProductionPlan tplan = new MProductionPlan(getCtx(), 0, get_TrxName()); PO.copyValues (fplan, tplan, getAD_Client_ID(), getAD_Org_ID()); tplan.setM_Production_ID(to.getM_Production_ID()); tplan.setProductionQty(fplan.getProductionQty().negate()); tplan.setProcessed(false); tplan.saveEx(); MProductionLine[] flines = fplan.getLines(); for(MProductionLine fline : flines) { MProductionLine tline = new MProductionLine(tplan); PO.copyValues (fline, tline, getAD_Client_ID(), getAD_Org_ID()); tline.setM_ProductionPlan_ID(tplan.getM_ProductionPlan_ID()); tline.setMovementQty(fline.getMovementQty().negate()); tline.setPlannedQty(fline.getPlannedQty().negate()); tline.setQtyUsed(fline.getQtyUsed().negate()); tline.saveEx(); } } } else { to.setProductionQty(getProductionQty().negate()); to.saveEx(); MProductionLine[] flines = getLines(); for(MProductionLine fline : flines) { MProductionLine tline = new MProductionLine(to); PO.copyValues (fline, tline, getAD_Client_ID(), getAD_Org_ID()); tline.setM_Production_ID(to.getM_Production_ID()); tline.setMovementQty(fline.getMovementQty().negate()); tline.setPlannedQty(fline.getPlannedQty().negate()); tline.setQtyUsed(fline.getQtyUsed().negate()); tline.saveEx(); } } return to; } /** * Add to Description * @param description text */ public void addDescription (String description) { String desc = getDescription(); if (desc == null) setDescription(description); else{ StringBuilder msgd = new StringBuilder(desc).append(" | ").append(description); setDescription(msgd.toString()); } } // addDescription @Override public boolean reverseAccrualIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); // Before reverseAccrual m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_REVERSEACCRUAL); if (m_processMsg != null) return false; MProduction reversal = reverse(true); if (reversal == null) return false; // After reverseAccrual m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_REVERSEACCRUAL); if (m_processMsg != null) return false; m_processMsg = reversal.getDocumentNo(); return true; } @Override public boolean reActivateIt() { if (log.isLoggable(Level.INFO)) log.info("reActivateIt - " + toString()); // Before reActivate m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_REACTIVATE); if (m_processMsg != null) return false; // After reActivate m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_REACTIVATE); if (m_processMsg != null) return false; return false; } @Override public String getSummary() { return getDocumentNo(); } @Override public String getDocumentInfo() { return getDocumentNo(); } @Override public File createPDF() { return null; } @Override public String getProcessMsg() { return m_processMsg; } @Override public int getDoc_User_ID() { return getCreatedBy(); } @Override public int getC_Currency_ID() { return MClient.get(getCtx()).getC_Currency_ID(); } @Override public BigDecimal getApprovalAmt() { return BigDecimal.ZERO; } @Override protected boolean beforeSave(boolean newRecord) { if (getC_DocType_ID() <= 0) { setC_DocType_ID(MDocType.getDocType(MDocType.DOCBASETYPE_MaterialProduction)); } // Set IsUseProductionPlan flag if (getM_Product_ID() > 0) { if (isUseProductionPlan()) { setIsUseProductionPlan(false); } } else { if (!isUseProductionPlan()) { setIsUseProductionPlan(true); } } return true; } /** * Document Status is Complete, Closed or Reversed * @return true if CO, CL or RE */ public boolean isStatusComplete() { String ds = getDocStatus(); return DOCSTATUS_Completed.equals(ds) || DOCSTATUS_Closed.equals(ds) || DOCSTATUS_Reversed.equals(ds); } // isStatusComplete }