IDEMPIERE-4263 Wrong matched invoice GL posting (currency balancing) in alternate schema for vendor credit memo if > 1 credit memo line (#250)

* IDEMPIERE-4263 Wrong matched invoice GL posting (currency balancing) in alternate schema for vendor credit memo if > 1 credit memo line
This commit is contained in:
Elaine Tan 2020-09-18 20:58:14 +08:00 committed by GitHub
parent 599e13dda3
commit 79d6c1e903
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 684 additions and 125 deletions

View File

@ -23,6 +23,7 @@ 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;
@ -75,6 +76,9 @@ public class Doc_MatchInv extends Doc
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 */
@ -142,6 +146,13 @@ public class Doc_MatchInv extends Doc
public ArrayList<Fact> createFacts (MAcctSchema as)
{
ArrayList<Fact> facts = new ArrayList<Fact>();
// invoice gain/loss accounting fact line list
ArrayList<FactLine> invGainLossFactLines = new ArrayList<FactLine>();
// invoice list
ArrayList<MInvoice> invList = new ArrayList<MInvoice>();
// C_Invoice_ID and the current M_MatchInv inventory clearing/expense accounting fact lines
HashMap<Integer, ArrayList<FactLine>> htFactLineInv = new HashMap<Integer, ArrayList<FactLine>>();
// Nothing to do
if (getM_Product_ID() == 0 // no Product
|| getQty().signum() == 0
@ -292,16 +303,32 @@ public class Doc_MatchInv extends Doc
}
}
// Rounding correction
// gain/loss + rounding adjustment
if (m_receiptLine != null && m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
p_Error = createReceiptGainLoss(as, fact, getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as), m_receiptLine.getParent(), dr.getAmtSourceDr(), dr.getAmtAcctDr());
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
{
p_Error = createInvoiceGainLoss(as, fact, expense, m_invoiceLine.getParent(), cr.getAmtSourceCr(), cr.getAmtAcctCr());
MInvoice invoice = m_invoiceLine.getParent();
if (!invList.contains(invoice))
invList.add(invoice);
ArrayList<FactLine> factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList<FactLine>();
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, htFactLineInv);
if (p_Error != null)
return null;
}
@ -587,6 +614,12 @@ public class Doc_MatchInv extends Doc
private ArrayList<Fact> createMatShipmentFacts(MAcctSchema as) {
ArrayList<Fact> facts = new ArrayList<Fact>();
// invoice gain/loss accounting fact line list
ArrayList<FactLine> invGainLossFactLines = new ArrayList<FactLine>();
// invoice list
ArrayList<MInvoice> invList = new ArrayList<MInvoice>();
// C_Invoice_ID and the current M_MatchInv inventory clearing/expense accounting fact lines
HashMap<Integer, ArrayList<FactLine>> htFactLineInv = new HashMap<Integer, ArrayList<FactLine>>();
// create Fact Header
Fact fact = new Fact(this, as, Fact.POST_Actual);
@ -708,16 +741,33 @@ public class Doc_MatchInv extends Doc
}
}
// Rounding correction
// gain/loss + rounding adjustment
if (m_receiptLine != null && m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
p_Error = createReceiptGainLoss(as, fact, getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as), m_receiptLine.getParent(), dr.getAmtSourceCr(), dr.getAmtAcctCr());
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
{
p_Error = createInvoiceGainLoss(as, fact, expense, m_invoiceLine.getParent(), cr.getAmtSourceDr(), cr.getAmtAcctDr());
MInvoice invoice = m_invoiceLine.getParent();
if (!invList.contains(invoice))
invList.add(invoice);
ArrayList<FactLine> factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList<FactLine>();
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, htFactLineInv);
if (p_Error != null)
return null;
}
@ -795,6 +845,12 @@ public class Doc_MatchInv extends Doc
public ArrayList<Fact> createCreditMemoFacts(MAcctSchema as) {
ArrayList<Fact> facts = new ArrayList<Fact>();
// invoice gain/loss accounting fact line list
ArrayList<FactLine> invGainLossFactLines = new ArrayList<FactLine>();
// invoice list
ArrayList<MInvoice> invList = new ArrayList<MInvoice>();
// C_Invoice_ID and the current M_MatchInv inventory clearing/expense accounting fact lines
HashMap<Integer, ArrayList<FactLine>> htFactLineInv = new HashMap<Integer, ArrayList<FactLine>>();
// create Fact Header
Fact fact = new Fact(this, as, Fact.POST_Actual);
@ -975,16 +1031,40 @@ public class Doc_MatchInv extends Doc
}
}
// Rounding correction
// gain / loss
if (refInvLine != null && refInvLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
p_Error = createInvoiceGainLoss(as, fact, expense, refInvLine.getParent(), dr.getAmtSourceCr(), dr.getAmtAcctCr());
MInvoice invoice = refInvLine.getParent();
if (!invList.contains(invoice))
invList.add(invoice);
ArrayList<FactLine> factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList<FactLine>();
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
{
p_Error = createInvoiceGainLoss(as, fact, expense, m_invoiceLine.getParent(), cr.getAmtSourceDr(), cr.getAmtAcctDr());
MInvoice invoice = m_invoiceLine.getParent();
if (!invList.contains(invoice))
invList.add(invoice);
ArrayList<FactLine> factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList<FactLine>();
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, htFactLineInv);
if (p_Error != null)
return null;
}
@ -1024,7 +1104,7 @@ public class Doc_MatchInv extends Doc
BigDecimal debit = dr.getAmtSourceDr();
BigDecimal credit = cr.getAmtSourceCr();
if (debit.compareTo(credit) == 0) {
if (debit.compareTo(credit) == 0 && (cr.getAcctBalance().add(dr.getAcctBalance())).compareTo(Env.ZERO) == 0) {
fact.remove(dr);
fact.remove(cr);
}
@ -1076,7 +1156,8 @@ public class Doc_MatchInv extends Doc
}
private String createInvoiceGainLoss(MAcctSchema as, Fact fact, MAccount acct,
MInvoice invoice, BigDecimal matchInvSource, BigDecimal matchInvAccounted)
MInvoice invoice, BigDecimal matchInvSource, BigDecimal matchInvAccounted,
ArrayList<FactLine> invGainLossFactLines, HashMap<Integer, ArrayList<FactLine>> htFactLineInv)
{
BigDecimal invoiceSource = null;
BigDecimal invoiceAccounted = null;
@ -1105,126 +1186,46 @@ public class Doc_MatchInv extends Doc
if (invoiceSource == null || invoiceAccounted == null)
return null;
//
if (m_matchInv.getReversal_ID() == 0)
{
String matchInvLineSql = "SELECT M_MatchInv_ID FROM M_MatchInv "
+ "WHERE C_InvoiceLine_ID IN (SELECT C_InvoiceLine_ID FROM C_InvoiceLine WHERE C_Invoice_ID=?) "
+ "AND COALESCE(Reversal_ID,0)=0";
List<List<Object>> list = DB.getSQLArrayObjectsEx(getTrxName(), matchInvLineSql, invoice.get_ID());
StringBuilder s = new StringBuilder();
if (list == null)
return null;
for (int index=0; index < list.size(); index++)
{
List<Object> l = list.get(index);
s.append(l.get(0));
if (index != list.size()-1)
s.append(",");
}
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 IN (").append(s).append(")")
.append(" AND Record_ID <> ?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID=?")
.append(" AND PostingType='A'");
}
else
{
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 IN (").append(m_matchInv.getReversal_ID()).append(")")
.append(" AND Record_ID <> ?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID=?")
.append(" AND PostingType='A'");
}
BigDecimal acctDifference = null; // gain is negative
// For Match Invoice
valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MMatchInv.Table_ID, get_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesInv != null)
{
BigDecimal totalAmtSourceDr = (BigDecimal) valuesInv.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtAcctDr = (BigDecimal) valuesInv.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
BigDecimal totalAmtSourceCr = (BigDecimal) valuesInv.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
BigDecimal totalAmtAcctCr = (BigDecimal) valuesInv.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (m_matchInv.getReversal_ID() == 0)
{
if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
}
else if (totalAmtSourceCr.signum() == 0 && totalAmtAcctCr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
}
else if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr);
}
else
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr).subtract(totalAmtAcctDr);
}
}
else
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
acctDifference = totalAmtAcctCr;
}
else
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
acctDifference = totalAmtAcctDr.negate();
}
}
}
StringBuilder description = new StringBuilder("Invoice=(").append(invoice.getC_Currency_ID()).append(")").append(invoiceSource).append("/").append(invoiceAccounted)
.append(" - MatchInv=(").append(getC_Currency_ID()).append(")").append(matchInvSource).append("/").append(matchInvAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
//
// Full Payment in currency
if (acctDifference == null && matchInvSource.compareTo(invoiceSource) == 0)
BigDecimal acctDifference = null;
// Full MR in currency
if (matchInvSource.compareTo(invoiceSource) == 0)
{
acctDifference = matchInvAccounted.subtract(invoiceAccounted.abs()); // gain is negative
acctDifference = matchInvAccounted.abs().subtract(invoiceAccounted.abs()); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
}
else // partial or MC
{
BigDecimal matchInvAccounted0 = MConversionRate.convert(getCtx(),
matchInvSource, invoice.getC_Currency_ID(),
as.getC_Currency_ID(), invoice.getDateAcct(),
invoice.getC_ConversionType_ID(), invoice.getAD_Client_ID(), invoice.getAD_Org_ID());
acctDifference = matchInvAccounted0.abs().subtract(matchInvAccounted.abs());
// ignore Tolerance
if (acctDifference.abs().compareTo(TOLERANCE) < 0)
acctDifference = Env.ZERO;
// Round
int precision = as.getStdPrecision();
if (acctDifference.scale() > precision)
acctDifference = acctDifference.setScale(precision, RoundingMode.HALF_UP);
StringBuilder d2 = new StringBuilder("(partial) = ").append(acctDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
}
if (acctDifference == null || acctDifference.signum() == 0)
if (acctDifference.signum() == 0)
{
log.fine("No Difference");
return null;
}
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
//
@ -1233,32 +1234,409 @@ public class Doc_MatchInv extends Doc
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
updateFactLine(fl);
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != invoice.getC_Currency_ID()) {
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference);
} else {
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference);
}
ArrayList<FactLine> factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList<FactLine>();
factLineList.add(fl);
htFactLineInv.put(invoice.get_ID(), factLineList);
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
updateFactLine(fl);
invGainLossFactLines.add(fl);
}
else
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
updateFactLine(fl);
ArrayList<FactLine> factLineList = htFactLineInv.get(invoice.get_ID());
if (factLineList == null)
factLineList = new ArrayList<FactLine>();
factLineList.add(fl);
htFactLineInv.put(invoice.get_ID(), factLineList);
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != invoice.getC_Currency_ID()) {
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference.negate());
} else {
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
}
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
updateFactLine(fl);
invGainLossFactLines.add(fl);
}
return null;
} // createInvoiceGainLoss
private String createInvoiceRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct,
ArrayList<FactLine> invGainLossFactLines, ArrayList<MInvoice> invList, HashMap<Integer, ArrayList<FactLine>> htFactLineInv)
{
// C_Invoice_ID and the total source amount from C_Invoice accounting fact lines
HashMap<Integer, BigDecimal> htInvSource = new HashMap<Integer, BigDecimal>();
// C_Invoice_ID and the total accounted amount from C_Invoice accounting fact lines
HashMap<Integer, BigDecimal> htInvAccounted = new HashMap<Integer, BigDecimal>();
for (MInvoice invoice : invList)
{
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<Object> valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MInvoice.Table_ID, invoice.getC_Invoice_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesInv != null) {
BigDecimal invoiceSource = (BigDecimal) valuesInv.get(0); // AmtSourceDr
BigDecimal invoiceAccounted = (BigDecimal) valuesInv.get(1); // AmtAcctDr
if (invoiceSource.signum() == 0 && invoiceAccounted.signum() == 0) {
invoiceSource = (BigDecimal) valuesInv.get(2); // AmtSourceCr
invoiceAccounted = (BigDecimal) valuesInv.get(3); // AmtAcctCr
}
htInvSource.put(invoice.getC_Invoice_ID(), invoiceSource);
htInvAccounted.put(invoice.getC_Invoice_ID(), invoiceAccounted);
}
}
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
// C_Invoice_ID and the total source DR amount from the current M_MatchInv accounting fact lines
HashMap<Integer, BigDecimal> htTotalAmtSourceDr = new HashMap<Integer, BigDecimal>();
// C_Invoice_ID and the total accounted DR amount from the current M_MatchInv accounting fact lines
HashMap<Integer, BigDecimal> htTotalAmtAcctDr = new HashMap<Integer, BigDecimal>();
// C_Invoice_ID and the total source CR amount from the current M_MatchInv accounting fact lines
HashMap<Integer, BigDecimal> htTotalAmtSourceCr = new HashMap<Integer, BigDecimal>();
// C_Invoice_ID and the total accounted CR amount from the current M_MatchInv accounting fact lines
HashMap<Integer, BigDecimal> htTotalAmtAcctCr = new HashMap<Integer, BigDecimal>();
for (Integer C_Invoice_ID : htFactLineInv.keySet())
{
ArrayList<FactLine> factLineList = htFactLineInv.get(C_Invoice_ID);
for (FactLine factLine : factLineList)
{
if (factLine.getAccount_ID() == acct.getAccount_ID())
{
BigDecimal totalAmtSourceDr = htTotalAmtSourceDr.get(C_Invoice_ID);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtAcctDr = htTotalAmtAcctDr.get(C_Invoice_ID);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
BigDecimal totalAmtSourceCr = htTotalAmtSourceCr.get(C_Invoice_ID);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
BigDecimal totalAmtAcctCr = htTotalAmtAcctCr.get(C_Invoice_ID);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
totalAmtSourceDr = totalAmtSourceDr.add(factLine.getAmtSourceDr());
totalAmtAcctDr = totalAmtAcctDr.add(factLine.getAmtAcctDr());
totalAmtSourceCr = totalAmtSourceCr.add(factLine.getAmtSourceCr());
totalAmtAcctCr = totalAmtAcctCr.add(factLine.getAmtAcctCr());
htTotalAmtSourceDr.put(C_Invoice_ID, totalAmtSourceDr);
htTotalAmtAcctDr.put(C_Invoice_ID, totalAmtAcctDr);
htTotalAmtSourceCr.put(C_Invoice_ID, totalAmtSourceCr);
htTotalAmtAcctCr.put(C_Invoice_ID, totalAmtAcctCr);
}
else if (factLine.getAccount_ID() == gain.getAccount_ID() || factLine.getAccount_ID() == loss.getAccount_ID())
{
if (!invGainLossFactLines.contains(factLine))
continue;
BigDecimal totalAmtSourceDr = htTotalAmtSourceDr.get(C_Invoice_ID);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtSourceCr = htTotalAmtSourceCr.get(C_Invoice_ID);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtSourceDr = totalAmtSourceDr.subtract(factLine.getAmtSourceCr());
totalAmtSourceCr = totalAmtSourceCr.subtract(factLine.getAmtSourceDr());
htTotalAmtSourceDr.put(C_Invoice_ID, totalAmtSourceDr);
htTotalAmtSourceCr.put(C_Invoice_ID, totalAmtSourceCr);
}
}
}
// C_Invoice_ID and the total source amount from M_MatchInv accounting fact lines
HashMap<Integer, BigDecimal> htMatchInvSource = new HashMap<Integer, BigDecimal>();
// C_Invoice_ID and the total accounted amount from M_MatchInv accounting fact lines
HashMap<Integer, BigDecimal> htMatchInvAccounted = new HashMap<Integer, BigDecimal>();
// C_Invoice_ID and the total source amount from M_MatchInv accounting fact lines
HashMap<Integer, BigDecimal> htMatchInvAcctDiff = new HashMap<Integer, BigDecimal>();
for (MInvoice invoice : invList)
{
BigDecimal matchInvSource = Env.ZERO;
BigDecimal matchInvAccounted = Env.ZERO;
BigDecimal totalAmtSourceDr = htTotalAmtSourceDr.get(invoice.getC_Invoice_ID());
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtAcctDr = htTotalAmtAcctDr.get(invoice.getC_Invoice_ID());
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
BigDecimal totalAmtSourceCr = htTotalAmtSourceCr.get(invoice.getC_Invoice_ID());
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
BigDecimal totalAmtAcctCr = htTotalAmtAcctCr.get(invoice.getC_Invoice_ID());
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
}
else if (totalAmtSourceCr.signum() == 0 && totalAmtAcctCr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
}
else
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr);
}
else
{
matchInvSource = matchInvSource.add(totalAmtSourceCr).subtract(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr).subtract(totalAmtAcctDr);
}
}
if (m_matchInv.getReversal_ID() == 0)
{
MMatchInv[] matchInvs = MMatchInv.getInvoice(getCtx(), invoice.get_ID(), getTrxName());
ArrayList<Integer> skipMatchInvIdList = new ArrayList<Integer>();
skipMatchInvIdList.add(m_matchInv.get_ID());
for (MMatchInv matchInv : matchInvs)
{
if (matchInv.getReversal_ID() > 0)
skipMatchInvIdList.add(matchInv.get_ID());
}
for (MMatchInv matchInv : matchInvs)
{
if (matchInv.get_ID() == m_matchInv.get_ID())
continue;
if (skipMatchInvIdList.contains(matchInv.get_ID()))
continue;
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=? OR Record_ID=?)") // match inv
.append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'")
.append(" AND Account_ID=?");
if (matchInv.getRef_MatchInv_ID() > 0)
{
if (invoice.isCreditMemo() && matchInv.getQty().compareTo(BigDecimal.ZERO) < 0)
sql.append(" AND Qty > 0");
else
sql.append(" AND Qty < 0");
}
// For Match Inv
List<Object> valuesMatchInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MMatchInv.Table_ID, matchInv.get_ID(), matchInv.getRef_MatchInv_ID() > 0 ? matchInv.getRef_MatchInv_ID() : -1, as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesMatchInv != null) {
totalAmtSourceDr = (BigDecimal) valuesMatchInv.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
totalAmtAcctDr = (BigDecimal) valuesMatchInv.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
totalAmtSourceCr = (BigDecimal) valuesMatchInv.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtAcctCr = (BigDecimal) valuesMatchInv.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
}
else if (totalAmtSourceCr.signum() == 0 && totalAmtAcctCr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
}
else
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
}
else
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
}
}
}
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=? OR Record_ID=?)") // match inv
.append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'")
.append(" AND (Account_ID=? OR Account_ID=? OR Account_ID=?)")
.append(" AND Description LIKE 'Invoice%'");
// For Match Inv
valuesMatchInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MMatchInv.Table_ID, matchInv.get_ID(), matchInv.getRef_MatchInv_ID() > 0 ? matchInv.getRef_MatchInv_ID() : -1, as.getC_AcctSchema_ID(),
gain.getAccount_ID(), loss.getAccount_ID(), as.getCurrencyBalancing_Acct().getAccount_ID());
if (valuesMatchInv != null) {
totalAmtSourceDr = (BigDecimal) valuesMatchInv.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
totalAmtAcctDr = (BigDecimal) valuesMatchInv.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
totalAmtSourceCr = (BigDecimal) valuesMatchInv.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtAcctCr = (BigDecimal) valuesMatchInv.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
matchInvAccounted = matchInvAccounted.subtract(totalAmtAcctDr).subtract(totalAmtAcctCr);
}
}
htMatchInvSource.put(invoice.getC_Invoice_ID(), matchInvSource);
htMatchInvAccounted.put(invoice.getC_Invoice_ID(), matchInvAccounted);
}
else
{
BigDecimal acctDifference = Env.ZERO;
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 IN (").append(m_matchInv.getReversal_ID()).append(")")
.append(" AND Record_ID <> ?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'")
.append(" AND Account_ID=?");
// For Match Inv
List<Object> valuesMatchInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MMatchInv.Table_ID, get_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesMatchInv != null) {
totalAmtSourceDr = (BigDecimal) valuesMatchInv.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
totalAmtAcctDr = (BigDecimal) valuesMatchInv.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
totalAmtSourceCr = (BigDecimal) valuesMatchInv.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtAcctCr = (BigDecimal) valuesMatchInv.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
acctDifference = totalAmtAcctCr.negate();
}
else
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
acctDifference = totalAmtAcctDr;
}
}
htMatchInvSource.put(invoice.getC_Invoice_ID(), matchInvSource);
htMatchInvAccounted.put(invoice.getC_Invoice_ID(), matchInvAccounted);
htMatchInvAcctDiff.put(invoice.getC_Invoice_ID(), acctDifference);
}
}
for (MInvoice invoice : invList)
{
BigDecimal invSource = htInvSource.get(invoice.getC_Invoice_ID());
if (invSource == null)
invSource = Env.ZERO;
BigDecimal invAccounted = htInvAccounted.get(invoice.getC_Invoice_ID());
if (invAccounted == null)
invAccounted = Env.ZERO;
BigDecimal matchInvSource = htMatchInvSource.get(invoice.getC_Invoice_ID());
if (matchInvSource == null)
matchInvSource = Env.ZERO;
BigDecimal matchInvAccounted = htMatchInvAccounted.get(invoice.getC_Invoice_ID());
if (matchInvAccounted == null)
matchInvAccounted = Env.ZERO;
BigDecimal acctDifference = htMatchInvAcctDiff.get(invoice.getC_Invoice_ID());
StringBuilder description = new StringBuilder("Invoice=(").append(getC_Currency_ID()).append(")").append(invSource).append("/").append(invAccounted)
.append(" - Match Invoice=(").append(getC_Currency_ID()).append(")").append(matchInvSource).append("/").append(matchInvAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
if (acctDifference == null && matchInvSource.abs().compareTo(invSource.abs()) == 0)
{
acctDifference = invAccounted.abs().subtract(matchInvAccounted.abs()); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
}
if (acctDifference == null || acctDifference.signum() == 0)
{
log.fine("No Difference");
continue;
}
if (acctDifference.abs().compareTo(TOLERANCE) > 0)
{
log.fine("acctDifference="+acctDifference);
continue;
}
//
if (invoice.isSOTrx())
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
updateFactLine(fl);
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != invoice.getC_Currency_ID())
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference.negate());
else
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
updateFactLine(fl);
}
else
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
updateFactLine(fl);
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != invoice.getC_Currency_ID())
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference);
else
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
updateFactLine(fl);
}
}
return null;
}
private String createReceiptGainLoss(MAcctSchema as, Fact fact, MAccount acct,
MInOut receipt, BigDecimal matchInvSource, BigDecimal matchInvAccounted)
{

View File

@ -339,4 +339,185 @@ public class MatchInvTest extends AbstractTestCase {
rollback();
}
@Test
/**
* Test the matched invoice posting for credit memo
* PO Qty=10 > IV Qty=10 > MR Qty=9 > CM Qty=1
*/
public void testCreditMemoPosting() {
MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc.
MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
order.setBPartner(bpartner);
order.setIsSOTrx(false);
order.setC_DocTypeTarget_ID();
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
order.saveEx();
MOrderLine orderLine = new MOrderLine(order);
orderLine.setLine(10);
orderLine.setProduct(product);
orderLine.setQty(BigDecimal.TEN);
orderLine.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
order.load(getTrxName());
assertFalse(info.isError());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
MInvoice invoice = new MInvoice(Env.getCtx(), 0, getTrxName());
invoice.setOrder(order);
invoice.setDateAcct(order.getDateOrdered());
invoice.setSalesRep_ID(order.getSalesRep_ID());
invoice.setC_BPartner_ID(order.getBill_BPartner_ID());
invoice.setC_BPartner_Location_ID(order.getBill_Location_ID());
invoice.setAD_User_ID(order.getBill_User_ID());
invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice);
invoice.setDocStatus(DocAction.STATUS_Drafted);
invoice.setDocAction(DocAction.ACTION_Complete);
invoice.saveEx();
MInvoiceLine invoiceLine = new MInvoiceLine(invoice);
invoiceLine.setC_OrderLine_ID(orderLine.get_ID());
invoiceLine.setLine(10);
invoiceLine.setProduct(product);
invoiceLine.setQty(BigDecimal.TEN);
invoiceLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete);
invoice.load(getTrxName());
assertFalse(info.isError());
assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus());
if (!invoice.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName());
assertTrue(error == null);
}
invoice.load(getTrxName());
assertTrue(invoice.isPosted());
MInOut receipt = new MInOut(order, 122, order.getDateOrdered()); // MM Receipt
receipt.saveEx();
MInOutLine receiptLine = new MInOutLine(receipt);
receiptLine.setC_OrderLine_ID(orderLine.get_ID());
receiptLine.setLine(10);
receiptLine.setProduct(product);
receiptLine.setQty(new BigDecimal(9));
MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID());
int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID();
receiptLine.setM_Locator_ID(M_Locator_ID);
receiptLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
receipt.load(getTrxName());
assertFalse(info.isError());
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
if (!receipt.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName());
assertTrue(error == null);
}
receipt.load(getTrxName());
assertTrue(receipt.isPosted());
int C_AcctSchema_ID = MClientInfo.get(Env.getCtx()).getC_AcctSchema1_ID();
MAcctSchema as = MAcctSchema.get(Env.getCtx(), C_AcctSchema_ID);
MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName());
for (MMatchInv mi : miList) {
if (!mi.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), mi.getAD_Client_ID(), MMatchInv.Table_ID, mi.get_ID(), false, getTrxName());
assertTrue(error == null);
}
mi.load(getTrxName());
assertTrue(mi.isPosted());
Doc doc = DocManager.getDocument(as, MMatchInv.Table_ID, mi.get_ID(), getTrxName());
doc.setC_BPartner_ID(mi.getC_InvoiceLine().getC_Invoice().getC_BPartner_ID());
MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as);
ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName());
MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as);
String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID()
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + C_AcctSchema_ID;
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName());
for (int id : ids) {
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
if (fa.getAccount_ID() == acctNIR.getAccount_ID())
assertTrue(fa.getAmtAcctDr().compareTo(Env.ZERO) >= 0);
else if (fa.getAccount_ID() == acctInvClr.getAccount_ID())
assertTrue(fa.getAmtAcctCr().compareTo(Env.ZERO) >= 0);
}
}
MInvoice creditMemo = new MInvoice(Env.getCtx(), 0, getTrxName());
creditMemo.setOrder(order);
creditMemo.setDateAcct(order.getDateOrdered());
creditMemo.setSalesRep_ID(order.getSalesRep_ID());
creditMemo.setC_BPartner_ID(order.getBill_BPartner_ID());
creditMemo.setC_BPartner_Location_ID(order.getBill_Location_ID());
creditMemo.setAD_User_ID(order.getBill_User_ID());
creditMemo.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APCreditMemo);
creditMemo.setDocStatus(DocAction.STATUS_Drafted);
creditMemo.setDocAction(DocAction.ACTION_Complete);
creditMemo.saveEx();
MInvoiceLine creditMemoLine = new MInvoiceLine(creditMemo);
creditMemoLine.setC_OrderLine_ID(orderLine.get_ID());
creditMemoLine.setLine(10);
creditMemoLine.setProduct(product);
creditMemoLine.setQty(BigDecimal.ONE);
creditMemoLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(creditMemo, DocAction.ACTION_Complete);
creditMemo.load(getTrxName());
assertFalse(info.isError());
assertEquals(DocAction.STATUS_Completed, creditMemo.getDocStatus());
if (!creditMemo.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), creditMemo.getAD_Client_ID(), MInvoice.Table_ID, creditMemo.get_ID(), false, getTrxName());
assertTrue(error == null);
}
creditMemo.load(getTrxName());
assertTrue(creditMemo.isPosted());
miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName());
for (MMatchInv mi : miList) {
if (!mi.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), mi.getAD_Client_ID(), MMatchInv.Table_ID, mi.get_ID(), false, getTrxName());
assertTrue(error == null);
}
mi.load(getTrxName());
assertTrue(mi.isPosted());
ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName());
MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as);
BigDecimal amtAcctDrInvClr = BigDecimal.ZERO;
BigDecimal amtAcctCrInvClr = BigDecimal.ZERO;
String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID()
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + C_AcctSchema_ID;
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName());
for (int id : ids) {
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
if (fa.getAccount_ID() == acctInvClr.getAccount_ID() && fa.getQty().compareTo(BigDecimal.ZERO) < 0) {
assertTrue(fa.getAmtAcctCr().compareTo(Env.ZERO) >= 0);
amtAcctCrInvClr = amtAcctCrInvClr.add(fa.getAmtAcctCr());
}
else if (fa.getAccount_ID() == acctInvClr.getAccount_ID() && fa.getQty().compareTo(BigDecimal.ZERO) > 0) {
assertTrue(fa.getAmtAcctDr().compareTo(Env.ZERO) >= 0);
amtAcctDrInvClr = amtAcctDrInvClr.add(fa.getAmtAcctDr());
}
}
assertTrue(amtAcctDrInvClr.compareTo(amtAcctCrInvClr) == 0);
}
rollback();
}
}