/****************************************************************************** * Product: Adempiere ERP & CRM Smart Business Solution * * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify it * * under the terms version 2 of the GNU General Public License as published * * by the Free Software Foundation. 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., * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * For the text or an alternative of this public license, you may reach us * * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA * * or via info@compiere.org or http://www.compiere.org/license.html * *****************************************************************************/ package org.compiere.acct; import java.math.BigDecimal; import java.sql.ResultSet; import java.util.ArrayList; import java.util.HashMap; import java.util.logging.Level; import org.compiere.model.MAccount; import org.compiere.model.MAcctSchema; import org.compiere.model.MClient; import org.compiere.model.MConversionRate; import org.compiere.model.MCost; import org.compiere.model.MCostDetail; import org.compiere.model.MCostElement; import org.compiere.model.MDocType; import org.compiere.model.MInventory; import org.compiere.model.MInventoryLine; import org.compiere.model.MInventoryLineMA; import org.compiere.model.MProduct; import org.compiere.model.ProductCost; import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.Util; /** * Post Inventory Documents. *
 *  Table:              M_Inventory (321)
 *  Document Types:     MMI
 *  
* @author Jorg Janke * @author Armen Rizal, Goodwill Consulting *
  • BF [ 1745154 ] Cost in Reversing Material Related Docs * @author red1 *
  • BF [ 2982994 ] Internal Use Inventory does not reverse Accts * @version $Id: Doc_Inventory.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ */ public class Doc_Inventory extends Doc { private int m_Reversal_ID = 0; @SuppressWarnings("unused") private String m_DocStatus = ""; private String parentDocSubTypeInv; /** * Constructor * @param as accounting schema * @param rs record * @param trxName trx */ public Doc_Inventory (MAcctSchema as, ResultSet rs, String trxName) { super (as, MInventory.class, rs, DOCTYPE_MatInventory, trxName); } // Doc_Inventory /** * Load Document Details * @return error message or null */ @Override protected String loadDocumentDetails() { MInventory inventory = (MInventory)getPO(); setDateDoc (inventory.getMovementDate()); setDateAcct(inventory.getMovementDate()); m_Reversal_ID = inventory.getReversal_ID();//store original (voided/reversed) document m_DocStatus = inventory.getDocStatus(); MDocType dt = MDocType.get(getCtx(), getC_DocType_ID()); parentDocSubTypeInv = dt.getDocSubTypeInv(); // IDEMPIERE-3046 Add Currency Field to Cost Adjustment Window if (MDocType.DOCSUBTYPEINV_CostAdjustment.equals(parentDocSubTypeInv)) { if (inventory.getC_Currency_ID() == 0) setC_Currency_ID(MClient.get(getCtx()).getAcctSchema().getC_Currency_ID()); } else { setC_Currency_ID (NO_CURRENCY); } // Contained Objects p_lines = loadLines(inventory); if (log.isLoggable(Level.FINE)) log.fine("Lines=" + p_lines.length); return null; } // loadDocumentDetails /** * Load inventory lines * @param inventory inventory * @return DocLine Array */ private DocLine[] loadLines(MInventory inventory) { ArrayList list = new ArrayList(); MInventoryLine[] lines = inventory.getLines(false); for (int i = 0; i < lines.length; i++) { MInventoryLine line = lines[i]; if (!line.isActive()) continue; String docSubTypeInv; if (Util.isEmpty(parentDocSubTypeInv)) { // IDEMPIERE-675: for backward compatibility - to post old documents that could have subtypeinv empty if (line.getQtyInternalUse().signum() != 0) { docSubTypeInv = MDocType.DOCSUBTYPEINV_InternalUseInventory; } else { docSubTypeInv = MDocType.DOCSUBTYPEINV_PhysicalInventory; } } else { docSubTypeInv = parentDocSubTypeInv; } BigDecimal qtyDiff = Env.ZERO; BigDecimal amtDiff = Env.ZERO; if (MDocType.DOCSUBTYPEINV_InternalUseInventory.equals(docSubTypeInv)) qtyDiff = line.getQtyInternalUse().negate(); else if (MDocType.DOCSUBTYPEINV_PhysicalInventory.equals(docSubTypeInv)) qtyDiff = line.getQtyCount().subtract(line.getQtyBook()); else if (MDocType.DOCSUBTYPEINV_CostAdjustment.equals(docSubTypeInv)) amtDiff = line.getNewCostPrice().subtract(line.getCurrentCostPrice()); // nothing to post if (qtyDiff.signum() == 0 && amtDiff.signum() == 0) continue; // DocLine docLine = new DocLine (line, this); docLine.setQty (qtyDiff, false); // -5 => -5 if (amtDiff.signum() != 0) { docLine.setAmount(amtDiff); } docLine.setReversalLine_ID(line.getReversalLine_ID()); if (log.isLoggable(Level.FINE)) log.fine(docLine.toString()); list.add (docLine); } // Return Array DocLine[] dls = new DocLine[list.size()]; list.toArray(dls); return dls; } // loadLines /** * Get Balance * @return Zero (always balanced) */ @Override public BigDecimal getBalance() { BigDecimal retValue = Env.ZERO; return retValue; } // getBalance /** * Create Facts (the accounting logic) for * MMI. *
    	 *  Inventory
    	 *      Inventory       DR      CR
    	 *      InventoryDiff   DR      CR   (or Charge)
    	 *  
    * @param as account schema * @return Fact */ @Override public ArrayList createFacts (MAcctSchema as) { // create Fact Header Fact fact = new Fact(this, as, Fact.POST_Actual); if (!MDocType.DOCSUBTYPEINV_CostAdjustment.equals(parentDocSubTypeInv)) setC_Currency_ID(as.getC_Currency_ID()); // Line pointers FactLine dr = null; FactLine cr = null; MInventory inventory = (MInventory) getPO(); boolean costAdjustment = MDocType.DOCSUBTYPEINV_CostAdjustment.equals(parentDocSubTypeInv); String docCostingMethod = inventory.getCostingMethod(); HashMap costMap = new HashMap(); for (int i = 0; i < p_lines.length; i++) { DocLine line = p_lines[i]; boolean doPosting = true; String costingLevel = null; MProduct product = null; if (costAdjustment) { product = line.getProduct(); if (!product.isStocked()) { doPosting = false; } else { String productCostingMethod = product.getCostingMethod(as); costingLevel = product.getCostingLevel(as); if (!docCostingMethod.equals(productCostingMethod)) { doPosting = false; } } } BigDecimal costs = null; BigDecimal adjustmentDiff = null; if (costAdjustment) { costs = line.getAmtSource(); product = line.getProduct(); int orgId = line.getAD_Org_ID(); int asiId = line.getM_AttributeSetInstance_ID(); if (MAcctSchema.COSTINGLEVEL_Client.equals(costingLevel)) { orgId = 0; asiId = 0; } else if (MAcctSchema.COSTINGLEVEL_Organization.equals(costingLevel)) asiId = 0; else if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(costingLevel)) orgId = 0; MCostElement ce = MCostElement.getMaterialCostElement(getCtx(), docCostingMethod, orgId); MCost cost = MCost.get(product, asiId, as, orgId, ce.getM_CostElement_ID(), getTrxName()); DB.getDatabase().forUpdate(cost, 120); BigDecimal currentQty = cost.getCurrentQty(); adjustmentDiff = costs; costs = costs.multiply(currentQty); } else { if (!isReversal(line)) { product = line.getProduct(); if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(product.getCostingLevel(as)) ) { if (line.getM_AttributeSetInstance_ID() == 0 ) { MInventoryLine invLine = (MInventoryLine) line.getPO(); MInventoryLineMA mas[] = MInventoryLineMA.get(getCtx(), invLine.get_ID(), getTrxName()); if (mas != null && mas.length > 0 ) { costs = BigDecimal.ZERO; for (int j = 0; j < mas.length; j++) { MInventoryLineMA ma = mas[j]; BigDecimal QtyMA = ma.getMovementQty(); ProductCost pc = line.getProductCost(); pc.setQty(QtyMA.negate()); pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID()); BigDecimal maCosts = line.getProductCosts(as, line.getAD_Org_ID(), true, "M_InventoryLine_ID=?"); costMap.put(line.get_ID()+ "_"+ ma.getM_AttributeSetInstance_ID(), maCosts); costs = costs.add(maCosts); } } } else { costs = line.getProductCosts(as, line.getAD_Org_ID(), true, "M_InventoryLine_ID=?"); } } else { // MZ Goodwill // if Physical Inventory CostDetail is exist then get Cost from Cost Detail costs = line.getProductCosts(as, line.getAD_Org_ID(), true, "M_InventoryLine_ID=?"); // end MZ } if (costs == null || costs.signum() == 0) { p_Error = "No Costs for " + line.getProduct().getName(); return null; } } else { //updated below costs = Env.ONE; } } if (doPosting) { int C_Currency_ID = getC_Currency_ID() > 0 ? getC_Currency_ID() : as.getC_Currency_ID(); // Inventory DR CR dr = fact.createLine(line, line.getAccount(ProductCost.ACCTTYPE_P_Asset, as), C_Currency_ID, costs); // may be zero difference - no line created. if (dr != null) { dr.setM_Locator_ID(line.getM_Locator_ID()); if (isReversal(line)) { // Set AmtAcctDr from Original Phys.Inventory if (!dr.updateReverseLine (MInventory.Table_ID, m_Reversal_ID, line.getReversalLine_ID(),Env.ONE)) { p_Error = "Original Physical Inventory not posted yet"; return null; } costs = dr.getAcctBalance(); //get original cost } // InventoryDiff DR CR // or Charge MAccount invDiff = null; if (isReversal(line) && line.getC_Charge_ID() != 0) { invDiff = line.getChargeAccount(as, costs); } else { invDiff = line.getChargeAccount(as, costs.negate()); } if (invDiff == null) { if (costAdjustment) { invDiff = line.getProductCost().getAccount(ProductCost.ACCTTYPE_P_CostAdjustment, as); } else { invDiff = getAccount(Doc.ACCTTYPE_InvDifferences, as); } } cr = fact.createLine(line, invDiff, C_Currency_ID, costs.negate()); if (cr != null) { cr.setM_Locator_ID(line.getM_Locator_ID()); cr.setQty(line.getQty().negate()); if (line.getC_Charge_ID() != 0) // explicit overwrite for charge cr.setAD_Org_ID(line.getAD_Org_ID()); if (isReversal(line)) { // Set AmtAcctCr from Original Phys.Inventory if (!cr.updateReverseLine (MInventory.Table_ID, m_Reversal_ID, line.getReversalLine_ID(),Env.ONE, dr)) { p_Error = "Original Physical Inventory not posted yet"; return null; } } } } } if (doPosting || costAdjustment) { product = line.getProduct(); BigDecimal costDetailAmt = costAdjustment ? adjustmentDiff : costs; if (costAdjustment && getC_Currency_ID() > 0 && getC_Currency_ID() != as.getC_Currency_ID()) { costDetailAmt = MConversionRate.convert (getCtx(), costDetailAmt, getC_Currency_ID(), as.getC_Currency_ID(), getDateAcct(), 0, getAD_Client_ID(), getAD_Org_ID(), true); } if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(product.getCostingLevel(as)) ) { if (line.getM_AttributeSetInstance_ID() == 0 ) { MInventoryLine invLine = (MInventoryLine) line.getPO(); MInventoryLineMA mas[] = MInventoryLineMA.get(getCtx(), invLine.get_ID(), getTrxName()); if (mas != null && mas.length > 0 ) { costs = BigDecimal.ZERO; for (int j = 0; j < mas.length; j++) { MInventoryLineMA ma = mas[j]; BigDecimal maCost = costMap.get(line.get_ID()+ "_"+ ma.getM_AttributeSetInstance_ID()); BigDecimal qty = ma.getMovementQty(); if (qty.signum() != line.getQty().signum()) qty = qty.negate(); if (maCost.signum() != costDetailAmt.signum()) maCost = maCost.negate(); if (!MCostDetail.createInventory(as, line.getAD_Org_ID(), line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(), line.get_ID(), 0, maCost, qty, line.getDescription(), getTrxName())) { p_Error = "Failed to create cost detail record"; return null; } } } } else { BigDecimal amt = costDetailAmt; if (!MCostDetail.createInventory(as, line.getAD_Org_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.get_ID(), 0, amt, line.getQty(), line.getDescription(), getTrxName())) { p_Error = "Failed to create cost detail record"; return null; } } } else { // Cost Detail BigDecimal amt = costDetailAmt; if (!MCostDetail.createInventory(as, line.getAD_Org_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.get_ID(), 0, amt, line.getQty(), line.getDescription(), getTrxName())) { p_Error = "Failed to create cost detail record"; return null; } } } } // ArrayList facts = new ArrayList(); facts.add(fact); return facts; } // createFact /** * @param line * @return true if line is for reversal */ private boolean isReversal(DocLine line) { return m_Reversal_ID !=0 && line.getReversalLine_ID() != 0; } } // Doc_Inventory