From 78b7dc32d07d3092476f87f94eade5b475c1f7fd Mon Sep 17 00:00:00 2001 From: Tony Snook Date: Sat, 5 Feb 2022 18:47:52 +1100 Subject: [PATCH] IDEMPIERE-5173 fix Accounting fact quantity incorrect (#1150) * IDEMPIERE-5173 fix Accounting fact quantity incorrect - also add unit tests * IDEMPIERE-5173 remove c_uom_id check * IDEMPIERE-5173 Use MMatchInv.isReversal() in preference to MMatch.getReversal_ID() > 0 * IDEMPIERE-5173 Update isReversal() method - add new test for is Reversal() when reversing a credit memo --- .../src/org/compiere/acct/Doc_MatchInv.java | 116 ++++---- .../src/org/compiere/acct/FactLine.java | 11 +- .../src/org/compiere/model/MMatchInv.java | 3 +- .../org/idempiere/test/base/MatchInvTest.java | 250 +++++++++++++++++- 4 files changed, 319 insertions(+), 61 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java b/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java index 2bc041ea3f..74b4a05eec 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java @@ -44,6 +44,7 @@ import org.compiere.model.MInvoice; import org.compiere.model.MInvoiceLine; import org.compiere.model.MMatchInv; import org.compiere.model.MOrderLandedCostAllocation; +import org.compiere.model.MUOM; import org.compiere.model.ProductCost; import org.compiere.model.Query; import org.compiere.model.X_M_Cost; @@ -201,7 +202,7 @@ public class Doc_MatchInv extends Doc dr.setQty(getQty()); BigDecimal temp = dr.getAcctBalance(); // Set AmtAcctCr/Dr from Receipt (sets also Project) - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!dr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -250,7 +251,7 @@ public class Doc_MatchInv extends Doc cr.setAmtSourceCr(BigDecimal.ZERO); } temp = cr.getAcctBalance(); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -284,7 +285,7 @@ public class Doc_MatchInv extends Doc invoice.getAD_Client_ID(), invoice.getAD_Org_ID()); cr = fact.createLine (null, expense, as.getC_Currency_ID(), null, LineNetAmt); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -295,7 +296,8 @@ public class Doc_MatchInv extends Doc } else { - cr.setQty(getQty().multiply(multiplier).negate()); + int precision = MUOM.getPrecision(getCtx(), m_invoiceLine.getC_UOM_ID()); + cr.setQty(getQty().multiply(multiplier).negate().setScale(precision, RoundingMode.HALF_UP)); } } @@ -339,20 +341,17 @@ public class Doc_MatchInv extends Doc return null; } - if (m_matchInv.getReversal_ID() == 0) + 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.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); + cr.setQty(getQty().negate()); } //AZ Goodwill @@ -467,6 +466,7 @@ public class Doc_MatchInv extends Doc m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), as.getC_Currency_ID(), ipv.negate()); updateFactLine(line); + line.setQty(getQty().negate()); line = fact.createLine(null, account, as.getC_Currency_ID(), ipv); updateFactLine(line); @@ -475,6 +475,7 @@ public class Doc_MatchInv extends Doc m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), as.getC_Currency_ID(), ipv.negate()); updateFactLine(line); + line.setQty(getQty().negate()); line = fact.createLine(null, account, as.getC_Currency_ID(), ipv); updateFactLine(line); @@ -663,7 +664,7 @@ public class Doc_MatchInv extends Doc dr.setQty(getQty()); BigDecimal temp = dr.getAcctBalance(); // Set AmtAcctCr/Dr from Receipt (sets also Project) - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!dr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -713,7 +714,7 @@ public class Doc_MatchInv extends Doc cr.setAmtSourceCr(BigDecimal.ZERO); } temp = cr.getAcctBalance(); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -747,7 +748,7 @@ public class Doc_MatchInv extends Doc invoice.getAD_Client_ID(), invoice.getAD_Org_ID()); cr = fact.createLine (null, expense, as.getC_Currency_ID(), LineNetAmt, null); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -758,7 +759,8 @@ public class Doc_MatchInv extends Doc } else { - cr.setQty(getQty().multiply(multiplier).negate()); + int precision = MUOM.getPrecision(getCtx(), m_invoiceLine.getC_UOM_ID()); + cr.setQty(getQty().multiply(multiplier).negate().setScale(precision, RoundingMode.HALF_UP)); } } @@ -803,7 +805,7 @@ public class Doc_MatchInv extends Doc return null; } - if (m_matchInv.getReversal_ID() == 0) + if (!m_matchInv.isReversal()) { cr.setC_Activity_ID(m_invoiceLine.getC_Activity_ID()); cr.setC_Campaign_ID(m_invoiceLine.getC_Campaign_ID()); @@ -931,7 +933,7 @@ public class Doc_MatchInv extends Doc dr.setAmtSourceCr(BigDecimal.ZERO); } BigDecimal temp = dr.getAcctBalance(); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!dr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -965,7 +967,7 @@ public class Doc_MatchInv extends Doc invoice.getAD_Client_ID(), invoice.getAD_Org_ID()); dr = fact.createLine (null, expense, as.getC_Currency_ID(), LineNetAmt, null); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!dr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -976,10 +978,11 @@ public class Doc_MatchInv extends Doc } else { - dr.setQty(getQty().multiply(multiplier).negate()); + int precision = MUOM.getPrecision(getCtx(), m_invoiceLine.getC_UOM_ID()); + dr.setQty(getQty().multiply(multiplier).negate().setScale(precision, RoundingMode.HALF_UP)); } } - if (m_matchInv.getReversal_ID() == 0) + if (!m_matchInv.isReversal()) { dr.setC_Activity_ID(refInvLine.getC_Activity_ID()); dr.setC_Campaign_ID(refInvLine.getC_Campaign_ID()); @@ -1020,7 +1023,7 @@ public class Doc_MatchInv extends Doc cr.setAmtSourceCr(BigDecimal.ZERO); } BigDecimal temp = cr.getAcctBalance(); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -1054,7 +1057,7 @@ public class Doc_MatchInv extends Doc invoice.getAD_Client_ID(), invoice.getAD_Org_ID()); cr = fact.createLine (null, expense, as.getC_Currency_ID(), LineNetAmt, null); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { if (!cr.updateReverseLine (MMatchInv.Table_ID, // Amt updated m_matchInv.getReversal_ID(), 0, BigDecimal.ONE)) @@ -1065,7 +1068,8 @@ public class Doc_MatchInv extends Doc } else { - cr.setQty(getQty().multiply(multiplier).negate()); + int precision = MUOM.getPrecision(getCtx(), m_invoiceLine.getC_UOM_ID()); + cr.setQty(getQty().multiply(multiplier).negate().setScale(precision, RoundingMode.HALF_UP)); } } @@ -1111,7 +1115,7 @@ public class Doc_MatchInv extends Doc return null; } - if (m_matchInv.getReversal_ID() == 0) + if (!m_matchInv.isReversal()) { cr.setC_Activity_ID(m_invoiceLine.getC_Activity_ID()); cr.setC_Campaign_ID(m_invoiceLine.getC_Campaign_ID()); @@ -1213,7 +1217,7 @@ public class Doc_MatchInv extends Doc MInvoice invoice, BigDecimal matchInvSource, BigDecimal matchInvAccounted, ArrayList invGainLossFactLines, HashMap> htFactLineInv) { - if (m_matchInv.getReversal_ID() > 0 && m_matchInv.get_ID() > m_matchInv.getReversal_ID()) + if (m_matchInv.isReversal()) return createReversalInvoiceGainLossRoundingCorrection(as, fact, acct); BigDecimal invoiceSource = null; @@ -1296,7 +1300,7 @@ public class Doc_MatchInv extends Doc */ private String createReversalInvoiceGainLossRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct) { - if (m_matchInv.getReversal_ID() == 0) + if (!m_matchInv.isReversal()) return null; MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); @@ -1339,7 +1343,7 @@ public class Doc_MatchInv extends Doc ArrayList invGainLossFactLines, ArrayList invList, ArrayList invLineList, HashMap> htFactLineInv) { - if (m_matchInv.getReversal_ID() > 0 && m_matchInv.get_ID() > m_matchInv.getReversal_ID()) + if (m_matchInv.isReversal()) return null; HashMap> htRoundingLineInvLine = new HashMap>(); @@ -1523,7 +1527,7 @@ public class Doc_MatchInv extends Doc skipMatchInvIdList.add(m_matchInv.get_ID()); for (MMatchInv matchInv : matchInvs) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) skipMatchInvIdList.add(matchInv.get_ID()); } @@ -1543,9 +1547,9 @@ public class Doc_MatchInv extends Doc .append(" AND PostingType='A'") .append(" AND Account_ID=?"); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); } @@ -1595,9 +1599,9 @@ public class Doc_MatchInv extends Doc .append(" AND (Account_ID=? OR Account_ID=? OR Account_ID=?)") .append(" AND Description LIKE 'Invoice%'"); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); } @@ -1825,7 +1829,7 @@ public class Doc_MatchInv extends Doc skipMatchInvIdList.add(m_matchInv.get_ID()); for (MMatchInv matchInv : matchInvs) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) skipMatchInvIdList.add(matchInv.get_ID()); } @@ -1845,9 +1849,9 @@ public class Doc_MatchInv extends Doc .append(" AND PostingType='A'") .append(" AND Account_ID=?"); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); } @@ -1904,9 +1908,9 @@ public class Doc_MatchInv extends Doc .append(" AND (Account_ID=? OR Account_ID=? OR Account_ID=?)") .append(" AND Description LIKE 'Invoice Line%'"); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); } @@ -2029,7 +2033,7 @@ public class Doc_MatchInv extends Doc MInOut receipt, BigDecimal matchInvSource, BigDecimal matchInvAccounted, ArrayList mrGainLossFactLines, ArrayList mrFactLines) { - if (m_matchInv.getReversal_ID() > 0 && m_matchInv.get_ID() > m_matchInv.getReversal_ID()) + if (m_matchInv.isReversal()) return createReversalReceiptGainLossRoundingCorrection(as, fact, acct); BigDecimal receiptSource = null; @@ -2104,7 +2108,7 @@ public class Doc_MatchInv extends Doc */ private String createReversalReceiptGainLossRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct) { - if (m_matchInv.getReversal_ID() == 0) + if (!m_matchInv.isReversal()) return null; MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); @@ -2144,7 +2148,7 @@ public class Doc_MatchInv extends Doc private String createReceiptRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct, ArrayList mrGainLossFactLines, ArrayList mrFactLines) { - if (m_matchInv.getReversal_ID() > 0 && m_matchInv.get_ID() > m_matchInv.getReversal_ID()) + if (m_matchInv.isReversal()) return null; ArrayList mrLineRoundingLines = new ArrayList(); @@ -2249,7 +2253,7 @@ public class Doc_MatchInv extends Doc skipMatchInvIdList.add(m_matchInv.get_ID()); for (MMatchInv matchInv : matchInvs) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) skipMatchInvIdList.add(matchInv.get_ID()); } @@ -2269,9 +2273,9 @@ public class Doc_MatchInv extends Doc .append(" AND PostingType='A'") .append(" AND Account_ID=?"); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); } @@ -2313,9 +2317,9 @@ public class Doc_MatchInv extends Doc .append(" AND (Account_ID=? OR Account_ID=? OR Account_ID=?)") .append(" AND Description LIKE 'InOut%'"); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); } @@ -2484,7 +2488,7 @@ public class Doc_MatchInv extends Doc skipMatchInvIdList.add(m_matchInv.get_ID()); for (MMatchInv matchInv : matchInvs) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) skipMatchInvIdList.add(matchInv.get_ID()); } @@ -2504,9 +2508,9 @@ public class Doc_MatchInv extends Doc .append(" AND PostingType='A'") .append(" AND Account_ID=?"); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); } @@ -2546,9 +2550,9 @@ public class Doc_MatchInv extends Doc .append(" AND (Account_ID=? OR Account_ID=? OR Account_ID=?)") .append(" AND Description LIKE 'InOut Line%'"); - if (m_matchInv.getReversal_ID() > 0) + if (m_matchInv.isReversal()) { - if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + if (matchInv.isReversal()) sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); } diff --git a/org.adempiere.base/src/org/compiere/acct/FactLine.java b/org.adempiere.base/src/org/compiere/acct/FactLine.java index 1ac2127310..ebcc0bb172 100644 --- a/org.adempiere.base/src/org/compiere/acct/FactLine.java +++ b/org.adempiere.base/src/org/compiere/acct/FactLine.java @@ -33,6 +33,7 @@ import org.compiere.model.MCurrency; import org.compiere.model.MFactAcct; import org.compiere.model.MMovement; import org.compiere.model.MRevenueRecognitionPlan; +import org.compiere.model.MUOM; import org.compiere.model.X_C_AcctSchema_Element; import org.compiere.model.X_Fact_Acct; import org.compiere.util.DB; @@ -1206,8 +1207,14 @@ public final class FactLine extends X_Fact_Acct setC_Tax_ID(fact.getC_Tax_ID()); // Org for cross charge setAD_Org_ID (fact.getAD_Org_ID()); - if (fact.getQty() != null) - setQty(fact.getQty().negate()); + if (fact.getQty() != null) { + if (getC_UOM_ID() != 0) + { + int precision = MUOM.getPrecision(getCtx(), getC_UOM_ID()); + setQty(fact.getQty().multiply(multiplier).negate().setScale(precision, RoundingMode.HALF_UP)); + } else + setQty(fact.getQty().multiply(multiplier).negate().stripTrailingZeros()); + } } else log.warning(new StringBuilder("Not Found (try later) ") diff --git a/org.adempiere.base/src/org/compiere/model/MMatchInv.java b/org.adempiere.base/src/org/compiere/model/MMatchInv.java index 46c7b62e37..fe23bc7669 100644 --- a/org.adempiere.base/src/org/compiere/model/MMatchInv.java +++ b/org.adempiere.base/src/org/compiere/model/MMatchInv.java @@ -407,7 +407,8 @@ public class MMatchInv extends X_M_MatchInv */ public boolean isReversal() { if (getReversal_ID() > 0) { - if (getM_InOutLine().getMovementQty().signum() != getQty().signum()) + MMatchInv reversal = new MMatchInv (getCtx(), getReversal_ID(), get_TrxName()); + if (reversal.getM_MatchInv_ID() < getM_MatchInv_ID()) return true; } return false; diff --git a/org.idempiere.test/src/org/idempiere/test/base/MatchInvTest.java b/org.idempiere.test/src/org/idempiere/test/base/MatchInvTest.java index 33f7d8bd44..1ba3c1c7b3 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/MatchInvTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/MatchInvTest.java @@ -633,10 +633,14 @@ public class MatchInvTest extends AbstractTestCase { 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()) + if (fa.getAccount_ID() == acctNIR.getAccount_ID()) { assertEquals(fa.getAmtAcctDr(), invMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctDr().toPlainString()); - else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) + assertEquals(mi.getQty(), fa.getQty(), "Accounting fact quantity incorrect"); + } + else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) { assertEquals(fa.getAmtAcctCr(), invMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString()); + assertEquals(mi.getQty().negate(), fa.getQty(), "Accounting fact quantity incorrect"); + } } } @@ -695,10 +699,12 @@ public class MatchInvTest extends AbstractTestCase { if (fa.getAccount_ID() == acctInvClr.getAccount_ID() && fa.getQty().compareTo(BigDecimal.ZERO) < 0) { assertEquals(fa.getAmtAcctCr(), credMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString()); amtAcctCrInvClr = amtAcctCrInvClr.add(fa.getAmtAcctCr()); + assertEquals(mi.getQty(), fa.getQty(), "Accounting fact quantity incorrect"); } else if (fa.getAccount_ID() == acctInvClr.getAccount_ID() && fa.getQty().compareTo(BigDecimal.ZERO) > 0) { assertEquals(fa.getAmtAcctDr(), credMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctDr().toPlainString()); amtAcctDrInvClr = amtAcctDrInvClr.add(fa.getAmtAcctDr()); + assertEquals(mi.getQty().negate(), fa.getQty(), "Accounting fact quantity incorrect"); } } assertTrue(amtAcctDrInvClr.compareTo(amtAcctCrInvClr) == 0); @@ -994,4 +1000,244 @@ public class MatchInvTest extends AbstractTestCase { } } } + + @Test + public void testIsReversalCM() { + 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(new BigDecimal("2")); + orderLine.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + order.load(getTrxName()); + assertFalse(info.isError()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + 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(BigDecimal.ONE); + 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()); + + MInvoice invoice = new MInvoice(receipt, receipt.getMovementDate()); + 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.setM_InOutLine_ID(receiptLine.get_ID()); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(new BigDecimal("2")); + 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()); + + MInvoice creditMemo = new MInvoice(receipt, receipt.getMovementDate()); + 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.setM_InOutLine_ID(receiptLine.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()); + + MMatchInv[] beforeList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName()); + assertEquals(1, beforeList.length); + + info = MWorkflow.runDocumentActionWorkflow(creditMemo, DocAction.ACTION_Reverse_Correct); + creditMemo.load(getTrxName()); + assertFalse(info.isError()); + assertEquals(DocAction.STATUS_Reversed, creditMemo.getDocStatus()); + + MMatchInv[] afterList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName()); + assertEquals(2, afterList.length); + beforeList[0].load(getTrxName()); + assertFalse(beforeList[0].isReversal()); + for(MMatchInv mi : afterList) { + if (!mi.equals(beforeList[0])) { + assertTrue(mi.isReversal()); + break; + } + } + } + + @Test + public void testReversalPosting() { + 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.ONE); + orderLine.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + order.load(getTrxName()); + assertFalse(info.isError()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + 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(BigDecimal.ONE); + 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()); + + MInvoice invoice = new MInvoice(receipt, receipt.getMovementDate()); + 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.setM_InOutLine_ID(receiptLine.get_ID()); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(BigDecimal.ONE); + 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); + } + MAcctSchema as = MClient.get(getAD_Client_ID()).getAcctSchema(); + BigDecimal invMatchAmt = invoiceLine.getMatchedQty().multiply(invoiceLine.getPriceActual()).setScale(as.getStdPrecision(), RoundingMode.HALF_UP); + + invoice.load(getTrxName()); + assertTrue(invoice.isPosted()); + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Reverse_Correct); + invoice.load(getTrxName()); + assertFalse(info.isError()); + assertEquals(DocAction.STATUS_Reversed, invoice.getDocStatus()); + + MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName()); + assertEquals(2, miList.length); + 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 + "=" + as.getC_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()) { + if (mi.isReversal()) + assertEquals(fa.getAmtAcctCr(), invMatchAmt, "MatchInv incorrect amount posted "); + else + assertEquals(fa.getAmtAcctDr(), invMatchAmt, "MatchInv incorrect amount posted "); + assertEquals(mi.getQty(), fa.getQty(), "Accounting fact quantity incorrect"); + } + else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) { + if (mi.isReversal()) + assertEquals(fa.getAmtAcctDr(), invMatchAmt, "MatchInv incorrect amount posted "); + else + assertEquals(fa.getAmtAcctCr(), invMatchAmt, "MatchInv incorrect amount posted "); + assertEquals(mi.getQty().negate(), fa.getQty(), "Accounting fact quantity incorrect"); + } + } + } + } }