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 ce93940071..45e7dad882 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java @@ -37,6 +37,7 @@ import org.compiere.model.MAcctSchema; import org.compiere.model.MAcctSchemaElement; import org.compiere.model.MConversionRate; import org.compiere.model.MCostDetail; +import org.compiere.model.MFactAcct; import org.compiere.model.MInOut; import org.compiere.model.MInOutLine; import org.compiere.model.MInvoice; @@ -44,6 +45,7 @@ import org.compiere.model.MInvoiceLine; import org.compiere.model.MMatchInv; import org.compiere.model.MOrderLandedCostAllocation; 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; @@ -150,8 +152,14 @@ public class Doc_MatchInv extends Doc 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 @@ -306,7 +314,15 @@ public class Doc_MatchInv extends Doc // 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()); + 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; } @@ -316,6 +332,8 @@ public class Doc_MatchInv extends Doc 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(); @@ -328,7 +346,7 @@ public class Doc_MatchInv extends Doc // rounding adjustment if (!htFactLineInv.isEmpty()) { - p_Error = createInvoiceRoundingCorrection(as, fact, expense, invGainLossFactLines, invList, htFactLineInv); + p_Error = createInvoiceRoundingCorrection(as, fact, expense, invGainLossFactLines, invList, invLineList, htFactLineInv); if (p_Error != null) return null; } @@ -492,7 +510,11 @@ public class Doc_MatchInv extends Doc return false; } - // Elaine 2008/6/20 + /** + * 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 @@ -612,14 +634,25 @@ public class Doc_MatchInv extends Doc 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); @@ -744,7 +777,15 @@ public class Doc_MatchInv extends Doc // 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()); + 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; } @@ -755,6 +796,8 @@ public class Doc_MatchInv extends Doc 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(); @@ -767,7 +810,7 @@ public class Doc_MatchInv extends Doc // rounding adjustment if (!htFactLineInv.isEmpty()) { - p_Error = createInvoiceRoundingCorrection(as, fact, expense, invGainLossFactLines, invList, htFactLineInv); + p_Error = createInvoiceRoundingCorrection(as, fact, expense, invGainLossFactLines, invList, invLineList, htFactLineInv); if (p_Error != null) return null; } @@ -843,12 +886,19 @@ public class Doc_MatchInv extends Doc 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>(); @@ -1037,6 +1087,8 @@ public class Doc_MatchInv extends Doc 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(); @@ -1051,6 +1103,8 @@ public class Doc_MatchInv extends Doc 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(); @@ -1064,7 +1118,7 @@ public class Doc_MatchInv extends Doc // rounding adjustment if (!htFactLineInv.isEmpty()) { - p_Error = createInvoiceRoundingCorrection(as, fact, expense, invGainLossFactLines, invList, htFactLineInv); + p_Error = createInvoiceRoundingCorrection(as, fact, expense, invGainLossFactLines, invList, invLineList, htFactLineInv); if (p_Error != null) return null; } @@ -1155,10 +1209,25 @@ public class Doc_MatchInv extends Doc factLine.setQty(getQty()); } + /** + * 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.getReversal_ID() > 0 && m_matchInv.get_ID() > m_matchInv.getReversal_ID()) + return createReversalInvoiceGainLossRoundingCorrection(as, fact, acct); + BigDecimal invoiceSource = null; BigDecimal invoiceAccounted = null; // @@ -1201,26 +1270,8 @@ public class Doc_MatchInv extends Doc 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.signum() == 0) + if (acctDifference == null || acctDifference.signum() == 0) { log.fine("No Difference"); return null; @@ -1229,23 +1280,6 @@ public class Doc_MatchInv extends Doc MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct()); // - if (invoice.isSOTrx()) - { - FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate()); - fl.setDescription(description.toString()); - updateFactLine(fl); - ArrayList factLineList = htFactLineInv.get(invoice.get_ID()); - if (factLineList == null) - factLineList = new ArrayList(); - 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()); @@ -1254,46 +1288,134 @@ public class Doc_MatchInv extends Doc if (factLineList == null) factLineList = new ArrayList(); factLineList.add(fl); - htFactLineInv.put(invoice.get_ID(), factLineList); fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference.negate()); fl.setDescription(description.toString()); updateFactLine(fl); invGainLossFactLines.add(fl); + factLineList.add(fl); + htFactLineInv.put(invoice.get_ID(), factLineList); } return null; } // createInvoiceGainLoss - private String createInvoiceRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct, - ArrayList invGainLossFactLines, ArrayList invList, HashMap> htFactLineInv) + /** + * Create Gain/Loss and Rounding Correction for reverse invoice + * @param as accounting schema + * @param fact + * @param acct + * @return error message or null + */ + private String createReversalInvoiceGainLossRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct) { + if (m_matchInv.getReversal_ID() == 0) + return null; + + MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); + MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct()); + + StringBuilder whereClause = new StringBuilder() + .append("AD_Table_ID=?") + .append(" AND Record_ID=?") + .append(" AND C_AcctSchema_ID=?") + .append(" AND PostingType='A'") + .append(" AND (Account_ID=? OR Account_ID=? OR Account_ID=? OR Account_ID=?)") + .append(" AND Description LIKE 'Invoice%'"); + + List list = new Query(getCtx(), MFactAcct.Table_Name, whereClause.toString(), getTrxName()) + .setParameters(MMatchInv.Table_ID, m_matchInv.getReversal_ID(), as.getC_AcctSchema_ID(), + acct.getAccount_ID(), gain.getAccount_ID(), loss.getAccount_ID(), as.getCurrencyBalancing_Acct().getAccount_ID()) + .setOrderBy(MFactAcct.COLUMNNAME_Fact_Acct_ID) + .list(); + for (MFactAcct fa : list) { + FactLine fl = fact.createLine(null, fa.getMAccount(), fa.getC_Currency_ID(), fa.getAmtAcctCr(), fa.getAmtAcctDr()); + fl.setDescription(fa.getDescription()); + updateFactLine(fl); + } + + return null; + } + + /** + * Create Rounding Correction for invoice + * @param as accounting schema + * @param fact + * @param acct + * @param invGainLossFactLines gain/loss fact lines for invoice + * @param invList invoice list + * @param invLineList invoice line list + * @param htFactLineInv C_Invoice_ID and the fact lines + * @return error message or null + */ + private String createInvoiceRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct, + ArrayList invGainLossFactLines, ArrayList invList, ArrayList invLineList, + HashMap> htFactLineInv) + { + if (m_matchInv.getReversal_ID() > 0 && m_matchInv.get_ID() > m_matchInv.getReversal_ID()) + return null; + + HashMap> htRoundingLineInvLine = new HashMap>(); + ArrayList invLineRoundingLines = new ArrayList(); + boolean isLineFullyMatched = createInvoiceLineRoundingCorrection(as, fact, acct, invGainLossFactLines, invList, invLineList, htFactLineInv, invLineRoundingLines, htRoundingLineInvLine); + + if (!isLineFullyMatched) + return null; + + BigDecimal totalInvClrAccounted = Env.ZERO; + for (FactLine invLineRoundingLine : invLineRoundingLines) + { + if (invLineRoundingLine.getAccount() == acct) + totalInvClrAccounted = totalInvClrAccounted.add(invLineRoundingLine.getAmtAcctDr()).subtract(invLineRoundingLine.getAmtAcctCr()); + } + + StringBuilder sqlInv = new StringBuilder() + .append("SELECT SUM(AmtSourceDr)-SUM(AmtSourceCr), SUM(AmtAcctDr)-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'"); + // C_Invoice_ID and the total source amount from C_Invoice accounting fact lines HashMap htInvSource = new HashMap(); // C_Invoice_ID and the total accounted amount from C_Invoice accounting fact lines HashMap htInvAccounted = new HashMap(); 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'"); - + BigDecimal invoiceSource = Env.ZERO; + BigDecimal invoiceAccounted = Env.ZERO; + // For Invoice - List valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(), + List valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sqlInv.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); + BigDecimal invoiceSourceDrCr = (BigDecimal) valuesInv.get(0); // AmtSource + BigDecimal invoiceAccountedDrCr = (BigDecimal) valuesInv.get(1); // AmtAcct + + invoiceSource = invoiceSource.add(invoiceSourceDrCr); + invoiceAccounted = invoiceAccounted.add(invoiceAccountedDrCr); + + totalInvClrAccounted = totalInvClrAccounted.add(invoiceAccountedDrCr); } + + // Currency Balancing + valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sqlInv.toString(), + MInvoice.Table_ID, invoice.getC_Invoice_ID(), as.getC_AcctSchema_ID(), as.getCurrencyBalancing_Acct().getAccount_ID()); + if (valuesInv != null) { + BigDecimal invoiceSourceDrCr = (BigDecimal) valuesInv.get(0); // AmtSource + BigDecimal invoiceAccountedDrCr = (BigDecimal) valuesInv.get(1); // AmtAcct + + if (invoiceSourceDrCr == null) + invoiceSourceDrCr = Env.ZERO; + if (invoiceAccountedDrCr == null) + invoiceAccountedDrCr = Env.ZERO; + + invoiceSource = invoiceSource.add(invoiceSourceDrCr); + invoiceAccounted = invoiceAccounted.add(invoiceAccountedDrCr); + } + + htInvSource.put(invoice.getC_Invoice_ID(), invoiceSource.abs()); + htInvAccounted.put(invoice.getC_Invoice_ID(), invoiceAccounted.abs()); } MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); @@ -1336,6 +1458,8 @@ public class Doc_MatchInv extends Doc htTotalAmtAcctDr.put(C_Invoice_ID, totalAmtAcctDr); htTotalAmtSourceCr.put(C_Invoice_ID, totalAmtSourceCr); htTotalAmtAcctCr.put(C_Invoice_ID, totalAmtAcctCr); + + totalInvClrAccounted = totalInvClrAccounted.add(factLine.getAmtAcctDr()).subtract(factLine.getAmtAcctCr()); } else if (factLine.getAccount_ID() == gain.getAccount_ID() || factLine.getAccount_ID() == loss.getAccount_ID()) { @@ -1345,15 +1469,23 @@ public class Doc_MatchInv extends Doc 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; - - totalAmtSourceDr = totalAmtSourceDr.subtract(factLine.getAmtSourceCr()); - totalAmtSourceCr = totalAmtSourceCr.subtract(factLine.getAmtSourceDr()); + BigDecimal totalAmtAcctCr = htTotalAmtAcctCr.get(C_Invoice_ID); + if (totalAmtAcctCr == null) + totalAmtAcctCr = Env.ZERO; + + totalAmtSourceDr = totalAmtSourceDr.add(factLine.getAmtSourceDr()); + totalAmtSourceCr = totalAmtSourceCr.add(factLine.getAmtSourceCr()); htTotalAmtSourceDr.put(C_Invoice_ID, totalAmtSourceDr); + htTotalAmtAcctDr.put(C_Invoice_ID, totalAmtAcctDr); htTotalAmtSourceCr.put(C_Invoice_ID, totalAmtSourceCr); + htTotalAmtAcctCr.put(C_Invoice_ID, totalAmtAcctCr); } } } @@ -1381,32 +1513,21 @@ public class Doc_MatchInv extends Doc BigDecimal totalAmtAcctCr = htTotalAmtAcctCr.get(invoice.getC_Invoice_ID()); if (totalAmtAcctCr == null) totalAmtAcctCr = Env.ZERO; + + matchInvSource = matchInvSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); + matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr); - if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0) + for (FactLine invLineRoundingLine : invLineRoundingLines) { - 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) + if (invLineRoundingLine.getAccount() == acct) { - matchInvSource = matchInvSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); - matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr); + ArrayList roundingLineList = htRoundingLineInvLine.get(invoice.get_ID()); + if (roundingLineList == null || !roundingLineList.contains(invLineRoundingLine)) + continue; + matchInvAccounted = matchInvAccounted.add(invLineRoundingLine.getAmtAcctDr()).subtract(invLineRoundingLine.getAmtAcctCr()); } - 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()); @@ -1414,7 +1535,7 @@ public class Doc_MatchInv extends Doc skipMatchInvIdList.add(m_matchInv.get_ID()); for (MMatchInv matchInv : matchInvs) { - if (matchInv.getReversal_ID() > 0) + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) skipMatchInvIdList.add(matchInv.get_ID()); } @@ -1434,6 +1555,18 @@ public class Doc_MatchInv extends Doc .append(" AND PostingType='A'") .append(" AND Account_ID=?"); + if (m_matchInv.getReversal_ID() > 0) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); + } + else + { + if (matchInv.getReversal_ID() > 0) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + } + if (matchInv.getRef_MatchInv_ID() > 0) { if (invoice.isCreditMemo() && matchInv.getQty().compareTo(BigDecimal.ZERO) < 0) @@ -1459,29 +1592,10 @@ public class Doc_MatchInv extends Doc 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); - } - } + matchInvSource = matchInvSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); + matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr); + + totalInvClrAccounted = totalInvClrAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr); } sql = new StringBuilder() @@ -1493,6 +1607,26 @@ 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 (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); + } + else + { + if (matchInv.getReversal_ID() > 0) + sql.append(" AND Record_ID <> ").append(matchInv.get_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 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(), @@ -1510,63 +1644,17 @@ public class Doc_MatchInv extends Doc totalAmtAcctCr = (BigDecimal) valuesMatchInv.get(3); if (totalAmtAcctCr == null) totalAmtAcctCr = Env.ZERO; - - matchInvAccounted = matchInvAccounted.subtract(totalAmtAcctDr).subtract(totalAmtAcctCr); + + matchInvSource = matchInvSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); } } 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 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); - } } + ArrayList invRoundingLines = new ArrayList(); for (MInvoice invoice : invList) { BigDecimal invSource = htInvSource.get(invoice.getC_Invoice_ID()); @@ -1593,6 +1681,8 @@ public class Doc_MatchInv extends Doc if (log.isLoggable(Level.FINE)) log.fine(d2.toString()); description.append(" - ").append(d2); } + else + isLineFullyMatched = false; if (acctDifference == null || acctDifference.signum() == 0) { @@ -1600,31 +1690,20 @@ public class Doc_MatchInv extends Doc continue; } - if (acctDifference.abs().compareTo(TOLERANCE) > 0) + 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); + invRoundingLines.add(fl); + + totalInvClrAccounted = totalInvClrAccounted.add(fl.getAmtAcctDr()).subtract(fl.getAmtAcctCr()); if (as.isCurrencyBalancing() && as.getC_Currency_ID() != invoice.getC_Currency_ID()) fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference); @@ -1632,14 +1711,339 @@ public class Doc_MatchInv extends Doc fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference); fl.setDescription(description.toString()); updateFactLine(fl); + invRoundingLines.add(fl); } } + + if (isLineFullyMatched) { + if (totalInvClrAccounted != null && totalInvClrAccounted.signum() != 0 && totalInvClrAccounted.abs().compareTo(TOLERANCE) < 0) + { + BigDecimal totalRounding = Env.ZERO; + for (FactLine invRoundingLine : invRoundingLines) + { + if (invRoundingLine.getAccount() == acct) + totalRounding = totalRounding.add(invRoundingLine.getAmtAcctDr()).subtract(invRoundingLine.getAmtAcctCr()); + } + + if (totalRounding.compareTo(totalInvClrAccounted) == 0) + { + for (FactLine invRoundingLine : invRoundingLines) + { + fact.remove(invRoundingLine); + } + totalInvClrAccounted = Env.ZERO; + } + } + + if (totalInvClrAccounted != null && totalInvClrAccounted.signum() != 0 && totalInvClrAccounted.abs().compareTo(TOLERANCE) < 0) + { + StringBuilder description = new StringBuilder("Invoice - MatchInv - (full) = ").append(totalInvClrAccounted); + if (log.isLoggable(Level.FINE)) log.fine(description.toString()); + + FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), totalInvClrAccounted.negate()); + fl.setDescription(description.toString()); + updateFactLine(fl); + + if (as.isCurrencyBalancing()) + fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), totalInvClrAccounted); + else + fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), totalInvClrAccounted); + fl.setDescription(description.toString()); + updateFactLine(fl); + } + } + return null; } - private String createReceiptGainLoss(MAcctSchema as, Fact fact, MAccount acct, - MInOut receipt, BigDecimal matchInvSource, BigDecimal matchInvAccounted) + /** + * Create Rounding Correction for invoice line + * @param as accounting schema + * @param fact + * @param acct + * @param invGainLossFactLines gain/loss fact lines for invoice + * @param invList invoice list + * @param invLineList invoice line list + * @param htFactLineInv C_Invoice_ID and the fact lines + * @param invRoundingLines rounding correction fact lines for invoice + * @param htRoundingLineInv C_Invoice_ID and the correction fact lines + * @return error message or null + */ + private boolean createInvoiceLineRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct, + ArrayList invGainLossFactLines, ArrayList invList, ArrayList invLineList, + HashMap> htFactLineInv, ArrayList invRoundingLines, HashMap> htRoundingLineInv) { + StringBuilder sqlInvLine = new StringBuilder() + .append("SELECT SUM(AmtSourceDr)-SUM(AmtSourceCr), SUM(AmtAcctDr)-SUM(AmtAcctCr)") + .append(" FROM Fact_Acct ") + .append("WHERE AD_Table_ID=? AND Record_ID=? AND Line_ID=?") + .append(" AND C_AcctSchema_ID=?") + .append(" AND Account_ID=?") + .append(" AND PostingType='A'"); + + // C_InvoiceLine_ID and the total source amount from C_Invoice accounting fact lines + HashMap htInvLineSource = new HashMap(); + // C_InvoiceLine_ID and the total accounted amount from C_Invoice accounting fact lines + HashMap htInvLineAccounted = new HashMap(); + for (MInvoiceLine invoiceLine : invLineList) + { + // For Invoice Line + List valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sqlInvLine.toString(), + MInvoice.Table_ID, invoiceLine.getC_Invoice_ID(), invoiceLine.getC_InvoiceLine_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID()); + if (valuesInv != null) { + BigDecimal invoiceSourceDrCr = (BigDecimal) valuesInv.get(0); // AmtSource + BigDecimal invoiceAccountedDrCr = (BigDecimal) valuesInv.get(1); // AmtAcct + + htInvLineSource.put(invoiceLine.getC_InvoiceLine_ID(), invoiceSourceDrCr.abs()); + htInvLineAccounted.put(invoiceLine.getC_InvoiceLine_ID(), invoiceAccountedDrCr.abs()); + } + } + + 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 amount from M_MatchInv accounting fact lines + HashMap htInvClrSource = new HashMap(); + // C_Invoice_ID and the total accounted amount from M_MatchInv accounting fact lines + HashMap htInvClrAccounted = new HashMap(); + for (Integer C_Invoice_ID : htFactLineInv.keySet()) + { + ArrayList factLineList = htFactLineInv.get(C_Invoice_ID); + for (FactLine factLine : factLineList) + { + if (factLine.getAccount_ID() == acct.getAccount_ID()) + { + BigDecimal invClrSource = htInvClrSource.get(C_Invoice_ID); + if (invClrSource == null) + invClrSource = Env.ZERO; + invClrSource = invClrSource.add(factLine.getAmtSourceDr()).subtract(factLine.getAmtSourceCr()); + htInvClrSource.put(C_Invoice_ID, invClrSource); + + BigDecimal invClrAccounted = htInvClrAccounted.get(C_Invoice_ID); + if (invClrAccounted == null) + invClrAccounted = Env.ZERO; + invClrAccounted = invClrAccounted.add(factLine.getAmtAcctDr()).subtract(factLine.getAmtAcctCr()); + htInvClrAccounted.put(C_Invoice_ID, invClrAccounted); + } + } + } + + for (MInvoiceLine invoiceLine : invLineList) + { + { + MMatchInv[] matchInvs = MMatchInv.getInvoiceLine(getCtx(), invoiceLine.get_ID(), getTrxName()); + + ArrayList skipMatchInvIdList = new ArrayList(); + skipMatchInvIdList.add(m_matchInv.get_ID()); + for (MMatchInv matchInv : matchInvs) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + 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 (m_matchInv.getReversal_ID() > 0) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); + } + else + { + if (matchInv.getReversal_ID() > 0) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + } + + if (matchInv.getRef_MatchInv_ID() > 0) + { + if (invoiceLine.getParent().isCreditMemo() && matchInv.getQty().compareTo(BigDecimal.ZERO) < 0) + sql.append(" AND Qty > 0"); + else + sql.append(" AND Qty < 0"); + } + + // For Match Inv + List 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) { + BigDecimal totalAmtSourceDr = (BigDecimal) valuesMatchInv.get(0); + if (totalAmtSourceDr == null) + totalAmtSourceDr = Env.ZERO; + BigDecimal totalAmtAcctDr = (BigDecimal) valuesMatchInv.get(1); + if (totalAmtAcctDr == null) + totalAmtAcctDr = Env.ZERO; + BigDecimal totalAmtSourceCr = (BigDecimal) valuesMatchInv.get(2); + if (totalAmtSourceCr == null) + totalAmtSourceCr = Env.ZERO; + BigDecimal totalAmtAcctCr = (BigDecimal) valuesMatchInv.get(3); + if (totalAmtAcctCr == null) + totalAmtAcctCr = Env.ZERO; + + BigDecimal invClrSource = htInvClrSource.get(invoiceLine.getC_Invoice_ID()); + if (invClrSource == null) + invClrSource = Env.ZERO; + invClrSource = invClrSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); + htInvClrSource.put(invoiceLine.getC_Invoice_ID(), invClrSource); + + BigDecimal invClrAccounted = htInvClrAccounted.get(invoiceLine.getC_Invoice_ID()); + if (invClrAccounted == null) + invClrAccounted = Env.ZERO; + invClrAccounted = invClrAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr); + htInvClrAccounted.put(invoiceLine.getC_Invoice_ID(), invClrAccounted); + } + + 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 Line%'"); + + if (m_matchInv.getReversal_ID() > 0) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); + } + else + { + if (matchInv.getReversal_ID() > 0) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + } + + if (matchInv.getRef_MatchInv_ID() > 0) + { + if (invoiceLine.getParent().isCreditMemo() && matchInv.getQty().compareTo(BigDecimal.ZERO) < 0) + sql.append(" AND Qty > 0"); + else + sql.append(" AND Qty < 0"); + } + + // 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) { + BigDecimal totalAmtSourceDr = (BigDecimal) valuesMatchInv.get(0); + if (totalAmtSourceDr == null) + totalAmtSourceDr = Env.ZERO; + BigDecimal totalAmtAcctDr = (BigDecimal) valuesMatchInv.get(1); + if (totalAmtAcctDr == null) + totalAmtAcctDr = Env.ZERO; + BigDecimal totalAmtSourceCr = (BigDecimal) valuesMatchInv.get(2); + if (totalAmtSourceCr == null) + totalAmtSourceCr = Env.ZERO; + BigDecimal totalAmtAcctCr = (BigDecimal) valuesMatchInv.get(3); + if (totalAmtAcctCr == null) + totalAmtAcctCr = Env.ZERO; + + BigDecimal invClrSource = htInvClrSource.get(invoiceLine.getC_Invoice_ID()); + if (invClrSource == null) + invClrSource = Env.ZERO; + invClrSource = invClrSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); + htInvClrSource.put(invoiceLine.getC_Invoice_ID(), invClrSource); + + // ignore totalAmtAcctDr and totalAmtAccrCr + } + } + } + } + + boolean isLineFullyMatched = true; + for (MInvoiceLine invoiceLine : invLineList) + { + BigDecimal invLineSource = htInvLineSource.get(invoiceLine.get_ID()); + if (invLineSource == null) + invLineSource = Env.ZERO; + BigDecimal invLineAccounted = htInvLineAccounted.get(invoiceLine.get_ID()); + if (invLineAccounted == null) + invLineAccounted = Env.ZERO; + BigDecimal invClrSource = htInvClrSource.get(invoiceLine.getC_Invoice_ID()); + if (invClrSource == null) + invClrSource = Env.ZERO; + BigDecimal invClrAccounted = htInvClrAccounted.get(invoiceLine.getC_Invoice_ID()); + if (invClrAccounted == null) + invClrAccounted = Env.ZERO; + StringBuilder description = new StringBuilder("Invoice Line=(").append(getC_Currency_ID()).append(")").append(invLineSource).append("/").append(invLineAccounted) + .append(" - Match Invoice=(").append(getC_Currency_ID()).append(")").append(invClrSource).append("/").append(invClrAccounted); + if (log.isLoggable(Level.FINE)) log.fine(description.toString()); + BigDecimal acctDifference = invLineAccounted.abs().subtract(invClrAccounted.abs()); // gain is negative + if (invClrSource.abs().compareTo(invLineSource.abs()) == 0) + { + if (acctDifference != null && acctDifference.signum() != 0 && acctDifference.abs().compareTo(TOLERANCE) < 0) + { + StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference); + if (log.isLoggable(Level.FINE)) log.fine(d2.toString()); + description.append(" - ").append(d2); + + FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate()); + fl.setDescription(description.toString()); + updateFactLine(fl); + invRoundingLines.add(fl); + ArrayList roundingLineList = htRoundingLineInv.get(invoiceLine.getC_Invoice_ID()); + if (roundingLineList == null) + roundingLineList = new ArrayList(); + roundingLineList.add(fl); + + if (as.isCurrencyBalancing()) + 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); + invRoundingLines.add(fl); + roundingLineList.add(fl); + htRoundingLineInv.put(invoiceLine.getC_Invoice_ID(), roundingLineList); + } + } + else + { + if (acctDifference != null && acctDifference.signum() != 0 && acctDifference.abs().compareTo(TOLERANCE) < 0) + ; + else + isLineFullyMatched = false; + } + } + + return isLineFullyMatched; + } + + /** + * Create Gain/Loss for receipt + * @param as accounting schema + * @param fact + * @param acct + * @param receipt + * @param matchInvSource + * @param matchInvAccounted + * @param mrGainLossFactLines gain/loss fact lines for receipt + * @param mrFactLines fact lines for receipt + * @return error message or null + */ + private String createReceiptGainLoss(MAcctSchema as, Fact fact, MAccount acct, + MInOut receipt, BigDecimal matchInvSource, BigDecimal matchInvAccounted, + ArrayList mrGainLossFactLines, ArrayList mrFactLines) + { + if (m_matchInv.getReversal_ID() > 0 && m_matchInv.get_ID() > m_matchInv.getReversal_ID()) + return createReversalReceiptGainLossRoundingCorrection(as, fact, acct); + BigDecimal receiptSource = null; BigDecimal receiptAccounted = null; // @@ -1667,114 +2071,15 @@ public class Doc_MatchInv extends Doc if (receiptSource == null || receiptAccounted == null) return null; // - if (m_matchInv.getReversal_ID() == 0) - { - String matchInvLineSql = "SELECT M_MatchInv_ID FROM M_MatchInv " - + "WHERE M_InOutLine_ID IN (SELECT M_InOutLine_ID FROM M_InOutLine WHERE M_InOut_ID=?) " - + "AND COALESCE(Reversal_ID,0)=0"; - List> list = DB.getSQLArrayObjectsEx(getTrxName(), matchInvLineSql, receipt.get_ID()); - StringBuilder s = new StringBuilder(); - - if (list == null) - return null; - - for (int index=0; index < list.size(); index++) - { - List 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.negate();; - } - else - { - matchInvSource = matchInvSource.add(totalAmtSourceCr); - matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr); - acctDifference = totalAmtAcctDr; - } - } - } - - StringBuilder description = new StringBuilder("InOut=(").append(m_invoiceLine.getParent().getC_Currency_ID()).append(")").append(receiptSource).append("/").append(receiptAccounted) - .append(" - MatchInv=(").append(getC_Currency_ID()).append(")").append(matchInvSource).append("/").append(matchInvAccounted); + StringBuilder description = new StringBuilder("InOut=(") + .append(receipt.getC_Currency_ID()).append(")").append(receiptSource).append("/").append(receiptAccounted) + .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(receiptSource) == 0) + BigDecimal acctDifference = null; + // Full INV in currency + if (matchInvSource.compareTo(receiptSource) == 0) { - acctDifference = matchInvAccounted.subtract(receiptAccounted.abs()); // gain is negative + acctDifference = matchInvAccounted.abs().subtract(receiptAccounted.abs()); // gain is negative StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference); if (log.isLoggable(Level.FINE)) log.fine(d2.toString()); description.append(" - ").append(d2); @@ -1789,34 +2094,543 @@ public class Doc_MatchInv extends Doc MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct()); // - if (!receipt.isSOTrx()) + FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate()); + fl.setDescription(description.toString()); + updateFactLine(fl); + mrFactLines.add(fl); + + fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference); + fl.setDescription(description.toString()); + updateFactLine(fl); + mrGainLossFactLines.add(fl); + mrFactLines.add(fl); + return null; + } // createReceiptGainLoss + + /** + * Create Gain/Loss and Rounding Correction for reverse receipt + * @param as accounting schema + * @param fact + * @param acct + * @return error message or null + */ + private String createReversalReceiptGainLossRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct) + { + if (m_matchInv.getReversal_ID() == 0) + return null; + + MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); + MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct()); + + StringBuilder whereClause = new StringBuilder() + .append("AD_Table_ID=?") + .append(" AND Record_ID=?") + .append(" AND C_AcctSchema_ID=?") + .append(" AND PostingType='A'") + .append(" AND (Account_ID=? OR Account_ID=? OR Account_ID=? OR Account_ID=?)") + .append(" AND Description LIKE 'InOut%'"); + + List list = new Query(getCtx(), MFactAcct.Table_Name, whereClause.toString(), getTrxName()) + .setParameters(MMatchInv.Table_ID, m_matchInv.getReversal_ID(), as.getC_AcctSchema_ID(), + acct.getAccount_ID(), gain.getAccount_ID(), loss.getAccount_ID(), as.getCurrencyBalancing_Acct().getAccount_ID()) + .setOrderBy(MFactAcct.COLUMNNAME_Fact_Acct_ID) + .list(); + for (MFactAcct fa : list) { + FactLine fl = fact.createLine(null, fa.getMAccount(), fa.getC_Currency_ID(), fa.getAmtAcctCr(), fa.getAmtAcctDr()); + fl.setDescription(fa.getDescription()); + updateFactLine(fl); + } + + return null; + } + + /** + * Create Rounding Correction for receipt + * @param as accounting schema + * @param fact + * @param acct + * @param mrGainLossFactLines gain/loss fact lines for receipt + * @param mrFactLines fact lines for receipt + * @return error message or null + */ + 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()) + return null; + + ArrayList mrLineRoundingLines = new ArrayList(); + boolean isLineFullyMatched = createReceiptLineRoundingCorrection(as, fact, acct, mrGainLossFactLines, mrFactLines, mrLineRoundingLines); + + if (!isLineFullyMatched) + return null; + + int M_InOut_ID = m_receiptLine.getM_InOut_ID(); + + BigDecimal totalNIRAccounted = Env.ZERO; + for (FactLine mrLineRoundingLine : mrLineRoundingLines) { - FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate()); - fl.setDescription(description.toString()); - updateFactLine(fl); + if (mrLineRoundingLine.getAccount() == acct) + totalNIRAccounted = totalNIRAccounted.add(mrLineRoundingLine.getAmtAcctDr()).subtract(mrLineRoundingLine.getAmtAcctCr()); + } + + StringBuilder sqlMR = new StringBuilder() + .append("SELECT SUM(AmtSourceDr)-SUM(AmtSourceCr), SUM(AmtAcctDr)-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'"); + + BigDecimal mrSource = Env.ZERO; + BigDecimal mrAccounted = Env.ZERO; + + // For MR + List valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sqlMR.toString(), + MInOut.Table_ID, M_InOut_ID, as.getC_AcctSchema_ID(), acct.getAccount_ID()); + if (valuesInv != null) { + BigDecimal receiptSourceDrCr = (BigDecimal) valuesInv.get(0); // AmtSource + BigDecimal receiptAccountedDrCr = (BigDecimal) valuesInv.get(1); // AmtAcct + + mrSource = mrSource.add(receiptSourceDrCr); + mrAccounted = mrAccounted.add(receiptAccountedDrCr); + + totalNIRAccounted = totalNIRAccounted.add(receiptAccountedDrCr); + } + + // Currency Balancing + valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sqlMR.toString(), + MInOut.Table_ID, M_InOut_ID, as.getC_AcctSchema_ID(), as.getCurrencyBalancing_Acct().getAccount_ID()); + if (valuesInv != null) { + BigDecimal receiptSourceDrCr = (BigDecimal) valuesInv.get(0); // AmtSource + BigDecimal receiptAccountedDrCr = (BigDecimal) valuesInv.get(1); // AmtAcct + + if (receiptSourceDrCr == null) + receiptSourceDrCr = Env.ZERO; + if (receiptAccountedDrCr == null) + receiptAccountedDrCr = Env.ZERO; + + mrSource = mrSource.add(receiptSourceDrCr); + mrAccounted = mrAccounted.add(receiptAccountedDrCr); + } + + MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); + MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct()); + + BigDecimal totalAmtSourceDr = Env.ZERO; + BigDecimal totalAmtAcctDr = Env.ZERO; + BigDecimal totalAmtSourceCr = Env.ZERO; + BigDecimal totalAmtAcctCr = Env.ZERO; - if (as.isCurrencyBalancing() && as.getC_Currency_ID() != m_invoiceLine.getParent().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 factLineList = mrFactLines; + for (FactLine factLine : factLineList) + { + if (factLine.getAccount_ID() == acct.getAccount_ID()) + { + totalAmtSourceDr = totalAmtSourceDr.add(factLine.getAmtSourceDr()); + totalAmtAcctDr = totalAmtAcctDr.add(factLine.getAmtAcctDr()); + totalAmtSourceCr = totalAmtSourceCr.add(factLine.getAmtSourceCr()); + totalAmtAcctCr = totalAmtAcctCr.add(factLine.getAmtAcctCr()); + + totalNIRAccounted = totalNIRAccounted.add(factLine.getAmtAcctDr()).subtract(factLine.getAmtAcctCr()); + } + else if (factLine.getAccount_ID() == gain.getAccount_ID() || factLine.getAccount_ID() == loss.getAccount_ID()) + { + if (!mrGainLossFactLines.contains(factLine)) + continue; + + totalAmtSourceDr = totalAmtSourceDr.add(factLine.getAmtSourceDr()); + totalAmtSourceCr = totalAmtSourceCr.add(factLine.getAmtSourceCr()); + } + } + + BigDecimal matchInvSource = Env.ZERO; + BigDecimal matchInvAccounted = Env.ZERO; + + matchInvSource = matchInvSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); + matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr); + + for (FactLine mrLineRoundingLine : mrLineRoundingLines) + { + if (mrLineRoundingLine.getAccount() == acct) + matchInvAccounted = matchInvAccounted.add(mrLineRoundingLine.getAmtAcctDr()).subtract(mrLineRoundingLine.getAmtAcctCr()); + } + + MMatchInv[] matchInvs = MMatchInv.getInOut(getCtx(), M_InOut_ID, getTrxName()); + ArrayList skipMatchInvIdList = new ArrayList(); + skipMatchInvIdList.add(m_matchInv.get_ID()); + for (MMatchInv matchInv : matchInvs) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + 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=?") // match inv + .append(" AND C_AcctSchema_ID=?") + .append(" AND PostingType='A'") + .append(" AND Account_ID=?"); + + if (m_matchInv.getReversal_ID() > 0) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); + } + else + { + if (matchInv.getReversal_ID() > 0) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + } + + // For Match Inv + List valuesMatchInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(), + MMatchInv.Table_ID, matchInv.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; + + matchInvSource = matchInvSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); + matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr); + + totalNIRAccounted = totalNIRAccounted.add(totalAmtAcctDr).subtract(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=?") // 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 'InOut%'"); + + if (m_matchInv.getReversal_ID() > 0) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); + } + else + { + if (matchInv.getReversal_ID() > 0) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + } + + // For Match Inv + valuesMatchInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(), + MMatchInv.Table_ID, matchInv.get_ID(), + 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; + + matchInvSource = matchInvSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); + } + } + + StringBuilder description = new StringBuilder("InOut=(") + .append(getC_Currency_ID()).append(")").append(mrSource).append("/").append(mrAccounted) + .append(" - Match Invoice=(").append(getC_Currency_ID()).append(")").append(matchInvSource).append("/").append(matchInvAccounted); + if (log.isLoggable(Level.FINE)) log.fine(description.toString()); + BigDecimal acctDifference = Env.ZERO; + if (matchInvSource.abs().compareTo(mrSource.abs()) == 0) + { + acctDifference = mrAccounted.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); + } + else + isLineFullyMatched = false; + + if (acctDifference == null || acctDifference.signum() == 0) + { + log.fine("No Difference"); + return null; + } + + if (acctDifference.abs().compareTo(TOLERANCE) >= 0) + { + log.fine("acctDifference="+acctDifference); + return null; + } + + ArrayList mrRoundingLines = new ArrayList(); + // + FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference); + fl.setDescription(description.toString()); + updateFactLine(fl); + mrRoundingLines.add(fl); + + totalNIRAccounted = totalNIRAccounted.add(fl.getAmtAcctDr()).subtract(fl.getAmtAcctCr()); + + if (as.isCurrencyBalancing() && as.getC_Currency_ID() != m_invoiceLine.getParent().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); + mrRoundingLines.add(fl); + + if (isLineFullyMatched) { + if (totalNIRAccounted != null && totalNIRAccounted.signum() != 0 && totalNIRAccounted.abs().compareTo(TOLERANCE) < 0) + { + BigDecimal totalRounding = Env.ZERO; + for (FactLine mrRoundingLine : mrRoundingLines) + { + if (mrRoundingLine.getAccount() == acct) + totalRounding = totalRounding.add(mrRoundingLine.getAmtAcctDr()).subtract(mrRoundingLine.getAmtAcctCr()); + } + + if (totalRounding.compareTo(totalNIRAccounted) == 0) + { + for (FactLine invRoundingLine : mrRoundingLines) + { + fact.remove(invRoundingLine); + } + totalNIRAccounted = Env.ZERO; + } + } + + if (totalNIRAccounted != null && totalNIRAccounted.signum() != 0 && totalNIRAccounted.abs().compareTo(TOLERANCE) < 0) + { + description = new StringBuilder("InOut - MatchInv - (full) = ").append(totalNIRAccounted); + if (log.isLoggable(Level.FINE)) log.fine(description.toString()); + + fl = fact.createLine (null, acct, as.getC_Currency_ID(), totalNIRAccounted); + fl.setDescription(description.toString()); + updateFactLine(fl); + + if (as.isCurrencyBalancing()) + fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), totalNIRAccounted.negate()); + else + fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), totalNIRAccounted.negate()); + fl.setDescription(description.toString()); + updateFactLine(fl); + } + } + + return null; + } + + /** + * Create Rounding Correction for receipt line + * @param as accounting schema + * @param fact + * @param acct + * @param mrGainLossFactLines gain/loss fact lines for receipt + * @param mrFactLines fact lines for receipt + * @param mrRoundingLines rounding correction fact lines for receipt + * @return error message or null + */ + private boolean createReceiptLineRoundingCorrection(MAcctSchema as, Fact fact, MAccount acct, + ArrayList mrGainLossFactLines, ArrayList mrFactLines, ArrayList mrRoundingLines) + { + BigDecimal mrLineSource = null; + BigDecimal mrLineAccounted = null; + + StringBuilder sqlMRLine = new StringBuilder() + .append("SELECT SUM(AmtSourceDr)-SUM(AmtSourceCr), SUM(AmtAcctDr)-SUM(AmtAcctCr)") + .append(" FROM Fact_Acct ") + .append("WHERE AD_Table_ID=? AND Record_ID=? AND Line_ID=?") + .append(" AND C_AcctSchema_ID=?") + .append(" AND Account_ID=?") + .append(" AND PostingType='A'"); + // For MR Line + List valuesMR = DB.getSQLValueObjectsEx(getTrxName(), sqlMRLine.toString(), + MInOut.Table_ID, m_receiptLine.getM_InOut_ID(), m_receiptLine.getM_InOutLine_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID()); + if (valuesMR != null) { + mrLineSource = (BigDecimal) valuesMR.get(0); // AmtSource + mrLineAccounted = (BigDecimal) valuesMR.get(1); // AmtAcct + } + + MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); + MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct()); + + BigDecimal nirSource = Env.ZERO; + BigDecimal nirAccounted = Env.ZERO; + + ArrayList factLineList = mrFactLines; + for (FactLine factLine : factLineList) + { + if (factLine.getAccount_ID() == acct.getAccount_ID()) + { + nirSource = nirSource.add(factLine.getAmtSourceDr()).subtract(factLine.getAmtSourceCr()); + nirAccounted = nirAccounted.add(factLine.getAmtAcctDr()).subtract(factLine.getAmtAcctCr()); + } + } + + MMatchInv[] matchInvs = MMatchInv.getInOutLine(getCtx(), m_receiptLine.get_ID(), getTrxName()); + ArrayList skipMatchInvIdList = new ArrayList(); + skipMatchInvIdList.add(m_matchInv.get_ID()); + for (MMatchInv matchInv : matchInvs) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + 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=?") // match inv + .append(" AND C_AcctSchema_ID=?") + .append(" AND PostingType='A'") + .append(" AND Account_ID=?"); + + if (m_matchInv.getReversal_ID() > 0) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); + } + else + { + if (matchInv.getReversal_ID() > 0) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + } + + // For Match Inv + List valuesMatchInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(), + MMatchInv.Table_ID, matchInv.get_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID()); + if (valuesMatchInv != null) { + BigDecimal totalAmtSourceDr = (BigDecimal) valuesMatchInv.get(0); + if (totalAmtSourceDr == null) + totalAmtSourceDr = Env.ZERO; + BigDecimal totalAmtAcctDr = (BigDecimal) valuesMatchInv.get(1); + if (totalAmtAcctDr == null) + totalAmtAcctDr = Env.ZERO; + BigDecimal totalAmtSourceCr = (BigDecimal) valuesMatchInv.get(2); + if (totalAmtSourceCr == null) + totalAmtSourceCr = Env.ZERO; + BigDecimal totalAmtAcctCr = (BigDecimal) valuesMatchInv.get(3); + if (totalAmtAcctCr == null) + totalAmtAcctCr = Env.ZERO; + + nirSource = nirSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); + nirAccounted = nirAccounted.add(totalAmtAcctDr).subtract(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=?") // 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 'InOut Line%'"); + + if (m_matchInv.getReversal_ID() > 0) + { + if (matchInv.getReversal_ID() > 0 && matchInv.get_ID() > matchInv.getReversal_ID()) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + sql.append(" AND Record_ID < ").append(m_matchInv.getReversal_ID()); + } + else + { + if (matchInv.getReversal_ID() > 0) + sql.append(" AND Record_ID <> ").append(matchInv.get_ID()); + } + + // For Match Inv + valuesMatchInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(), + MMatchInv.Table_ID, matchInv.get_ID(), as.getC_AcctSchema_ID(), + gain.getAccount_ID(), loss.getAccount_ID(), as.getCurrencyBalancing_Acct().getAccount_ID()); + if (valuesMatchInv != null) { + BigDecimal totalAmtSourceDr = (BigDecimal) valuesMatchInv.get(0); + if (totalAmtSourceDr == null) + totalAmtSourceDr = Env.ZERO; + BigDecimal totalAmtAcctDr = (BigDecimal) valuesMatchInv.get(1); + if (totalAmtAcctDr == null) + totalAmtAcctDr = Env.ZERO; + BigDecimal totalAmtSourceCr = (BigDecimal) valuesMatchInv.get(2); + if (totalAmtSourceCr == null) + totalAmtSourceCr = Env.ZERO; + BigDecimal totalAmtAcctCr = (BigDecimal) valuesMatchInv.get(3); + if (totalAmtAcctCr == null) + totalAmtAcctCr = Env.ZERO; + + nirSource = nirSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr); + // ignore totalAmtAcctDr and totalAmtAccrCr + } + } + + boolean isLineFullyMatched = true; + + StringBuilder description = new StringBuilder("InOut Line=(") + .append(getC_Currency_ID()).append(")").append(mrLineSource).append("/").append(mrLineAccounted) + .append(" - Match Invoice=(").append(getC_Currency_ID()).append(")").append(nirSource).append("/").append(nirAccounted); + if (log.isLoggable(Level.FINE)) log.fine(description.toString()); + BigDecimal acctDifference = mrLineAccounted.abs().subtract(nirAccounted.abs()); // gain is negative + + if (nirSource.abs().compareTo(mrLineSource.abs()) == 0) + { + if (acctDifference != null && acctDifference.signum() != 0 && acctDifference.abs().compareTo(TOLERANCE) < 0) + { + StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference); + if (log.isLoggable(Level.FINE)) log.fine(d2.toString()); + description.append(" - ").append(d2); + + FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference); + fl.setDescription(description.toString()); + updateFactLine(fl); + mrRoundingLines.add(fl); + + if (as.isCurrencyBalancing() && as.getC_Currency_ID() != m_invoiceLine.getParent().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); + mrRoundingLines.add(fl); } - fl.setDescription(description.toString()); - updateFactLine(fl); } else { - FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference); - fl.setDescription(description.toString()); - updateFactLine(fl); - - if (as.isCurrencyBalancing() && as.getC_Currency_ID() != m_invoiceLine.getParent().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); + if (acctDifference != null && acctDifference.signum() != 0 && acctDifference.abs().compareTo(TOLERANCE) < 0) + ; + else + isLineFullyMatched = false; } - return null; - } // createReceiptGainLoss + + return isLineFullyMatched; + } } // Doc_MatchInv diff --git a/org.idempiere.test/src/org/idempiere/test/base/MatchInv2ndAcctSchemaTest.java b/org.idempiere.test/src/org/idempiere/test/base/MatchInv2ndAcctSchemaTest.java new file mode 100644 index 0000000000..850c8c7f0e --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/base/MatchInv2ndAcctSchemaTest.java @@ -0,0 +1,1909 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - Elaine Tan - etantg * + **********************************************************************/ +package org.idempiere.test.base; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; + +import org.compiere.acct.Doc; +import org.compiere.acct.DocManager; +import org.compiere.model.I_C_Currency; +import org.compiere.model.MAccount; +import org.compiere.model.MAcctSchema; +import org.compiere.model.MBPartner; +import org.compiere.model.MConversionRate; +import org.compiere.model.MCurrency; +import org.compiere.model.MDocType; +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.MOrder; +import org.compiere.model.MOrderLine; +import org.compiere.model.MPriceList; +import org.compiere.model.MPriceListVersion; +import org.compiere.model.MProduct; +import org.compiere.model.MProductPrice; +import org.compiere.model.MWarehouse; +import org.compiere.model.PO; +import org.compiere.model.ProductCost; +import org.compiere.model.Query; +import org.compiere.process.DocAction; +import org.compiere.process.DocumentEngine; +import org.compiere.process.ProcessInfo; +import org.compiere.util.Env; +import org.compiere.wf.MWorkflow; +import org.idempiere.test.AbstractTestCase; +import org.junit.jupiter.api.Test; + +/** + * @author Elaine Tan - etantg + */ +public class MatchInv2ndAcctSchemaTest extends AbstractTestCase { + + public MatchInv2ndAcctSchemaTest() { + } + + @Test + /** + * Test the matched invoice posting for credit memo (same period) + * PO Qty1=2400, Qty2=2400 + * IV Qty1=2400, Qty2=2400 + * CM Qty1=100, Qty2=100 + * https://idempiere.atlassian.net/browse/IDEMPIERE-4263 + */ + public void testCreditMemoPosting_1() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product1 = MProduct.get(Env.getCtx(), 124); // Elm Tree + MProduct product2 = MProduct.get(Env.getCtx(), 123); // Oak Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal usdToEur = new BigDecimal(31.526248754713); + MConversionRate cr = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, usdToEur); + BigDecimal eurToUsd = cr.getDivideRate(); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur1 = new BigDecimal(1); + BigDecimal qtyOrdered1 = new BigDecimal(2400); + MOrderLine orderLine1 = createPurchaseOrderLine(order, 10, product1, qtyOrdered1, priceInEur1); + BigDecimal priceInEur2 = new BigDecimal(2); + BigDecimal qtyOrdered2 = new BigDecimal(2400); + MOrderLine orderLine2 = createPurchaseOrderLine(order, 20, product2, qtyOrdered2, priceInEur2); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced1 = new BigDecimal(2400); + MInvoiceLine invoiceLine1 = createAPInvoiceLine(invoice, orderLine1, qtyInvoiced1); + BigDecimal qtyInvoiced2 = new BigDecimal(2400); + MInvoiceLine invoiceLine2 = createAPInvoiceLine(invoice, orderLine2, qtyInvoiced2); + completeDocument(invoice); + postDocument(invoice); + + MInvoice creditMemo = createAPCreditMemo(order, currentDate); + BigDecimal qtyCredited1 = new BigDecimal(100); + MInvoiceLine creditMemoLine1 = createAPCreditMemoLine(creditMemo, orderLine1, qtyCredited1); + BigDecimal qtyCredited2 = new BigDecimal(100); + MInvoiceLine creditMemoLine2 = createAPCreditMemoLine(creditMemo, orderLine2, qtyCredited2); + completeDocument(creditMemo); + postDocument(creditMemo); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine1.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyInvoiced1.doubleValue(), qtyCredited1.doubleValue())); + + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine1); + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, creditMemoLine1); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine1); + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, creditMemoLine1); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine2.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyInvoiced2.doubleValue(), qtyCredited2.doubleValue())); + + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine2); + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, creditMemoLine2); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro,accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine2); + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, creditMemoLine2); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting for credit memo (same period) + * PO Qty=10, Price=33.75 + * MR Qty=5 + * IV Qty=6 + * CM Qty=1 + * MR Qty=5 + * IV Qty=5 + */ + public void testCreditMemoPosting_2() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd = new BigDecimal(31.526248754713); + MConversionRate cr = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, eurToUsd, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur = new BigDecimal(33.75); + BigDecimal qtyOrdered = new BigDecimal(10); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInEur); + completeDocument(order); + + MInOut receipt1 = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered = new BigDecimal(5); + MInOutLine receiptLine1 = createMMReceiptLine(receipt1, orderLine, qtyDelivered); + completeDocument(receipt1); + postDocument(receipt1); + + MInvoice invoice1 = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced = new BigDecimal(6); + MInvoiceLine invoiceLine1 = createAPInvoiceLine(invoice1, orderLine, qtyInvoiced); + completeDocument(invoice1); + postDocument(invoice1); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine1.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine1); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine1); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine1); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine1); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInvoice creditMemo = createAPCreditMemo(order, currentDate); + BigDecimal qtyCredited = new BigDecimal(1); + MInvoiceLine creditMemoLine = createAPCreditMemoLine(creditMemo, orderLine, qtyCredited); + completeDocument(creditMemo); + postDocument(creditMemo); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyInvoiced.doubleValue(), qtyCredited.doubleValue())); + + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine1); + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine1); + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInOut receipt2 = createMMReceipt(order, currentDate); + qtyDelivered = new BigDecimal(5); + MInOutLine receiptLine2 = createMMReceiptLine(receipt2, orderLine, qtyDelivered); + completeDocument(receipt2); + postDocument(receipt2); + + MInvoice invoice2 = createAPInvoice(order, currentDate); + qtyInvoiced = new BigDecimal(5); + MInvoiceLine invoiceLine2 = createAPInvoiceLine(invoice2, orderLine, qtyInvoiced); + completeDocument(invoice2); + postDocument(invoice2); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine2.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine2); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine2); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting for credit memo (different period) + * PO Qty=3, Price=0.3023, Period 1 + * IV Qty=3, Period 1 + * MR Qty=1, Period 2 + * CM Qty=2, Period 2 + */ + public void testCreditMemoPosting_3() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(currentDate.getTime()); + cal.add(Calendar.DAY_OF_MONTH, -1); + Timestamp date1 = new Timestamp(cal.getTimeInMillis()); + Timestamp date2 = currentDate; + + int C_ConversionType_ID = 201; // Company + + MPriceList priceList = new MPriceList(Env.getCtx(), 0, null); + priceList.setName("Purchase GBP " + System.currentTimeMillis()); + MCurrency britishPound = MCurrency.get("GBP"); // British Pound (GBP) + priceList.setC_Currency_ID(britishPound.getC_Currency_ID()); + priceList.setPricePrecision(britishPound.getStdPrecision()); + priceList.saveEx(); + + MPriceListVersion plv = new MPriceListVersion(priceList); + plv.setM_DiscountSchema_ID(101); // Purchase 2001 + plv.setValidFrom(date1); + plv.saveEx(); + + BigDecimal priceInPound = new BigDecimal(0.3023); + MProductPrice pp = new MProductPrice(plv, product.getM_Product_ID(), priceInPound, priceInPound, Env.ZERO); + pp.saveEx(); + + MCurrency usd = MCurrency.get("USD"); // USD + BigDecimal usdToPound1 = new BigDecimal(0.88917098794); + MConversionRate crUsd1 = createConversionRate(britishPound.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, date1, usdToPound1, false); + BigDecimal poundToUsd1 = crUsd1.getMultiplyRate(); + + BigDecimal usdToPound2 = new BigDecimal(0.84225); + MConversionRate crUsd2 = createConversionRate(britishPound.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, date2, usdToPound2, false); + BigDecimal poundToUsd2 = crUsd2.getMultiplyRate(); + + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal poundToEuro1 = new BigDecimal(34.7186); + MConversionRate crEur1 = createConversionRate(britishPound.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date1, poundToEuro1, true); + + BigDecimal poundToEuro2 = new BigDecimal(37.1828); + MConversionRate crEur2 = createConversionRate(britishPound.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date2, poundToEuro2, true); + + try { + MOrder order = createPurchaseOrder(bpartner, date1, priceList.getM_PriceList_ID(), C_ConversionType_ID); + BigDecimal qtyOrdered = new BigDecimal(3); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInPound); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, date1); + BigDecimal qtyInvoiced = new BigDecimal(3); + MInvoiceLine invoiceLine = createAPInvoiceLine(invoice, orderLine, qtyInvoiced); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt = createMMReceipt(order, date2); + BigDecimal qtyDelivered = Env.ONE; + MInOutLine receiptLine = createMMReceiptLine(receipt, orderLine, qtyDelivered); + completeDocument(receipt); + postDocument(receipt); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, poundToEuro2, matchQty, receiptLine); + BigDecimal accountedAmtCr = getAccountedAmount(euro, poundToEuro1, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, poundToUsd2, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, poundToUsd1, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInvoice creditMemo = createAPCreditMemo(order, date2); + BigDecimal qtyCredited = new BigDecimal(2); + MInvoiceLine creditMemoLine = createAPCreditMemoLine(creditMemo, orderLine, qtyCredited); + completeDocument(creditMemo); + postDocument(creditMemo); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyInvoiced.doubleValue(), qtyCredited.doubleValue())); + + accountedAmtCr = getAccountedAmount(euro, poundToEuro1, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(euro, poundToEuro2, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, poundToUsd1, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(usd, poundToUsd2, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(crUsd1); + deleteConversionRate(crUsd2); + deleteConversionRate(crEur1); + deleteConversionRate(crEur2); + + pp.deleteEx(true); + plv.deleteEx(true); + priceList.deleteEx(true); + + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting for credit memo (different period) + * PO Qty1=1000, Qty2=1000, Qty3=1000, Price1=3.00, Price2=2.70, Price3=3.15, Period 1 + * IV Qty1=1000, Qty2=1000, Qty3=1000, Period 1 + * MR Qty1=800, Qty2=700, Qty3=1000, Period 2 + * CM Qty1=200, Qty2=300, Period 3 + */ + public void testCreditMemoPosting_4() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product1 = MProduct.get(Env.getCtx(), 124); // Elm Tree + MProduct product2 = MProduct.get(Env.getCtx(), 123); // Oak Tree + MProduct product3 = MProduct.get(Env.getCtx(), 130); // Plum Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(currentDate.getTime()); + cal.add(Calendar.DAY_OF_MONTH, -2); + Timestamp date1 = new Timestamp(cal.getTimeInMillis()); + cal.setTimeInMillis(currentDate.getTime()); + cal.add(Calendar.DAY_OF_MONTH, -1); + Timestamp date2 = new Timestamp(cal.getTimeInMillis()); + Timestamp date3 = currentDate; + + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd1 = new BigDecimal(30.212666962751); + MConversionRate cr1 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date1, eurToUsd1, false); + + BigDecimal eurToUsd2 = new BigDecimal(31.526248754713); + MConversionRate cr2 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date2, eurToUsd2, false); + + BigDecimal eurToUsd3 = new BigDecimal(29.326631220545); + MConversionRate cr3 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date3, eurToUsd3, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, date1, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur1 = new BigDecimal(3.00); + BigDecimal qtyOrdered1 = new BigDecimal(1000); + MOrderLine orderLine1 = createPurchaseOrderLine(order, 10, product1, qtyOrdered1, priceInEur1); + BigDecimal priceInEur2 = new BigDecimal(2.70); + BigDecimal qtyOrdered2 = new BigDecimal(1000); + MOrderLine orderLine2 = createPurchaseOrderLine(order, 20, product2, qtyOrdered2, priceInEur2); + BigDecimal priceInEur3 = new BigDecimal(3.15); + BigDecimal qtyOrdered3 = new BigDecimal(1000); + MOrderLine orderLine3 = createPurchaseOrderLine(order, 30, product3, qtyOrdered3, priceInEur3); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, date1); + BigDecimal qtyInvoiced1 = qtyOrdered1; + MInvoiceLine invoiceLine1 = createAPInvoiceLine(invoice, orderLine1, qtyInvoiced1); + BigDecimal qtyInvoiced2 = qtyOrdered2; + MInvoiceLine invoiceLine2 = createAPInvoiceLine(invoice, orderLine2, qtyInvoiced2); + BigDecimal qtyInvoiced3 = qtyOrdered3; + MInvoiceLine invoiceLine3 = createAPInvoiceLine(invoice, orderLine3, qtyInvoiced3); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt = createMMReceipt(order, date2); + BigDecimal qtyDelivered1 = new BigDecimal(800); + MInOutLine receiptLine1 = createMMReceiptLine(receipt, orderLine1, qtyDelivered1); + BigDecimal qtyDelivered2 = new BigDecimal(700); + MInOutLine receiptLine2 = createMMReceiptLine(receipt, orderLine2, qtyDelivered2); + BigDecimal qtyDelivered3 = new BigDecimal(1000); + MInOutLine receiptLine3 = createMMReceiptLine(receipt, orderLine3, qtyDelivered3); + completeDocument(receipt); + postDocument(receipt); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine1.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered1.doubleValue(), qtyInvoiced1.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine1); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine1); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd2, matchQty, receiptLine1); + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine1); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine2.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered2.doubleValue(), qtyInvoiced2.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine2); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd2, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine2); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine3.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered3.doubleValue(), qtyInvoiced3.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine3); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine3); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd2, matchQty, receiptLine3); + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine3); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInvoice creditMemo = createAPCreditMemo(order, date3); + BigDecimal qtyCredited1 = new BigDecimal(200); + MInvoiceLine creditMemoLine1 = createAPCreditMemoLine(creditMemo, orderLine1, qtyCredited1); + BigDecimal qtyCredited2 = new BigDecimal(300); + MInvoiceLine creditMemoLine2 = createAPCreditMemoLine(creditMemo, orderLine2, qtyCredited2); + completeDocument(creditMemo); + postDocument(creditMemo); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine1.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyInvoiced1.doubleValue(), qtyCredited1.doubleValue())); + + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine1); + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, creditMemoLine1); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine1); + accountedAmtDr = getAccountedAmount(usd, eurToUsd3, matchQty, creditMemoLine1); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine2.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyInvoiced2.doubleValue(), qtyCredited2.doubleValue())); + + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine2); + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, creditMemoLine2); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine2); + accountedAmtDr = getAccountedAmount(usd, eurToUsd3, matchQty, creditMemoLine2); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr1); + deleteConversionRate(cr2); + deleteConversionRate(cr3); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting for credit memo (same period) + * PO Qty=2, Price=0.1875 + * IV Qty=2 + * MR Qty=1 + * CM Qty=1 + */ + public void testCreditMemoPosting_5() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd = new BigDecimal(30.870771861909); + MConversionRate cr = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, eurToUsd, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur = new BigDecimal(0.1875); + BigDecimal qtyOrdered = new BigDecimal(2); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInEur); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced = new BigDecimal(2); + MInvoiceLine invoiceLine = createAPInvoiceLine(invoice, orderLine, qtyInvoiced); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered = new BigDecimal(1); + MInOutLine receiptLine = createMMReceiptLine(receipt, orderLine, qtyDelivered); + completeDocument(receipt); + postDocument(receipt); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInvoice creditMemo = createAPCreditMemo(order, currentDate); + BigDecimal qtyCredited = new BigDecimal(1); + MInvoiceLine creditMemoLine = createAPCreditMemoLine(creditMemo, orderLine, qtyCredited); + completeDocument(creditMemo); + postDocument(creditMemo); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyInvoiced.doubleValue(), qtyCredited.doubleValue())); + + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + BigDecimal currBalAmt = new BigDecimal(0.01).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + inventoryClearingLineList.add(new PostingLine(usd, currBalAmt, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting for credit memo (same period) + * PO Qty=200, Price=0.1875 + * IV Qty=200 + * MR Qty=100 + * CM Qty=100 + */ + public void testCreditMemoPosting_6() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd = new BigDecimal(30.870771861909); + MConversionRate cr = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, eurToUsd, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur = new BigDecimal(0.1875); + BigDecimal qtyOrdered = new BigDecimal(200); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInEur); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced = new BigDecimal(200); + MInvoiceLine invoiceLine = createAPInvoiceLine(invoice, orderLine, qtyInvoiced); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered = new BigDecimal(100); + MInOutLine receiptLine = createMMReceiptLine(receipt, orderLine, qtyDelivered); + completeDocument(receipt); + postDocument(receipt); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInvoice creditMemo = createAPCreditMemo(order, currentDate); + BigDecimal qtyCredited = new BigDecimal(100); + MInvoiceLine creditMemoLine = createAPCreditMemoLine(creditMemo, orderLine, qtyCredited); + completeDocument(creditMemo); + postDocument(creditMemo); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyInvoiced.doubleValue(), qtyCredited.doubleValue())); + + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + BigDecimal currBalAmt = new BigDecimal(0.01).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + inventoryClearingLineList.add(new PostingLine(usd, currBalAmt, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting for credit memo (same period) + * PO Qty=45, Price=0.3742 + * IV Qty=45 + * MR Qty=1 + * CM Qty=44 + */ + public void testCreditMemoPosting_7() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd = new BigDecimal(30.870771861909); + MConversionRate cr = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, eurToUsd, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur = new BigDecimal(0.3742); + BigDecimal qtyOrdered = new BigDecimal(45); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInEur); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced = new BigDecimal(45); + MInvoiceLine invoiceLine = createAPInvoiceLine(invoice, orderLine, qtyInvoiced); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered = new BigDecimal(1); + MInOutLine receiptLine = createMMReceiptLine(receipt, orderLine, qtyDelivered); + completeDocument(receipt); + postDocument(receipt); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInvoice creditMemo = createAPCreditMemo(order, currentDate); + BigDecimal qtyCredited = new BigDecimal(44); + MInvoiceLine creditMemoLine = createAPCreditMemoLine(creditMemo, orderLine, qtyCredited); + completeDocument(creditMemo); + postDocument(creditMemo); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyInvoiced.doubleValue(), qtyCredited.doubleValue())); + + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting for credit memo (same period + reversal) + * PO Qty=2, Price=0.1875 + * IV Qty=2 + * MR Qty=1 + * MR Qty=1 (Reversed) + * MR Qty=1 (Reversed) + * CM Qty=1 + */ + public void testCreditMemoPosting_8() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd = new BigDecimal(30.870771861909); + MConversionRate cr = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, eurToUsd, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur = new BigDecimal(0.1875); + BigDecimal qtyOrdered = new BigDecimal(2); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInEur); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced = new BigDecimal(2); + MInvoiceLine invoiceLine = createAPInvoiceLine(invoice, orderLine, qtyInvoiced); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt1 = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered = new BigDecimal(1); + MInOutLine receiptLine = createMMReceiptLine(receipt1, orderLine, qtyDelivered); + completeDocument(receipt1); + postDocument(receipt1); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInOut receipt2 = createMMReceipt(order, currentDate); + qtyDelivered = new BigDecimal(1); + receiptLine = createMMReceiptLine(receipt2, orderLine, qtyDelivered); + completeDocument(receipt2); + postDocument(receipt2); + + miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + BigDecimal currBalAmt = new BigDecimal(0.01).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + inventoryClearingLineList.add(new PostingLine(usd, currBalAmt, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + reverseDocument(receipt2); + MInOut receipt3 = new MInOut(Env.getCtx(), receipt2.getReversal_ID(), getTrxName()); + postDocument(receipt3); + + ArrayList miList0 = new ArrayList(); + for (MMatchInv mi : miList) { + mi.load(getTrxName()); + miList0.add(new MMatchInv(Env.getCtx(), mi.getReversal_ID(), getTrxName())); + } + MMatchInv[] miList2 = new MMatchInv[miList0.size()]; + ArrayList notInvoicedReceiptsLineList2 = new ArrayList(); + ArrayList inventoryClearingLineList2 = new ArrayList(); + for (PostingLine notInvoicedReceiptsLine : notInvoicedReceiptsLineList) + notInvoicedReceiptsLineList2.add(new PostingLine(notInvoicedReceiptsLine.currency, notInvoicedReceiptsLine.amtAcctCr, notInvoicedReceiptsLine.amtAcctDr)); + for (PostingLine inventoryClearingLine : inventoryClearingLineList) + inventoryClearingLineList2.add(new PostingLine(inventoryClearingLine.currency, inventoryClearingLine.amtAcctCr, inventoryClearingLine.amtAcctDr)); + testMatchInvoicePosting(ass, miList0.toArray(miList2), notInvoicedReceiptsLineList2, inventoryClearingLineList2); + + MInvoice creditMemo = createAPCreditMemo(order, currentDate); + BigDecimal qtyCredited = new BigDecimal(1); + MInvoiceLine creditMemoLine = createAPCreditMemoLine(creditMemo, orderLine, qtyCredited); + completeDocument(creditMemo); + postDocument(creditMemo); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyInvoiced.doubleValue(), qtyCredited.doubleValue())); + + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, creditMemoLine); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + inventoryClearingLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + currBalAmt = new BigDecimal(0.01).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + inventoryClearingLineList.add(new PostingLine(usd, currBalAmt, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting (same period) + * PO Qty=1200, Price=0.3742 + * IV Qty=1156 + * IV Qty=44 + * MR Qty=1200 + */ + public void testMatReceiptPosting_1() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd = new BigDecimal(30.870771861909); + MConversionRate cr = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, eurToUsd, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur = new BigDecimal(0.3742); + BigDecimal qtyOrdered = new BigDecimal(1200); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInEur); + completeDocument(order); + + MInvoice invoice1 = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced1 = new BigDecimal(1156); + MInvoiceLine invoiceLine1 = createAPInvoiceLine(invoice1, orderLine, qtyInvoiced1); + completeDocument(invoice1); + postDocument(invoice1); + + MInvoice invoice2 = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced2 = new BigDecimal(44); + MInvoiceLine invoiceLine2 = createAPInvoiceLine(invoice2, orderLine, qtyInvoiced2); + completeDocument(invoice2); + postDocument(invoice2); + + MInOut receipt = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered = new BigDecimal(1200); + MInOutLine receiptLine = createMMReceiptLine(receipt, orderLine, qtyDelivered); + completeDocument(receipt); + postDocument(receipt); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine1.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced1.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine1); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine1); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine2.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced2.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine2); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine2); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting (different period) + * PO Qty=1200, Price=0.3742, Period 1 + * IV Qty=1200, Period 1 + * MR Qty=1156, Period 1 + * MR Qty=44, Period 2 + */ + public void testMatReceiptPosting_2() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(currentDate.getTime()); + cal.add(Calendar.DAY_OF_MONTH, -1); + Timestamp date1 = new Timestamp(cal.getTimeInMillis()); + Timestamp date2 = currentDate; + + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd1 = new BigDecimal(30.870771861909); + MConversionRate cr1 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date1, eurToUsd1, false); + + BigDecimal eurToUsd2 = new BigDecimal(31.326259863856); + MConversionRate cr2 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date2, eurToUsd2, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, date1, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur = new BigDecimal(0.3742); + BigDecimal qtyOrdered = new BigDecimal(1200); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInEur); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, date1); + BigDecimal qtyInvoiced = new BigDecimal(1200); + MInvoiceLine invoiceLine = createAPInvoiceLine(invoice, orderLine, qtyInvoiced); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt1 = createMMReceipt(order, date1); + BigDecimal qtyDelivered1 = new BigDecimal(1156); + MInOutLine receiptLine1 = createMMReceiptLine(receipt1, orderLine, qtyDelivered1); + completeDocument(receipt1); + postDocument(receipt1); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine1.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered1.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine1); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd1, matchQty, receiptLine1); + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInOut receipt2 = createMMReceipt(order, date2); + BigDecimal qtyDelivered2 = new BigDecimal(44); + MInOutLine receiptLine2 = createMMReceiptLine(receipt2, orderLine, qtyDelivered2); + completeDocument(receipt2); + postDocument(receipt2); + + miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine2.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered2.doubleValue(), qtyInvoiced.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd2, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr1); + deleteConversionRate(cr2); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting (same period + reversal) + * PO Qty=2, Price=0.1875 + * IV Qty=2 + * MR Qty=1 + * MR Qty=1 (Reversed) + * MR Qty=1 (Reversed) + * MR Qty=1 + */ + public void testMatReceiptPosting_3() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd = new BigDecimal(30.870771861909); + MConversionRate cr = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, eurToUsd, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur = new BigDecimal(0.1875); + BigDecimal qtyOrdered = new BigDecimal(2); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInEur); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced = new BigDecimal(2); + MInvoiceLine invoiceLine = createAPInvoiceLine(invoice, orderLine, qtyInvoiced); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt1 = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered1 = new BigDecimal(1); + MInOutLine receiptLine1 = createMMReceiptLine(receipt1, orderLine, qtyDelivered1); + completeDocument(receipt1); + postDocument(receipt1); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine1.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered1.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine1); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine1); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInOut receipt2 = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered2 = new BigDecimal(1); + MInOutLine receiptLine2 = createMMReceiptLine(receipt2, orderLine, qtyDelivered2); + completeDocument(receipt2); + postDocument(receipt2); + + miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine2.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered2.doubleValue(), qtyInvoiced.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + BigDecimal currBalAmt = new BigDecimal(0.01).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + inventoryClearingLineList.add(new PostingLine(usd, currBalAmt, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + reverseDocument(receipt2); + MInOut receipt3 = new MInOut(Env.getCtx(), receipt2.getReversal_ID(), getTrxName()); + postDocument(receipt3); + + ArrayList miList0 = new ArrayList(); + for (MMatchInv mi : miList) { + mi.load(getTrxName()); + miList0.add(new MMatchInv(Env.getCtx(), mi.getReversal_ID(), getTrxName())); + } + MMatchInv[] miList2 = new MMatchInv[miList0.size()]; + ArrayList notInvoicedReceiptsLineList2 = new ArrayList(); + ArrayList inventoryClearingLineList2 = new ArrayList(); + for (PostingLine notInvoicedReceiptsLine : notInvoicedReceiptsLineList) + notInvoicedReceiptsLineList2.add(new PostingLine(notInvoicedReceiptsLine.currency, notInvoicedReceiptsLine.amtAcctCr, notInvoicedReceiptsLine.amtAcctDr)); + for (PostingLine inventoryClearingLine : inventoryClearingLineList) + inventoryClearingLineList2.add(new PostingLine(inventoryClearingLine.currency, inventoryClearingLine.amtAcctCr, inventoryClearingLine.amtAcctDr)); + testMatchInvoicePosting(ass, miList0.toArray(miList2), notInvoicedReceiptsLineList2, inventoryClearingLineList2); + + MInOut receipt4 = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered4 = new BigDecimal(1); + MInOutLine receiptLine4 = createMMReceiptLine(receipt4, orderLine, qtyDelivered4); + completeDocument(receipt4); + postDocument(receipt4); + + miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine4.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered4.doubleValue(), qtyInvoiced.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine4); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd, matchQty, receiptLine4); + accountedAmtCr = getAccountedAmount(usd, eurToUsd, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + currBalAmt = new BigDecimal(0.01).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + inventoryClearingLineList.add(new PostingLine(usd, currBalAmt, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting (different period + reversal) + * PO Qty=2, Price=0.1875, Period 1 + * IV Qty=2, Period 1 + * MR Qty=1, Period 1 + * MR Qty=1, Period 1 (Reversed) + * MR Qty=1, Period 1 (Reversed) + * MR Qty=1, Period 2 + */ + public void testMatReceiptPosting_4() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(currentDate.getTime()); + cal.add(Calendar.DAY_OF_MONTH, -1); + Timestamp date1 = new Timestamp(cal.getTimeInMillis()); + Timestamp date2 = currentDate; + + int C_ConversionType_ID = 201; // Company + + MCurrency usd = MCurrency.get(100); // USD + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal eurToUsd1 = new BigDecimal(30.870771861909); + MConversionRate cr1 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date1, eurToUsd1, false); + + BigDecimal eurToUsd2 = new BigDecimal(31.326259863856); + MConversionRate cr2 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date2, eurToUsd2, false); + + int M_PriceList_ID = 103; // Export in EUR + + try { + MOrder order = createPurchaseOrder(bpartner, date1, M_PriceList_ID, C_ConversionType_ID); + BigDecimal priceInEur = new BigDecimal(0.1875); + BigDecimal qtyOrdered = new BigDecimal(2); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInEur); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, date1); + BigDecimal qtyInvoiced = new BigDecimal(2); + MInvoiceLine invoiceLine = createAPInvoiceLine(invoice, orderLine, qtyInvoiced); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt1 = createMMReceipt(order, date1); + BigDecimal qtyDelivered1 = new BigDecimal(1); + MInOutLine receiptLine1 = createMMReceiptLine(receipt1, orderLine, qtyDelivered1); + completeDocument(receipt1); + postDocument(receipt1); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine1.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered1.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine1); + BigDecimal accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd1, matchQty, receiptLine1); + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInOut receipt2 = createMMReceipt(order, date1); + BigDecimal qtyDelivered2 = new BigDecimal(1); + MInOutLine receiptLine2 = createMMReceiptLine(receipt2, orderLine, qtyDelivered2); + completeDocument(receipt2); + postDocument(receipt2); + + miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine2.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered2.doubleValue(), qtyInvoiced.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd1, matchQty, receiptLine2); + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + BigDecimal currBalAmt = new BigDecimal(0.01).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + inventoryClearingLineList.add(new PostingLine(usd, currBalAmt, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + reverseDocument(receipt2); + MInOut receipt3 = new MInOut(Env.getCtx(), receipt2.getReversal_ID(), getTrxName()); + postDocument(receipt3); + + ArrayList miList0 = new ArrayList(); + for (MMatchInv mi : miList) { + mi.load(getTrxName()); + miList0.add(new MMatchInv(Env.getCtx(), mi.getReversal_ID(), getTrxName())); + } + MMatchInv[] miList2 = new MMatchInv[miList0.size()]; + ArrayList notInvoicedReceiptsLineList2 = new ArrayList(); + ArrayList inventoryClearingLineList2 = new ArrayList(); + for (PostingLine notInvoicedReceiptsLine : notInvoicedReceiptsLineList) + notInvoicedReceiptsLineList2.add(new PostingLine(notInvoicedReceiptsLine.currency, notInvoicedReceiptsLine.amtAcctCr, notInvoicedReceiptsLine.amtAcctDr)); + for (PostingLine inventoryClearingLine : inventoryClearingLineList) + inventoryClearingLineList2.add(new PostingLine(inventoryClearingLine.currency, inventoryClearingLine.amtAcctCr, inventoryClearingLine.amtAcctDr)); + testMatchInvoicePosting(ass, miList0.toArray(miList2), notInvoicedReceiptsLineList2, inventoryClearingLineList2); + + MInOut receipt4 = createMMReceipt(order, date2); + BigDecimal qtyDelivered4 = new BigDecimal(1); + MInOutLine receiptLine4 = createMMReceiptLine(receipt4, orderLine, qtyDelivered4); + completeDocument(receipt4); + postDocument(receipt4); + + miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine4.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered4.doubleValue(), qtyInvoiced.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, Env.ONE, matchQty, receiptLine4); + accountedAmtCr = getAccountedAmount(euro, Env.ONE, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, eurToUsd2, matchQty, receiptLine4); + accountedAmtCr = getAccountedAmount(usd, eurToUsd1, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + currBalAmt = new BigDecimal(0.01).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + inventoryClearingLineList.add(new PostingLine(usd, currBalAmt, Env.ZERO)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(cr1); + deleteConversionRate(cr2); + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting (same period) + * PO Qty=500, Price=23.32 + * MR Qty=500 + * IV Qty=250 + * IV Qty=250 + */ + public void testMatReceiptPosting_5() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + + int C_ConversionType_ID = 201; // Company + + MPriceList priceList = new MPriceList(Env.getCtx(), 0, null); + priceList.setName("Purchase GBP " + System.currentTimeMillis()); + MCurrency britishPound = MCurrency.get("GBP"); // British Pound (GBP) + priceList.setC_Currency_ID(britishPound.getC_Currency_ID()); + priceList.setPricePrecision(britishPound.getStdPrecision()); + priceList.saveEx(); + + MPriceListVersion plv = new MPriceListVersion(priceList); + plv.setM_DiscountSchema_ID(101); // Purchase 2001 + plv.setValidFrom(currentDate); + plv.saveEx(); + + BigDecimal priceInPound = new BigDecimal(23.32); + MProductPrice pp = new MProductPrice(plv, product.getM_Product_ID(), priceInPound, priceInPound, Env.ZERO); + pp.saveEx(); + + MCurrency usd = MCurrency.get("USD"); // USD + BigDecimal poundToUsd = new BigDecimal(0.676234); + MConversionRate crUsd = createConversionRate(britishPound.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, currentDate, poundToUsd); + + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal poundToEuro = new BigDecimal(22.5062); + MConversionRate crEur = createConversionRate(britishPound.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, poundToEuro); + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, priceList.getM_PriceList_ID(), C_ConversionType_ID); + BigDecimal qtyOrdered = new BigDecimal(500); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInPound); + completeDocument(order); + + MInOut receipt = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered = new BigDecimal(500); + MInOutLine receiptLine = createMMReceiptLine(receipt, orderLine, qtyDelivered); + completeDocument(receipt); + postDocument(receipt); + + MInvoice invoice1 = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced1 = new BigDecimal(250); + MInvoiceLine invoiceLine1 = createAPInvoiceLine(invoice1, orderLine, qtyInvoiced1); + completeDocument(invoice1); + postDocument(invoice1); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine1.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced1.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, poundToEuro, matchQty, receiptLine); + BigDecimal accountedAmtCr = getAccountedAmount(euro, poundToEuro, matchQty, invoiceLine1); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, poundToUsd, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, poundToUsd, matchQty, invoiceLine1); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + MInvoice invoice2 = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced2 = new BigDecimal(250); + MInvoiceLine invoiceLine2 = createAPInvoiceLine(invoice2, orderLine, qtyInvoiced2); + completeDocument(invoice2); + postDocument(invoice2); + + miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine2.get_ID(), getTrxName()); + notInvoicedReceiptsLineList = new ArrayList(); + inventoryClearingLineList = new ArrayList(); + matchQty = new BigDecimal(Math.min(qtyDelivered.doubleValue(), qtyInvoiced2.doubleValue())); + + accountedAmtDr = getAccountedAmount(euro, poundToEuro, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(euro, poundToEuro, matchQty, invoiceLine2); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + BigDecimal currBalAmt = new BigDecimal(0.01).setScale(euro.getStdPrecision(), RoundingMode.HALF_UP); + notInvoicedReceiptsLineList.add(new PostingLine(euro, Env.ZERO, currBalAmt)); + + accountedAmtDr = getAccountedAmount(usd, poundToUsd, matchQty, receiptLine); + accountedAmtCr = getAccountedAmount(usd, poundToUsd, matchQty, invoiceLine2); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + currBalAmt = new BigDecimal(0.01).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + notInvoicedReceiptsLineList.add(new PostingLine(usd, Env.ZERO, currBalAmt)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + } finally { + deleteConversionRate(crUsd); + deleteConversionRate(crEur); + + pp.deleteEx(true); + plv.deleteEx(true); + priceList.deleteEx(true); + + rollback(); + } + } + + @Test + /** + * Test the matched invoice posting (same period + reversal) + * PO Qty=5, Price=65 + * IV Qty=5 + * MR Qty=5 (Reversed) + * MR Qty=5 (Reversed) + */ + public void testMatReceiptPostingWithDiffCurrencyPrecision() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // Elm Tree + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + + int C_ConversionType_ID = 201; // Company + + MPriceList priceList = new MPriceList(Env.getCtx(), 0, null); + priceList.setName("Purchase JPY " + System.currentTimeMillis()); + MCurrency japaneseYen = MCurrency.get("JPY"); // Japanese Yen (JPY) + priceList.setC_Currency_ID(japaneseYen.getC_Currency_ID()); + priceList.setPricePrecision(japaneseYen.getStdPrecision()); + priceList.saveEx(); + + MPriceListVersion plv = new MPriceListVersion(priceList); + plv.setM_DiscountSchema_ID(101); // Purchase 2001 + plv.setValidFrom(currentDate); + plv.saveEx(); + + BigDecimal priceInYen = new BigDecimal(65); + MProductPrice pp = new MProductPrice(plv, product.getM_Product_ID(), priceInYen, priceInYen, Env.ZERO); + pp.saveEx(); + + MCurrency usd = MCurrency.get("USD"); // USD + BigDecimal yenToUsd = new BigDecimal(0.00956427); + MConversionRate crUsd = createConversionRate(japaneseYen.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, currentDate, yenToUsd); + + MCurrency euro = MCurrency.get("EUR"); // EUR + BigDecimal yenToEuro = new BigDecimal(0.29); + MConversionRate crEur = createConversionRate(japaneseYen.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, currentDate, yenToEuro); + + try { + MOrder order = createPurchaseOrder(bpartner, currentDate, priceList.getM_PriceList_ID(), C_ConversionType_ID); + BigDecimal qtyOrdered = new BigDecimal(5); + MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInYen); + completeDocument(order); + + MInvoice invoice = createAPInvoice(order, currentDate); + BigDecimal qtyInvoiced = new BigDecimal(5); + MInvoiceLine invoiceLine = createAPInvoiceLine(invoice, orderLine, qtyInvoiced); + completeDocument(invoice); + postDocument(invoice); + + MInOut receipt1 = createMMReceipt(order, currentDate); + BigDecimal qtyDelivered1 = new BigDecimal(5); + MInOutLine receiptLine1 = createMMReceiptLine(receipt1, orderLine, qtyDelivered1); + completeDocument(receipt1); + postDocument(receipt1); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MMatchInv[] miList = MMatchInv.getInOutLine(Env.getCtx(), receiptLine1.get_ID(), getTrxName()); + ArrayList notInvoicedReceiptsLineList = new ArrayList(); + ArrayList inventoryClearingLineList = new ArrayList(); + BigDecimal matchQty = new BigDecimal(Math.min(qtyDelivered1.doubleValue(), qtyInvoiced.doubleValue())); + + BigDecimal accountedAmtDr = getAccountedAmount(euro, yenToEuro, matchQty, receiptLine1); + BigDecimal accountedAmtCr = getAccountedAmount(euro, yenToEuro, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(euro, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(euro, Env.ZERO, accountedAmtCr)); + + accountedAmtDr = getAccountedAmount(usd, yenToUsd, matchQty, receiptLine1); + accountedAmtCr = getAccountedAmount(usd, yenToUsd, matchQty, invoiceLine); + notInvoicedReceiptsLineList.add(new PostingLine(usd, accountedAmtDr, Env.ZERO)); + inventoryClearingLineList.add(new PostingLine(usd, Env.ZERO, accountedAmtCr)); + + testMatchInvoicePosting(ass, miList, notInvoicedReceiptsLineList, inventoryClearingLineList); + + reverseDocument(receipt1); + MInOut receipt2 = new MInOut(Env.getCtx(), receipt1.getReversal_ID(), getTrxName()); + postDocument(receipt2); + + ArrayList miList0 = new ArrayList(); + for (MMatchInv mi : miList) { + mi.load(getTrxName()); + miList0.add(new MMatchInv(Env.getCtx(), mi.getReversal_ID(), getTrxName())); + } + MMatchInv[] miList2 = new MMatchInv[miList0.size()]; + ArrayList notInvoicedReceiptsLineList2 = new ArrayList(); + ArrayList inventoryClearingLineList2 = new ArrayList(); + for (PostingLine notInvoicedReceiptsLine : notInvoicedReceiptsLineList) + notInvoicedReceiptsLineList2.add(new PostingLine(notInvoicedReceiptsLine.currency, notInvoicedReceiptsLine.amtAcctCr, notInvoicedReceiptsLine.amtAcctDr)); + for (PostingLine inventoryClearingLine : inventoryClearingLineList) + inventoryClearingLineList2.add(new PostingLine(inventoryClearingLine.currency, inventoryClearingLine.amtAcctCr, inventoryClearingLine.amtAcctDr)); + testMatchInvoicePosting(ass, miList0.toArray(miList2), notInvoicedReceiptsLineList2, inventoryClearingLineList2); + } finally { + deleteConversionRate(crUsd); + deleteConversionRate(crEur); + + pp.deleteEx(true); + plv.deleteEx(true); + priceList.deleteEx(true); + rollback(); + } + } + + private MConversionRate createConversionRate(int C_Currency_ID, int C_Currency_ID_To, int C_ConversionType_ID, + Timestamp date, BigDecimal rate) { + return createConversionRate(C_Currency_ID, C_Currency_ID_To, C_ConversionType_ID, date, rate, true); + } + + private MConversionRate createConversionRate(int C_Currency_ID, int C_Currency_ID_To, int C_ConversionType_ID, + Timestamp date, BigDecimal rate, boolean isMultiplyRate) { + MConversionRate cr = new MConversionRate(Env.getCtx(), 0, null); + cr.setC_Currency_ID(C_Currency_ID); + cr.setC_Currency_ID_To(C_Currency_ID_To); + cr.setC_ConversionType_ID(C_ConversionType_ID); + cr.setValidFrom(date); + cr.setValidTo(date); + if (isMultiplyRate) + cr.setMultiplyRate(rate); + else + cr.setDivideRate(rate); + cr.saveEx(); + return cr; + } + + private void deleteConversionRate(MConversionRate cr) { + String whereClause = "ValidFrom=? AND ValidTo=? " + + "AND C_Currency_ID=? AND C_Currency_ID_To=? " + + "AND C_ConversionType_ID=? " + + "AND AD_Client_ID=? AND AD_Org_ID=?"; + MConversionRate reciprocal = new Query(Env.getCtx(), MConversionRate.Table_Name, whereClause, null) + .setParameters(cr.getValidFrom(), cr.getValidTo(), + cr.getC_Currency_ID_To(), cr.getC_Currency_ID(), + cr.getC_ConversionType_ID(), + cr.getAD_Client_ID(), cr.getAD_Org_ID()) + .firstOnly(); + if (reciprocal != null) + reciprocal.deleteEx(true); + cr.deleteEx(true); + } + + private MOrder createPurchaseOrder(MBPartner bpartner, Timestamp date, int M_PriceList_ID, int C_ConversionType_ID) { + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(bpartner); + order.setIsSOTrx(false); + order.setC_DocTypeTarget_ID(); + order.setDateOrdered(date); + order.setDateAcct(date); + order.setM_PriceList_ID(M_PriceList_ID); + order.setC_ConversionType_ID(C_ConversionType_ID); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + order.saveEx(); + return order; + } + + private MOrderLine createPurchaseOrderLine(MOrder order, int line, MProduct product, BigDecimal qty, BigDecimal price) { + MOrderLine orderLine = new MOrderLine(order); + orderLine.setLine(line); + orderLine.setProduct(product); + orderLine.setQty(qty); + orderLine.setPrice(price); + orderLine.saveEx(); + return orderLine; + } + + private void completeDocument(PO po) { + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(po, DocAction.ACTION_Complete); + po.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + String docStatus = (String) po.get_Value("DocStatus"); + assertEquals(DocAction.STATUS_Completed, docStatus, DocAction.STATUS_Completed + " != " + docStatus); + } + + private void reverseDocument(PO po) { + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(po, DocAction.ACTION_Reverse_Correct); + po.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + String docStatus = (String) po.get_Value("DocStatus"); + assertEquals(DocAction.STATUS_Reversed, docStatus, DocAction.STATUS_Reversed + " != " + docStatus); + } + + private void postDocument(PO po) { + if (!po.get_ValueAsBoolean("Posted")) { + String error = DocumentEngine.postImmediate(Env.getCtx(), po.getAD_Client_ID(), po.get_Table_ID(), po.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + po.load(getTrxName()); + assertTrue(po.get_ValueAsBoolean("Posted")); + } + + private MInvoice createAPInvoice(MOrder order, Timestamp date) { + MInvoice invoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + invoice.setOrder(order); + invoice.setDateAcct(date); + 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(); + return invoice; + } + + private MInvoiceLine createAPInvoiceLine(MInvoice invoice, MOrderLine orderLine, BigDecimal qty) { + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setC_OrderLine_ID(orderLine.get_ID()); + invoiceLine.setLine(orderLine.getLine()); + invoiceLine.setProduct(orderLine.getProduct()); + invoiceLine.setQty(qty); + invoiceLine.setPrice(orderLine.getPriceActual()); + invoiceLine.saveEx(); + return invoiceLine; + } + + private MInvoice createAPCreditMemo(MOrder order, Timestamp date) { + MInvoice creditMemo = new MInvoice(Env.getCtx(), 0, getTrxName()); + creditMemo.setOrder(order); + creditMemo.setDateAcct(date); + 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(); + return creditMemo; + } + + private MInvoiceLine createAPCreditMemoLine(MInvoice creditMemo, MOrderLine orderLine, BigDecimal qty) { + MInvoiceLine creditMemoLine = new MInvoiceLine(creditMemo); + creditMemoLine.setC_OrderLine_ID(orderLine.get_ID()); + creditMemoLine.setLine(orderLine.getLine()); + creditMemoLine.setProduct(orderLine.getProduct()); + creditMemoLine.setQty(qty); + creditMemoLine.setPrice(orderLine.getPriceActual()); + creditMemoLine.saveEx(); + return creditMemoLine; + } + + private MInOut createMMReceipt(MOrder order, Timestamp date) { + MInOut receipt = new MInOut(order, 122, date); // MM Receipt + receipt.saveEx(); + return receipt; + } + + private MInOutLine createMMReceiptLine(MInOut receipt, MOrderLine orderLine, BigDecimal qty) { + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setC_OrderLine_ID(orderLine.get_ID()); + receiptLine.setLine(orderLine.getLine()); + receiptLine.setProduct(orderLine.getProduct()); + receiptLine.setQty(qty); + 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(); + return receiptLine; + } + + private void testMatchInvoicePosting(MAcctSchema[] ass, MMatchInv[] miList, ArrayList notInvoicedReceiptsLineList, ArrayList inventoryClearingLineList) { + assertTrue(miList.length > 0); + + if (notInvoicedReceiptsLineList == null) + notInvoicedReceiptsLineList = new ArrayList(); + if (inventoryClearingLineList == null) + inventoryClearingLineList = new ArrayList(); + + HashMap totalNIRAmtByCurrencyId = new HashMap(); + for (PostingLine notInvoicedReceiptsLine : notInvoicedReceiptsLineList) { + BigDecimal totalAmt = totalNIRAmtByCurrencyId.get(notInvoicedReceiptsLine.currency.get_ID()); + if (totalAmt == null) + totalAmt = Env.ZERO; + totalAmt = totalAmt.add(notInvoicedReceiptsLine.amtAcctDr).subtract(notInvoicedReceiptsLine.amtAcctCr); + totalNIRAmtByCurrencyId.put(notInvoicedReceiptsLine.currency.get_ID(), totalAmt); + } + + HashMap totalInvClrAmtByCurrencyId = new HashMap(); + for (PostingLine inventoryClearingLine : inventoryClearingLineList) { + BigDecimal totalAmt = totalInvClrAmtByCurrencyId.get(inventoryClearingLine.currency.get_ID()); + if (totalAmt == null) + totalAmt = Env.ZERO; + totalAmt = totalAmt.add(inventoryClearingLine.amtAcctDr).subtract(inventoryClearingLine.amtAcctCr); + totalInvClrAmtByCurrencyId.put(inventoryClearingLine.currency.get_ID(), totalAmt); + } + + for (MMatchInv mi : miList) { + postDocument(mi); + + for (MAcctSchema as : ass) { + 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); + + BigDecimal totalNIRAmtAcct = Env.ZERO; + BigDecimal totalInvClrAmtAcct = Env.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 + "=" + 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 (acctNIR.getAccount_ID() == fa.getAccount_ID()) + totalNIRAmtAcct = totalNIRAmtAcct.add(fa.getAmtAcctDr()).subtract(fa.getAmtAcctCr()); + else if (acctInvClr.getAccount_ID() == fa.getAccount_ID()) + totalInvClrAmtAcct = totalInvClrAmtAcct.add(fa.getAmtAcctDr()).subtract(fa.getAmtAcctCr()); + } + + BigDecimal totalAmtExpected = totalNIRAmtByCurrencyId.get(as.getC_Currency_ID()); + if (totalAmtExpected != null && totalAmtExpected.compareTo(totalNIRAmtAcct) == 0) + totalNIRAmtByCurrencyId.remove(as.getC_Currency_ID()); + totalAmtExpected = totalInvClrAmtByCurrencyId.get(as.getC_Currency_ID()); + if (totalAmtExpected != null && totalAmtExpected.compareTo(totalInvClrAmtAcct) == 0) + totalInvClrAmtByCurrencyId.remove(as.getC_Currency_ID()); + } + } + + assertTrue(totalNIRAmtByCurrencyId.isEmpty(), totalNIRAmtByCurrencyId.toString()); + assertTrue(totalInvClrAmtByCurrencyId.isEmpty(), totalInvClrAmtByCurrencyId.toString()); + } + + private BigDecimal getSourceAmount(I_C_Currency currency, BigDecimal price, BigDecimal qty) { + return price.multiply(qty).setScale(currency.getStdPrecision(), RoundingMode.HALF_UP); + } + + private BigDecimal getAccountedAmount(MCurrency currency, BigDecimal multiplyRate, BigDecimal miQty, MInOutLine iol) { + BigDecimal sourceAmt = getSourceAmount(iol.getParent().getC_Order().getC_Currency(), iol.getC_OrderLine().getPriceActual(), iol.getMovementQty()); + BigDecimal accountedAmt = sourceAmt.multiply(multiplyRate).setScale(currency.getStdPrecision(), RoundingMode.HALF_UP); + BigDecimal multiplier = miQty.divide(iol.getMovementQty(), 12, RoundingMode.HALF_UP); + return accountedAmt.multiply(multiplier).setScale(currency.getStdPrecision(), RoundingMode.HALF_UP); + } + + private BigDecimal getAccountedAmount(MCurrency currency, BigDecimal multiplyRate, BigDecimal miQty, MInvoiceLine il) { + BigDecimal sourceAmt = getSourceAmount(il.getParent().getC_Order().getC_Currency(), il.getC_OrderLine().getPriceActual(), il.getQtyInvoiced()); + BigDecimal accountedAmt = sourceAmt.multiply(multiplyRate).setScale(currency.getStdPrecision(), RoundingMode.HALF_UP); + BigDecimal multiplier = miQty.divide(il.getQtyInvoiced(), 12, RoundingMode.HALF_UP); + return accountedAmt.multiply(multiplier).setScale(currency.getStdPrecision(), RoundingMode.HALF_UP); + } + + private class PostingLine { + private MCurrency currency; + private BigDecimal amtAcctDr; + private BigDecimal amtAcctCr; + + private PostingLine(MCurrency currency, BigDecimal amtAcctDr, BigDecimal amtAcctCr) { + this.currency = currency; + this.amtAcctDr = amtAcctDr; + this.amtAcctCr = amtAcctCr; + } + + public String toString() { + return currency.toString() + ", " + amtAcctDr + ", " + amtAcctCr + "\n"; + } + } +} \ No newline at end of file