/******************************************************************************
* 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.math.RoundingMode;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.adempiere.exceptions.AverageCostingZeroQtyException;
import org.compiere.model.I_C_Order;
import org.compiere.model.I_C_OrderLine;
import org.compiere.model.MAccount;
import org.compiere.model.MAcctSchema;
import org.compiere.model.MAcctSchemaElement;
import org.compiere.model.MConversionRate;
import org.compiere.model.MCost;
import org.compiere.model.MCostDetail;
import org.compiere.model.MCostElement;
import org.compiere.model.MCurrency;
import org.compiere.model.MFactAcct;
import org.compiere.model.MInOut;
import org.compiere.model.MInOutLine;
import org.compiere.model.MInvoice;
import org.compiere.model.MInvoiceLine;
import org.compiere.model.MMatchInv;
import org.compiere.model.MOrderLandedCostAllocation;
import org.compiere.model.MTax;
import org.compiere.model.MUOM;
import org.compiere.model.ProductCost;
import org.compiere.model.Query;
import org.compiere.model.X_M_Cost;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Trx;
/**
* Post MatchInv Documents.
*
* Table: M_MatchInv (472)
* Document Types: MXI
*
* Update Costing Records
* @author Jorg Janke
* @version $Id: Doc_MatchInv.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $
*
* FR [ 1840016 ] Avoid usage of clearing accounts - subject to C_AcctSchema.IsPostIfClearingEqual
* Avoid posting if both accounts Not Invoiced Receipts and Inventory Clearing are equal
* BF [ 2789949 ] Multicurrency in matching posting
*/
public class Doc_MatchInv extends Doc
{
/**
* Constructor
* @param as accounting schema
* @param rs record
* @param trxName trx
*/
public Doc_MatchInv (MAcctSchema as, ResultSet rs, String trxName)
{
super(as, MMatchInv.class, rs, DOCTYPE_MatMatchInv, trxName);
} // Doc_MatchInv
/** Tolerance G&L */
private static final BigDecimal TOLERANCE = BigDecimal.valueOf(0.02);
/** Invoice Line */
private MInvoiceLine m_invoiceLine = null;
/** Material Receipt */
private MInOutLine m_receiptLine = null;
private ProductCost m_pc = null;
private MMatchInv m_matchInv;
/**
* Load Specific Document Details
* @return error message or null
*/
@Override
protected String loadDocumentDetails ()
{
setC_Currency_ID (Doc.NO_CURRENCY);
m_matchInv = (MMatchInv)getPO();
setDateDoc(m_matchInv.getDateTrx());
setQty (m_matchInv.getQty());
// Invoice Info
int C_InvoiceLine_ID = m_matchInv.getC_InvoiceLine_ID();
m_invoiceLine = new MInvoiceLine (getCtx(), C_InvoiceLine_ID, getTrxName());
// BP for NotInvoicedReceipts
int C_BPartner_ID = m_invoiceLine.getParent().getC_BPartner_ID();
setC_BPartner_ID(C_BPartner_ID);
//
int M_InOutLine_ID = m_matchInv.getM_InOutLine_ID();
m_receiptLine = new MInOutLine (getCtx(), M_InOutLine_ID, getTrxName());
//
m_pc = new ProductCost (Env.getCtx(),
getM_Product_ID(), m_matchInv.getM_AttributeSetInstance_ID(), getTrxName());
m_pc.setQty(getQty());
return null;
} // loadDocumentDetails
/**
* Get Source Currency Balance - subtracts line and tax amounts from total - no rounding
* @return Zero (always balanced)
*/
@Override
public BigDecimal getBalance()
{
return Env.ZERO;
} // getBalance
/**
* Create Facts (the accounting logic) for
* MXI.
* (single line)
*
* NotInvoicedReceipts DR (Receipt Org)
* InventoryClearing CR
* InvoicePV DR CR (difference)
* Commitment
* Expense CR
* Offset DR
*
* @param as accounting schema
* @return Fact
*/
@Override
public ArrayList createFacts (MAcctSchema as)
{
ArrayList facts = new ArrayList();
// invoice gain/loss accounting fact line list
ArrayList invGainLossFactLines = new ArrayList();
// invoice list
ArrayList invList = new ArrayList();
// invoice line list
ArrayList invLineList = new ArrayList();
// C_Invoice_ID and the current M_MatchInv inventory clearing/expense accounting fact lines
HashMap> htFactLineInv = new HashMap>();
// receipt gain/loss accounting fact line list
ArrayList mrGainLossFactLines = new ArrayList();
// NIR accounting fact line list
ArrayList mrFactLines = new ArrayList();
// Nothing to do
if (getM_Product_ID() == 0 // no Product
|| getQty().signum() == 0
|| (m_receiptLine.get_ID() > 0 && m_receiptLine.getMovementQty().signum() == 0)) // Qty = 0
{
if (log.isLoggable(Level.FINE)) log.fine("No Product/Qty - M_Product_ID=" + getM_Product_ID()
+ ",Qty=" + getQty() + ",InOutQty=" + m_receiptLine.getMovementQty());
return facts;
}
if (m_receiptLine.getM_InOutLine_ID() == 0)
{
MInvoice m_invoice = new MInvoice(getCtx(), m_invoiceLine.getC_Invoice_ID(), getTrxName());
boolean isCreditMemo = m_invoice.isCreditMemo();
if (!isCreditMemo)
return facts;
else
return createCreditMemoFacts(as);
}
if (m_receiptLine.getParent().getC_DocType().getDocBaseType().equals(DOCTYPE_MatShipment))
return createMatShipmentFacts(as);
// create Fact Header
Fact fact = new Fact(this, as, Fact.POST_Actual);
setC_Currency_ID (as.getC_Currency_ID());
boolean isInterOrg = isInterOrg(as);
// NotInvoicedReceipt DR
// From Receipt
BigDecimal multiplier = getQty()
.divide(m_receiptLine.getMovementQty(), 12, RoundingMode.HALF_UP);
FactLine dr = fact.createLine (null,
getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as),
as.getC_Currency_ID(), Env.ONE, null); // updated below
if (dr == null)
{
p_Error = "No Product Costs";
return null;
}
dr.setQty(getQty());
BigDecimal temp = dr.getAcctBalance();
// Set AmtAcctCr/Dr from Receipt (sets also Project)
if (m_matchInv.isReversal())
{
if (!dr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
if (!dr.updateReverseLine (MInOut.Table_ID, // Amt updated
m_receiptLine.getM_InOut_ID(), m_receiptLine.getM_InOutLine_ID(),
multiplier))
{
p_Error = "Mat.Receipt not posted yet";
return null;
}
}
if (log.isLoggable(Level.FINE)) log.fine("CR - Amt(" + temp + "->" + dr.getAcctBalance()
+ ") - " + dr.toString());
// InventoryClearing CR
// From Invoice
MAccount expense = m_pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as);
if (m_pc.isService())
expense = m_pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
BigDecimal LineNetAmt = m_invoiceLine.getLineNetAmt();
multiplier = getQty()
.divide(m_invoiceLine.getQtyInvoiced(), 12, RoundingMode.HALF_UP);
if (multiplier.compareTo(Env.ONE) != 0)
LineNetAmt = LineNetAmt.multiply(multiplier);
if (m_pc.isService())
LineNetAmt = dr.getAcctBalance(); // book out exact receipt amt
FactLine cr = null;
if (as.isAccrual())
{
cr = fact.createLine (null, expense,
as.getC_Currency_ID(), null, LineNetAmt); // updated below
if (cr == null)
{
if (log.isLoggable(Level.FINE)) log.fine("Line Net Amt=0 - M_Product_ID=" + getM_Product_ID()
+ ",Qty=" + getQty() + ",InOutQty=" + m_receiptLine.getMovementQty());
cr = fact.createLine (null, expense, as.getC_Currency_ID(), null, Env.ONE);
cr.setAmtAcctCr(BigDecimal.ZERO);
cr.setAmtSourceCr(BigDecimal.ZERO);
}
temp = cr.getAcctBalance();
if (m_matchInv.isReversal())
{
if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE, dr))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
cr.setQty(getQty().negate());
// Set AmtAcctCr/Dr from Invoice (sets also Project)
if (!cr.updateReverseLine (MInvoice.Table_ID, // Amt updated
m_invoiceLine.getC_Invoice_ID(), m_invoiceLine.getC_InvoiceLine_ID(), multiplier))
{
p_Error = "Invoice not posted yet";
return null;
}
}
if (log.isLoggable(Level.FINE)) log.fine("DR - Amt(" + temp + "->" + cr.getAcctBalance()
+ ") - " + cr.toString());
}
else // Cash Acct
{
MInvoice invoice = m_invoiceLine.getParent();
if (as.getC_Currency_ID() != invoice.getC_Currency_ID())
LineNetAmt = MConversionRate.convert(getCtx(), LineNetAmt,
invoice.getC_Currency_ID(), as.getC_Currency_ID(),
invoice.getDateAcct(), invoice.getC_ConversionType_ID(),
invoice.getAD_Client_ID(), invoice.getAD_Org_ID());
cr = fact.createLine (null, expense,
as.getC_Currency_ID(), null, LineNetAmt);
if (m_matchInv.isReversal())
{
if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE, dr))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
int precision = MUOM.getPrecision(getCtx(), m_invoiceLine.getC_UOM_ID());
cr.setQty(getQty().multiply(multiplier).negate().setScale(precision, RoundingMode.HALF_UP));
}
}
// gain/loss + rounding adjustment
if (m_receiptLine != null && m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
mrFactLines.add(dr);
p_Error = createReceiptGainLoss(as, fact, getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as), m_receiptLine.getParent(), dr.getAmtSourceDr(), dr.getAmtAcctDr(), mrGainLossFactLines, mrFactLines);
if (p_Error != null)
return null;
}
// rounding adjustment
if (!mrFactLines.isEmpty())
{
p_Error = createReceiptRoundingCorrection(as, fact, getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as), mrGainLossFactLines, mrFactLines);
if (p_Error != null)
return null;
}
// gain/loss
if (m_invoiceLine != null && m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
MInvoice invoice = m_invoiceLine.getParent();
if (!invList.contains(invoice))
invList.add(invoice);
if (!invLineList.contains(m_invoiceLine))
invLineList.add(m_invoiceLine);
ArrayList factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList();
factLineList.add(cr);
htFactLineInv.put(invoice.get_ID(), factLineList);
p_Error = createInvoiceGainLoss(as, fact, expense, invoice, cr.getAmtSourceCr(), cr.getAmtAcctCr(), invGainLossFactLines, htFactLineInv);
if (p_Error != null)
return null;
}
// rounding adjustment
if (!htFactLineInv.isEmpty())
{
p_Error = createInvoiceRoundingCorrection(as, fact, expense, invGainLossFactLines, invList, invLineList, htFactLineInv);
if (p_Error != null)
return null;
}
cr.setC_Activity_ID(m_invoiceLine.getC_Activity_ID());
cr.setC_Campaign_ID(m_invoiceLine.getC_Campaign_ID());
cr.setC_Project_ID(m_invoiceLine.getC_Project_ID());
cr.setC_ProjectPhase_ID(m_invoiceLine.getC_ProjectPhase_ID());
cr.setC_ProjectTask_ID(m_invoiceLine.getC_ProjectTask_ID());
cr.setC_UOM_ID(m_invoiceLine.getC_UOM_ID());
cr.setUser1_ID(m_invoiceLine.getUser1_ID());
cr.setUser2_ID(m_invoiceLine.getUser2_ID());
if (m_matchInv.isReversal())
{
cr.setQty(getQty().negate());
}
//AZ Goodwill
//Desc: Source Not Balanced problem because Currency is Difference - PO=CNY but AP=USD
//see also Fact.java: checking for isMultiCurrency()
if (dr.getC_Currency_ID() != cr.getC_Currency_ID())
setIsMultiCurrency(true);
//end AZ
// Avoid usage of clearing accounts
// If both accounts Not Invoiced Receipts and Inventory Clearing are equal
// then remove the posting
MAccount acct_db = dr.getAccount(); // not_invoiced_receipts
MAccount acct_cr = cr.getAccount(); // inventory_clearing
if ((!as.isPostIfClearingEqual()) && acct_db.equals(acct_cr) && (!isInterOrg)) {
BigDecimal debit = dr.getAmtSourceDr();
BigDecimal credit = cr.getAmtSourceCr();
if (debit.compareTo(credit) == 0) {
fact.remove(dr);
fact.remove(cr);
}
}
// End Avoid usage of clearing accounts
// Invoice Price Variance difference
BigDecimal ipv = cr.getAcctBalance().add(dr.getAcctBalance()).negate();
BigDecimal ipvSource = dr.getAmtSourceDr().subtract(cr.getAmtSourceCr()).negate();
processInvoicePriceVariance(as, fact, ipv, ipvSource);
if (log.isLoggable(Level.FINE)) log.fine("IPV=" + ipv + "; Balance=" + fact.getSourceBalance());
String error = createMatchInvCostDetail(as);
if (error != null && error.trim().length() > 0)
{
p_Error = error;
return null;
}
//
facts.add(fact);
/** Commitment release ****/
if (as.isAccrual() && as.isCreatePOCommitment())
{
fact = Doc_Order.getCommitmentRelease(as, this,
getQty(), m_invoiceLine.getC_InvoiceLine_ID(), Env.ONE);
if (fact == null)
return null;
facts.add(fact);
} // Commitment
return facts;
} // createFact
/**
* @param as
* @param fact
* @param ipv
*/
protected void processInvoicePriceVariance(MAcctSchema as, Fact fact,
BigDecimal ipv, BigDecimal ipvSource) {
if (ipv.signum() == 0) return;
MMatchInv matchInv = (MMatchInv)getPO();
String costingMethod = m_pc.getProduct().getCostingMethod(as);
BigDecimal amtVariance = Env.ZERO;
BigDecimal amtAsset = Env.ZERO;
BigDecimal qtyMatched = matchInv.getQty();
BigDecimal qtyCost = null;
Boolean isStockCoverage = false;
boolean isReversal = matchInv.getReversal_ID() > 0 && matchInv.getReversal_ID() < matchInv.get_ID();
if (X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod) && m_invoiceLine.getM_Product_ID() > 0 && !isReversal)
{
isStockCoverage = true;
int AD_Org_ID = m_receiptLine.getAD_Org_ID();
int M_AttributeSetInstance_ID = matchInv.getM_AttributeSetInstance_ID();
if (MAcctSchema.COSTINGLEVEL_Client.equals(as.getCostingLevel()))
{
AD_Org_ID = 0;
M_AttributeSetInstance_ID = 0;
}
else if (MAcctSchema.COSTINGLEVEL_Organization.equals(as.getCostingLevel()))
M_AttributeSetInstance_ID = 0;
else if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(as.getCostingLevel()))
AD_Org_ID = 0;
MCostElement ce = MCostElement.getMaterialCostElement(getCtx(), costingMethod, AD_Org_ID);
MCostDetail cd = MCostDetail.get (as.getCtx(), "M_MatchInv_ID=? AND Coalesce(M_CostElement_ID,0)=0",
matchInv.getM_MatchInv_ID(), M_AttributeSetInstance_ID, as.getC_AcctSchema_ID(), getTrxName());
if(cd!=null){
qtyCost = cd.getCurrentQty();
}else{
MCost c = MCost.get(getCtx(), getAD_Client_ID(), AD_Org_ID, m_invoiceLine.getM_Product_ID(),
as.getM_CostType_ID(), as.getC_AcctSchema_ID(), ce.getM_CostElement_ID(),
M_AttributeSetInstance_ID, getTrxName());
qtyCost = (c!=null? c.getCurrentQty():Env.ZERO);
}
if (qtyCost != null && qtyCost.compareTo(qtyMatched) < 0 )
{
//If current cost qty < invoice qty
amtAsset = qtyCost.multiply(ipv).divide(qtyMatched,as.getCostingPrecision(),RoundingMode.HALF_UP);
amtVariance = ipv.subtract(amtAsset);
}else{
//If current qty >= invoice qty
amtAsset = ipv;
}
}
else if (X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod) && m_invoiceLine.getM_Product_ID() > 0 && isReversal)
{
isStockCoverage = true;
int M_AttributeSetInstance_ID = matchInv.getM_AttributeSetInstance_ID();
MCostDetail cd = MCostDetail.get (as.getCtx(), "M_MatchInv_ID=? AND Coalesce(M_CostElement_ID,0)=0",
matchInv.getReversal_ID(), M_AttributeSetInstance_ID, as.getC_AcctSchema_ID(), getTrxName());
amtAsset = cd != null ? cd.getAmt().negate() : BigDecimal.ZERO;
amtVariance = ipv.subtract(amtAsset);
}
Trx trx = Trx.get(getTrxName(), false);
Savepoint savepoint = null;
boolean zeroQty = false;
try {
savepoint = trx.setSavepoint(null);
if (!MCostDetail.createMatchInvoice(as, m_invoiceLine.getAD_Org_ID(),
m_invoiceLine.getM_Product_ID(), m_invoiceLine.getM_AttributeSetInstance_ID(),
matchInv.getM_MatchInv_ID(), 0,
isStockCoverage ? amtAsset: ipv, BigDecimal.ZERO, "Invoice Price Variance", getTrxName())) {
throw new RuntimeException("Failed to create cost detail record.");
}
} catch (SQLException e) {
throw new RuntimeException(e.getLocalizedMessage(), e);
} catch (AverageCostingZeroQtyException e) {
zeroQty = true;
try {
trx.rollback(savepoint);
savepoint = null;
} catch (SQLException e1) {
throw new RuntimeException(e1.getLocalizedMessage(), e1);
}
} finally {
if (savepoint != null) {
try {
trx.releaseSavepoint(savepoint);
} catch (SQLException e) {}
}
}
MAccount account = m_pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as);
if (m_pc.isService())
account = m_pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
if (X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod)) {
FactLine varianceLine = null;
if (amtVariance.compareTo(Env.ZERO) != 0)
{
varianceLine = fact.createLine(null,
m_pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as), as.getC_Currency_ID(),
amtVariance);
updateFactLine(varianceLine);
if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID())
{
updateFactLineAmtSource(varianceLine, ipvSource.multiply(amtVariance).divide(ipv));
}
}
if (amtAsset.compareTo(Env.ZERO) != 0)
{
FactLine line = fact.createLine(null, account, as.getC_Currency_ID(), amtAsset);
updateFactLine(line);
if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID())
{
updateFactLineAmtSource(line, ipvSource.multiply(amtAsset).divide(ipv));
}
}
} else if (X_M_Cost.COSTINGMETHOD_AverageInvoice.equals(costingMethod) && !zeroQty) {
//TODO test for avg Invoice costing method as here dropped posting of posting to IPV account
FactLine line = fact.createLine(null, account, as.getC_Currency_ID(), ipv);
updateFactLine(line);
if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID())
{
updateFactLineAmtSource(line, ipvSource);
}
}else{
//For standard costing post to IPV account
FactLine pv = fact.createLine(null,
m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as),
as.getC_Currency_ID(), ipv);
updateFactLine(pv);
if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID())
{
updateFactLineAmtSource(pv, ipvSource);
}
}
}
/** Verify if the posting involves two or more organizations
@return true if there are more than one org involved on the posting
*/
private boolean isInterOrg(MAcctSchema as) {
MAcctSchemaElement elementorg = as.getAcctSchemaElement(MAcctSchemaElement.ELEMENTTYPE_Organization);
if (elementorg == null || !elementorg.isBalanced()) {
// no org element or not need to be balanced
return false;
}
// verify if org of receipt line is different from org of invoice line
if (m_receiptLine != null && m_invoiceLine != null && m_receiptLine.getAD_Org_ID() != m_invoiceLine.getAD_Org_ID())
return true;
return false;
}
/**
* Create cost detail for match invoice
* @param as accounting schema
* @return error message or null
*/
private String createMatchInvCostDetail(MAcctSchema as)
{
if (m_invoiceLine != null && m_invoiceLine.get_ID() > 0
&& m_receiptLine != null && m_receiptLine.get_ID() > 0)
{
MMatchInv matchInv = (MMatchInv)getPO();
BigDecimal LineNetAmt = m_invoiceLine.getLineNetAmt();
BigDecimal multiplier = getQty()
.divide(m_invoiceLine.getQtyInvoiced(), 12, RoundingMode.HALF_UP);
if (multiplier.compareTo(Env.ONE) != 0)
LineNetAmt = LineNetAmt.multiply(multiplier);
// MZ Goodwill
// Create Cost Detail Matched Invoice using Total Amount and Total Qty based on InvoiceLine
MMatchInv[] mInv = MMatchInv.getInvoiceLine(getCtx(), m_invoiceLine.getC_InvoiceLine_ID(), getTrxName());
BigDecimal tQty = Env.ZERO;
BigDecimal tAmt = Env.ZERO;
for (int i = 0 ; i < mInv.length ; i++)
{
if (mInv[i].isPosted() && mInv[i].getM_MatchInv_ID() != get_ID() && mInv[i].getM_AttributeSetInstance_ID() == matchInv.getM_AttributeSetInstance_ID())
{
tQty = tQty.add(mInv[i].getQty());
multiplier = mInv[i].getQty()
.divide(m_invoiceLine.getQtyInvoiced(), 12, RoundingMode.HALF_UP);
tAmt = tAmt.add(m_invoiceLine.getLineNetAmt().multiply(multiplier));
}
}
tAmt = tAmt.add(LineNetAmt); //Invoice Price
// adjust for tax
MTax tax = MTax.get(getCtx(), m_invoiceLine.getC_Tax_ID());
int stdPrecision = MCurrency.getStdPrecision(getCtx(), m_invoiceLine.getParent().getC_Currency_ID());
if (m_invoiceLine.isTaxIncluded())
{
BigDecimal tAmtTax = tax.calculateTax(tAmt, true, stdPrecision);
if (tax.isSummary())
{
tAmt = tAmt.subtract(tAmtTax);
BigDecimal base = tAmt;
for (MTax childTax : tax.getChildTaxes(false))
{
if (!childTax.isZeroTax())
{
if (childTax.isDistributeTaxWithLineItem())
{
BigDecimal taxAmt = childTax.calculateTax(base, false, stdPrecision);
tAmt = tAmt.add(taxAmt);
}
}
}
}
else if (!tax.isDistributeTaxWithLineItem())
{
tAmt = tAmt.subtract(tAmtTax);
}
}
else
{
if (tax.isSummary())
{
BigDecimal base = tAmt;
for (MTax childTax : tax.getChildTaxes(false))
{
if (!childTax.isZeroTax())
{
if (childTax.isDistributeTaxWithLineItem())
{
BigDecimal taxAmt = childTax.calculateTax(base, false, stdPrecision);
tAmt = tAmt.add(taxAmt);
}
}
}
}
else if (tax.isDistributeTaxWithLineItem())
{
BigDecimal taxAmt = tax.calculateTax(tAmt, false, stdPrecision);
tAmt = tAmt.add(taxAmt);
}
}
// Different currency
MInvoice invoice = m_invoiceLine.getParent();
if (as.getC_Currency_ID() != invoice.getC_Currency_ID())
{
tAmt = MConversionRate.convert(getCtx(), tAmt,
invoice.getC_Currency_ID(), as.getC_Currency_ID(),
invoice.getDateAcct(), invoice.getC_ConversionType_ID(),
invoice.getAD_Client_ID(), invoice.getAD_Org_ID());
if (tAmt == null)
{
return "AP Invoice not convertible - " + as.getName();
}
}
// set Qty to negative value when MovementType is Vendor Returns
MInOut receipt = m_receiptLine.getParent();
if (receipt.getMovementType().equals(MInOut.MOVEMENTTYPE_VendorReturns))
tQty = tQty.add(getQty().negate()); // Qty is set to negative value
else
tQty = tQty.add(getQty());
// Set Total Amount and Total Quantity from Matched Invoice
if (!MCostDetail.createInvoice(as, getAD_Org_ID(),
getM_Product_ID(), matchInv.getM_AttributeSetInstance_ID(),
m_invoiceLine.getC_InvoiceLine_ID(), 0, // No cost element
tAmt, tQty, getDescription(), getTrxName()))
{
return "Failed to create cost detail record";
}
Map landedCostMap = new LinkedHashMap();
I_C_OrderLine orderLine = m_receiptLine.getC_OrderLine();
if (orderLine == null)
return "";
int C_OrderLine_ID = orderLine.getC_OrderLine_ID();
MOrderLandedCostAllocation[] allocations = MOrderLandedCostAllocation.getOfOrderLine(C_OrderLine_ID, getTrxName());
for(MOrderLandedCostAllocation allocation : allocations)
{
BigDecimal totalAmt = allocation.getAmt();
BigDecimal totalQty = allocation.getQty();
BigDecimal amt = totalAmt.multiply(tQty).divide(totalQty, 12, RoundingMode.HALF_UP);
if (orderLine.getC_Currency_ID() != as.getC_Currency_ID())
{
I_C_Order order = orderLine.getC_Order();
Timestamp dateAcct = order.getDateAcct();
BigDecimal rate = MConversionRate.getRate(
order.getC_Currency_ID(), as.getC_Currency_ID(),
dateAcct, order.getC_ConversionType_ID(),
order.getAD_Client_ID(), order.getAD_Org_ID());
if (rate == null)
{
p_Error = "Purchase Order not convertible - " + as.getName();
return null;
}
amt = amt.multiply(rate);
if (amt.scale() > as.getCostingPrecision())
amt = amt.setScale(as.getCostingPrecision(), RoundingMode.HALF_UP);
}
int elementId = allocation.getC_OrderLandedCost().getM_CostElement_ID();
BigDecimal elementAmt = landedCostMap.get(elementId);
if (elementAmt == null)
{
elementAmt = amt;
}
else
{
elementAmt = elementAmt.add(amt);
}
landedCostMap.put(elementId, elementAmt);
}
for(Integer elementId : landedCostMap.keySet())
{
BigDecimal amt = landedCostMap.get(elementId);
if (!MCostDetail.createShipment(as, getAD_Org_ID(),
getM_Product_ID(), matchInv.getM_AttributeSetInstance_ID(),
m_receiptLine.getM_InOutLine_ID(), elementId,
amt, tQty, getDescription(), false, getTrxName()))
{
return "Failed to create cost detail record";
}
}
// end MZ
}
return "";
}
/**
* Create Facts for material shipment
* @param as accounting schema
* @return Fact
*/
private ArrayList createMatShipmentFacts(MAcctSchema as) {
ArrayList facts = new ArrayList();
// invoice gain/loss accounting fact line list
ArrayList invGainLossFactLines = new ArrayList();
// invoice list
ArrayList invList = new ArrayList();
// invoice line list
ArrayList invLineList = new ArrayList();
// C_Invoice_ID and the current M_MatchInv inventory clearing/expense accounting fact lines
HashMap> htFactLineInv = new HashMap>();
// receipt gain/loss accounting fact line list
ArrayList mrGainLossFactLines = new ArrayList();
// NIR accounting fact line list
ArrayList mrFactLines = new ArrayList();
// create Fact Header
Fact fact = new Fact(this, as, Fact.POST_Actual);
setC_Currency_ID (as.getC_Currency_ID());
boolean isInterOrg = isInterOrg(as);
// NotInvoicedReceipt DR
// From Receipt
BigDecimal multiplier = getQty()
.divide(m_receiptLine.getMovementQty(), 12, RoundingMode.HALF_UP);
multiplier = multiplier.negate();
FactLine dr = fact.createLine (null,
getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as),
as.getC_Currency_ID(), null, Env.ONE); // updated below
if (dr == null)
{
p_Error = "No Product Costs";
return null;
}
dr.setQty(getQty());
BigDecimal temp = dr.getAcctBalance();
// Set AmtAcctCr/Dr from Receipt (sets also Project)
if (m_matchInv.isReversal())
{
if (!dr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
if (!dr.updateReverseLine (MInOut.Table_ID, // Amt updated
m_receiptLine.getM_InOut_ID(), m_receiptLine.getM_InOutLine_ID(),
multiplier))
{
p_Error = "Mat.Shipment not posted yet";
return null;
}
}
if (log.isLoggable(Level.FINE)) log.fine("CR - Amt(" + temp + "->" + dr.getAcctBalance()
+ ") - " + dr.toString());
// InventoryClearing CR
// From Invoice
MAccount expense = m_pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as);
if (m_pc.isService())
expense = m_pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
BigDecimal LineNetAmt = m_invoiceLine.getLineNetAmt();
multiplier = getQty()
.divide(m_invoiceLine.getQtyInvoiced(), 12, RoundingMode.HALF_UP);
multiplier = multiplier.negate();
if (multiplier.compareTo(Env.ONE) != 0)
LineNetAmt = LineNetAmt.multiply(multiplier);
if (m_pc.isService())
LineNetAmt = dr.getAcctBalance(); // book out exact receipt amt
FactLine cr = null;
if (as.isAccrual())
{
cr = fact.createLine (null, expense,
as.getC_Currency_ID(), LineNetAmt, null); // updated below
if (cr == null)
{
if (log.isLoggable(Level.FINE)) log.fine("Line Net Amt=0 - M_Product_ID=" + getM_Product_ID()
+ ",Qty=" + getQty() + ",InOutQty=" + m_receiptLine.getMovementQty());
cr = fact.createLine (null, expense, as.getC_Currency_ID(), Env.ONE, null);
cr.setAmtAcctCr(BigDecimal.ZERO);
cr.setAmtSourceCr(BigDecimal.ZERO);
}
temp = cr.getAcctBalance();
if (m_matchInv.isReversal())
{
if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE, dr))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
cr.setQty(getQty().negate());
// Set AmtAcctCr/Dr from Invoice (sets also Project)
if (!cr.updateReverseLine (MInvoice.Table_ID, // Amt updated
m_invoiceLine.getC_Invoice_ID(), m_invoiceLine.getC_InvoiceLine_ID(), multiplier))
{
p_Error = "Invoice not posted yet";
return null;
}
}
if (log.isLoggable(Level.FINE)) log.fine("DR - Amt(" + temp + "->" + cr.getAcctBalance()
+ ") - " + cr.toString());
}
else // Cash Acct
{
MInvoice invoice = m_invoiceLine.getParent();
if (as.getC_Currency_ID() != invoice.getC_Currency_ID())
LineNetAmt = MConversionRate.convert(getCtx(), LineNetAmt,
invoice.getC_Currency_ID(), as.getC_Currency_ID(),
invoice.getDateAcct(), invoice.getC_ConversionType_ID(),
invoice.getAD_Client_ID(), invoice.getAD_Org_ID());
cr = fact.createLine (null, expense,
as.getC_Currency_ID(), LineNetAmt, null);
if (m_matchInv.isReversal())
{
if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE, dr))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
int precision = MUOM.getPrecision(getCtx(), m_invoiceLine.getC_UOM_ID());
cr.setQty(getQty().multiply(multiplier).negate().setScale(precision, RoundingMode.HALF_UP));
}
}
// gain/loss + rounding adjustment
if (m_receiptLine != null && m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
mrFactLines.add(dr);
p_Error = createReceiptGainLoss(as, fact, getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as), m_receiptLine.getParent(), dr.getAmtSourceCr(), dr.getAmtAcctCr(), mrGainLossFactLines, mrFactLines);
if (p_Error != null)
return null;
}
// rounding adjustment
if (!mrFactLines.isEmpty())
{
p_Error = createReceiptRoundingCorrection(as, fact, getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as), mrGainLossFactLines, mrFactLines);
if (p_Error != null)
return null;
}
// gain/loss
if (m_invoiceLine != null && m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
MInvoice invoice = m_invoiceLine.getParent();
if (!invList.contains(invoice))
invList.add(invoice);
if (!invLineList.contains(m_invoiceLine))
invLineList.add(m_invoiceLine);
ArrayList factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList();
factLineList.add(cr);
htFactLineInv.put(invoice.get_ID(), factLineList);
p_Error = createInvoiceGainLoss(as, fact, expense, invoice, cr.getAmtSourceDr(), cr.getAmtAcctDr(), invGainLossFactLines, htFactLineInv);
if (p_Error != null)
return null;
}
// rounding adjustment
if (!htFactLineInv.isEmpty())
{
p_Error = createInvoiceRoundingCorrection(as, fact, expense, invGainLossFactLines, invList, invLineList, htFactLineInv);
if (p_Error != null)
return null;
}
if (!m_matchInv.isReversal())
{
cr.setC_Activity_ID(m_invoiceLine.getC_Activity_ID());
cr.setC_Campaign_ID(m_invoiceLine.getC_Campaign_ID());
cr.setC_Project_ID(m_invoiceLine.getC_Project_ID());
cr.setC_ProjectPhase_ID(m_invoiceLine.getC_ProjectPhase_ID());
cr.setC_ProjectTask_ID(m_invoiceLine.getC_ProjectTask_ID());
cr.setC_UOM_ID(m_invoiceLine.getC_UOM_ID());
cr.setUser1_ID(m_invoiceLine.getUser1_ID());
cr.setUser2_ID(m_invoiceLine.getUser2_ID());
}
else
{
updateFactLine(cr);
}
//AZ Goodwill
//Desc: Source Not Balanced problem because Currency is Difference - PO=CNY but AP=USD
//see also Fact.java: checking for isMultiCurrency()
if (dr.getC_Currency_ID() != cr.getC_Currency_ID())
setIsMultiCurrency(true);
//end AZ
// Avoid usage of clearing accounts
// If both accounts Not Invoiced Receipts and Inventory Clearing are equal
// then remove the posting
MAccount acct_db = dr.getAccount(); // not_invoiced_receipts
MAccount acct_cr = cr.getAccount(); // inventory_clearing
if ((!as.isPostIfClearingEqual()) && acct_db.equals(acct_cr) && (!isInterOrg)) {
BigDecimal debit = dr.getAmtSourceDr();
BigDecimal credit = cr.getAmtSourceCr();
if (debit.compareTo(credit) == 0) {
fact.remove(dr);
fact.remove(cr);
}
}
// End Avoid usage of clearing accounts
// Invoice Price Variance difference
BigDecimal ipv = cr.getAcctBalance().add(dr.getAcctBalance()).negate();
BigDecimal ipvSource = dr.getAmtSourceDr().subtract(cr.getAmtSourceCr()).negate();
processInvoicePriceVariance(as, fact, ipv, ipvSource);
if (log.isLoggable(Level.FINE)) log.fine("IPV=" + ipv + "; Balance=" + fact.getSourceBalance());
String error = createMatchInvCostDetail(as);
if (error != null && error.trim().length() > 0)
{
p_Error = error;
return null;
}
//
facts.add(fact);
/** Commitment release ****/
if (as.isAccrual() && as.isCreatePOCommitment())
{
fact = Doc_Order.getCommitmentRelease(as, this,
getQty(), m_invoiceLine.getC_InvoiceLine_ID(), Env.ONE);
if (fact == null)
return null;
facts.add(fact);
} // Commitment
return facts;
}
/**
* Create Facts for credit memo
* @param as accounting schema
* @return Fact
*/
public ArrayList createCreditMemoFacts(MAcctSchema as) {
ArrayList facts = new ArrayList();
// invoice gain/loss accounting fact line list
ArrayList invGainLossFactLines = new ArrayList();
// invoice list
ArrayList invList = new ArrayList();
// invoice line list
ArrayList invLineList = new ArrayList();
// C_Invoice_ID and the current M_MatchInv inventory clearing/expense accounting fact lines
HashMap> htFactLineInv = new HashMap>();
// create Fact Header
Fact fact = new Fact(this, as, Fact.POST_Actual);
setC_Currency_ID (as.getC_Currency_ID());
MMatchInv refMatchInv = new MMatchInv(getCtx(), m_matchInv.getRef_MatchInv_ID(), getTrxName());
MInvoiceLine refInvLine = new MInvoiceLine(getCtx(), refMatchInv.getC_InvoiceLine_ID(), getTrxName());
boolean isInterOrg = false;
MAcctSchemaElement elementorg = as.getAcctSchemaElement(MAcctSchemaElement.ELEMENTTYPE_Organization);
if (elementorg == null || !elementorg.isBalanced()) {
// no org element or not need to be balanced
isInterOrg = false;
}
// verify if org of receipt line is different from org of invoice line
if (refInvLine != null && m_invoiceLine != null && refInvLine.getAD_Org_ID() != m_invoiceLine.getAD_Org_ID())
isInterOrg = true;
MAccount expense = m_pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as);
if (m_pc.isService())
expense = m_pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
BigDecimal LineNetAmt = refInvLine.getLineNetAmt();
BigDecimal multiplier = getQty()
.divide(refInvLine.getQtyInvoiced(), 12, RoundingMode.HALF_UP);
multiplier = multiplier.negate();
if (multiplier.compareTo(Env.ONE) != 0)
LineNetAmt = LineNetAmt.multiply(multiplier);
FactLine dr = null;
if (as.isAccrual())
{
dr = fact.createLine (null, expense,
as.getC_Currency_ID(), LineNetAmt, null); // updated below
if (dr == null)
{
if (log.isLoggable(Level.FINE)) log.fine("Line Net Amt=0 - M_Product_ID=" + getM_Product_ID()
+ ",Qty=" + getQty() + ",InOutQty=" + m_receiptLine.getMovementQty());
dr = fact.createLine (null, expense, as.getC_Currency_ID(), Env.ONE, null);
dr.setAmtAcctCr(BigDecimal.ZERO);
dr.setAmtSourceCr(BigDecimal.ZERO);
}
BigDecimal temp = dr.getAcctBalance();
if (m_matchInv.isReversal())
{
if (!dr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
dr.setQty(getQty().negate());
// Set AmtAcctCr/Dr from Invoice (sets also Project)
if (!dr.updateReverseLine (MInvoice.Table_ID, // Amt updated
refInvLine.getC_Invoice_ID(), refInvLine.getC_InvoiceLine_ID(), multiplier))
{
p_Error = "Invoice not posted yet";
return null;
}
}
if (log.isLoggable(Level.FINE)) log.fine("DR - Amt(" + temp + "->" + dr.getAcctBalance()
+ ") - " + dr.toString());
}
else // Cash Acct
{
MInvoice invoice = refInvLine.getParent();
if (as.getC_Currency_ID() != invoice.getC_Currency_ID())
LineNetAmt = MConversionRate.convert(getCtx(), LineNetAmt,
invoice.getC_Currency_ID(), as.getC_Currency_ID(),
invoice.getDateAcct(), invoice.getC_ConversionType_ID(),
invoice.getAD_Client_ID(), invoice.getAD_Org_ID());
dr = fact.createLine (null, expense,
as.getC_Currency_ID(), LineNetAmt, null);
if (m_matchInv.isReversal())
{
if (!dr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
int precision = MUOM.getPrecision(getCtx(), m_invoiceLine.getC_UOM_ID());
dr.setQty(getQty().multiply(multiplier).negate().setScale(precision, RoundingMode.HALF_UP));
}
}
if (!m_matchInv.isReversal())
{
dr.setC_Activity_ID(refInvLine.getC_Activity_ID());
dr.setC_Campaign_ID(refInvLine.getC_Campaign_ID());
dr.setC_Project_ID(refInvLine.getC_Project_ID());
dr.setC_ProjectPhase_ID(refInvLine.getC_ProjectPhase_ID());
dr.setC_ProjectTask_ID(refInvLine.getC_ProjectTask_ID());
dr.setC_UOM_ID(refInvLine.getC_UOM_ID());
dr.setUser1_ID(refInvLine.getUser1_ID());
dr.setUser2_ID(refInvLine.getUser2_ID());
}
else
{
updateFactLine(dr);
}
// InventoryClearing CR
// From Invoice
LineNetAmt = m_invoiceLine.getLineNetAmt();
multiplier = getQty()
.divide(m_invoiceLine.getQtyInvoiced(), 12, RoundingMode.HALF_UP);
multiplier = multiplier.negate();
if (multiplier.compareTo(Env.ONE) != 0)
LineNetAmt = LineNetAmt.multiply(multiplier);
if (m_pc.isService())
LineNetAmt = dr.getAcctBalance(); // book out exact receipt amt
FactLine cr = null;
if (as.isAccrual())
{
cr = fact.createLine (null, expense,
as.getC_Currency_ID(), LineNetAmt, null); // updated below
if (cr == null)
{
if (log.isLoggable(Level.FINE)) log.fine("Line Net Amt=0 - M_Product_ID=" + getM_Product_ID()
+ ",Qty=" + getQty() + ",InOutQty=" + m_receiptLine.getMovementQty());
cr = fact.createLine (null, expense, as.getC_Currency_ID(), Env.ONE, null);
cr.setAmtAcctCr(BigDecimal.ZERO);
cr.setAmtSourceCr(BigDecimal.ZERO);
}
BigDecimal temp = cr.getAcctBalance();
if (m_matchInv.isReversal())
{
if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE, dr))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
cr.setQty(getQty().negate());
// Set AmtAcctCr/Dr from Invoice (sets also Project)
if (!cr.updateReverseLine (MInvoice.Table_ID, // Amt updated
m_invoiceLine.getC_Invoice_ID(), m_invoiceLine.getC_InvoiceLine_ID(), multiplier))
{
p_Error = "Invoice not posted yet";
return null;
}
}
if (log.isLoggable(Level.FINE)) log.fine("DR - Amt(" + temp + "->" + cr.getAcctBalance()
+ ") - " + cr.toString());
}
else // Cash Acct
{
MInvoice invoice = m_invoiceLine.getParent();
if (as.getC_Currency_ID() != invoice.getC_Currency_ID())
LineNetAmt = MConversionRate.convert(getCtx(), LineNetAmt,
invoice.getC_Currency_ID(), as.getC_Currency_ID(),
invoice.getDateAcct(), invoice.getC_ConversionType_ID(),
invoice.getAD_Client_ID(), invoice.getAD_Org_ID());
cr = fact.createLine (null, expense,
as.getC_Currency_ID(), LineNetAmt, null);
if (m_matchInv.isReversal())
{
if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated
m_matchInv.getReversal_ID(), 0, BigDecimal.ONE, dr))
{
p_Error = "Failed to create reversal entry";
return null;
}
}
else
{
int precision = MUOM.getPrecision(getCtx(), m_invoiceLine.getC_UOM_ID());
cr.setQty(getQty().multiply(multiplier).negate().setScale(precision, RoundingMode.HALF_UP));
}
}
// gain / loss
if (refInvLine != null && refInvLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
MInvoice invoice = refInvLine.getParent();
if (!invList.contains(invoice))
invList.add(invoice);
if (!invLineList.contains(refInvLine))
invLineList.add(refInvLine);
ArrayList factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList();
factLineList.add(dr);
htFactLineInv.put(invoice.get_ID(), factLineList);
p_Error = createInvoiceGainLoss(as, fact, expense, invoice, dr.getAmtSourceCr(), dr.getAmtAcctCr(), invGainLossFactLines, htFactLineInv);
if (p_Error != null)
return null;
}
if (m_invoiceLine != null && m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
MInvoice invoice = m_invoiceLine.getParent();
if (!invList.contains(invoice))
invList.add(invoice);
if (!invLineList.contains(m_invoiceLine))
invLineList.add(m_invoiceLine);
ArrayList factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList();
factLineList.add(cr);
htFactLineInv.put(invoice.get_ID(), factLineList);
p_Error = createInvoiceGainLoss(as, fact, expense, invoice, cr.getAmtSourceDr(), cr.getAmtAcctDr(), invGainLossFactLines, htFactLineInv);
if (p_Error != null)
return null;
}
// rounding adjustment
if (!htFactLineInv.isEmpty())
{
p_Error = createInvoiceRoundingCorrection(as, fact, expense, invGainLossFactLines, invList, invLineList, htFactLineInv);
if (p_Error != null)
return null;
}
if (!m_matchInv.isReversal())
{
cr.setC_Activity_ID(m_invoiceLine.getC_Activity_ID());
cr.setC_Campaign_ID(m_invoiceLine.getC_Campaign_ID());
cr.setC_Project_ID(m_invoiceLine.getC_Project_ID());
cr.setC_ProjectPhase_ID(m_invoiceLine.getC_ProjectPhase_ID());
cr.setC_ProjectTask_ID(m_invoiceLine.getC_ProjectTask_ID());
cr.setC_UOM_ID(m_invoiceLine.getC_UOM_ID());
cr.setUser1_ID(m_invoiceLine.getUser1_ID());
cr.setUser2_ID(m_invoiceLine.getUser2_ID());
}
else
{
updateFactLine(cr);
}
//AZ Goodwill
//Desc: Source Not Balanced problem because Currency is Difference - PO=CNY but AP=USD
//see also Fact.java: checking for isMultiCurrency()
if (dr.getC_Currency_ID() != cr.getC_Currency_ID())
setIsMultiCurrency(true);
//end AZ
// Avoid usage of clearing accounts
// If both accounts are equal
// then remove the posting
MAccount acct_db = dr.getAccount(); // inventory_clearing
MAccount acct_cr = cr.getAccount(); // inventory_clearing
if ((!as.isPostIfClearingEqual()) && acct_db.equals(acct_cr) && (!isInterOrg)) {
BigDecimal debit = dr.getAmtSourceDr();
BigDecimal credit = cr.getAmtSourceCr();
if (debit.compareTo(credit) == 0 && (cr.getAcctBalance().add(dr.getAcctBalance())).compareTo(Env.ZERO) == 0) {
fact.remove(dr);
fact.remove(cr);
}
}
// End Avoid usage of clearing accounts
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
BigDecimal ipv = cr.getAcctBalance().add(dr.getAcctBalance()).negate();
if (ipv.compareTo(Env.ZERO) != 0)
{
FactLine pv = fact.createLine(null,
cr.getAcctBalance().abs().compareTo(dr.getAcctBalance().abs()) < 0 ? loss : gain,
as.getC_Currency_ID(), ipv);
updateFactLine(pv);
}
//
facts.add(fact);
/** Commitment release ****/
if (as.isAccrual() && as.isCreatePOCommitment())
{
fact = Doc_Order.getCommitmentRelease(as, this,
getQty(), m_invoiceLine.getC_InvoiceLine_ID(), Env.ONE);
if (fact == null)
return null;
facts.add(fact);
} // Commitment
return facts;
}
/**
* Update fact line with invoice line details (qty and accounting dimension)
* @param factLine
*/
protected void updateFactLine(FactLine factLine) {
factLine.setC_Activity_ID(m_invoiceLine.getC_Activity_ID());
factLine.setC_Campaign_ID(m_invoiceLine.getC_Campaign_ID());
factLine.setC_Project_ID(m_invoiceLine.getC_Project_ID());
factLine.setC_ProjectPhase_ID(m_invoiceLine.getC_ProjectPhase_ID());
factLine.setC_ProjectTask_ID(m_invoiceLine.getC_ProjectTask_ID());
factLine.setC_UOM_ID(m_invoiceLine.getC_UOM_ID());
factLine.setUser1_ID(m_invoiceLine.getUser1_ID());
factLine.setUser2_ID(m_invoiceLine.getUser2_ID());
factLine.setM_Product_ID(m_invoiceLine.getM_Product_ID());
factLine.setQty(getQty());
}
/**
* Invoice currency & acct schema currency are not same then update AmtSource value
* to avoid source not balanced error/ignore suspense balancing.
*
* @param factLine
* @param ipvSource
*/
protected void updateFactLineAmtSource(FactLine factLine, BigDecimal ipvSource)
{
// When only Rate differ then set Dr & Cr Source amount as zero.
factLine.setAmtSourceCr(Env.ZERO);
factLine.setAmtSourceDr(Env.ZERO);
// Price is vary then set Source amount according to source variance
if (ipvSource.compareTo(Env.ZERO) != 0)
{
if (ipvSource.signum() < 0)
factLine.setAmtSourceCr(ipvSource);
else
factLine.setAmtSourceDr(ipvSource);
}
}
/**
* Create Gain/Loss for invoice
* @param as accounting schema
* @param fact
* @param acct
* @param invoice
* @param matchInvSource
* @param matchInvAccounted
* @param invGainLossFactLines gain/loss fact lines for invoice
* @param htFactLineInv C_Invoice_ID and the fact lines
* @return error message or null
*/
private String createInvoiceGainLoss(MAcctSchema as, Fact fact, MAccount acct,
MInvoice invoice, BigDecimal matchInvSource, BigDecimal matchInvAccounted,
ArrayList invGainLossFactLines, HashMap> htFactLineInv)
{
if (m_matchInv.isReversal())
return createReversalInvoiceGainLossRoundingCorrection(as, fact, acct);
BigDecimal invoiceSource = null;
BigDecimal invoiceAccounted = null;
//
StringBuilder sql = new StringBuilder()
.append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID=?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID=?")
.append(" AND PostingType='A'");
// For Invoice
List