diff --git a/migration/i8.1z/oracle/202011260936_IDEMPIERE-4567.sql b/migration/i8.1z/oracle/202011260936_IDEMPIERE-4567.sql new file mode 100644 index 0000000000..4ab5ce031e --- /dev/null +++ b/migration/i8.1z/oracle/202011260936_IDEMPIERE-4567.sql @@ -0,0 +1,33 @@ +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- IDEMPIERE-4567 Resetting payment allocation to a charge leaves wrong BP Balance +-- Nov 26, 2020, 9:35:38 AM CET +UPDATE C_BPartner bp +SET +SO_CreditUsed = + COALESCE((SELECT SUM(currencyBase(invoiceOpen(i.C_Invoice_ID,i.C_InvoicePaySchedule_ID),i.C_Currency_ID,i.DateInvoiced, i.AD_Client_ID,i.AD_Org_ID)) FROM C_Invoice_v i + WHERE i.C_BPartner_ID=bp.C_BPartner_ID AND i.IsSOTrx='Y' AND i.IsPaid='N' AND i.DocStatus IN ('CO','CL')),0), +TotalOpenBalance = + COALESCE((SELECT SUM(currencyBase(invoiceOpen(i.C_Invoice_ID,i.C_InvoicePaySchedule_ID),i.C_Currency_ID,i.DateInvoiced, i.AD_Client_ID,i.AD_Org_ID)*i.MultiplierAP) FROM C_Invoice_v i + WHERE i.C_BPartner_ID=bp.C_BPartner_ID AND i.IsPaid='N' AND i.DocStatus IN ('CO','CL')),0) - + COALESCE((SELECT SUM(currencyBase(Paymentavailable(p.C_Payment_ID),p.C_Currency_ID,p.DateTrx,p.AD_Client_ID,p.AD_Org_ID)) FROM C_Payment_v p + WHERE p.C_BPartner_ID=bp.C_BPartner_ID AND p.IsAllocated='N' + AND p.C_Charge_ID IS NULL AND p.DocStatus IN ('CO','CL')),0) +WHERE AD_Client_ID = 11 AND ( +SO_CreditUsed != + COALESCE((SELECT SUM(currencyBase(invoiceOpen(i.C_Invoice_ID,i.C_InvoicePaySchedule_ID),i.C_Currency_ID,i.DateInvoiced, i.AD_Client_ID,i.AD_Org_ID)) FROM C_Invoice_v i + WHERE i.C_BPartner_ID=bp.C_BPartner_ID AND i.IsSOTrx='Y' AND i.IsPaid='N' AND i.DocStatus IN ('CO','CL')),0) +OR +TotalOpenBalance != + COALESCE((SELECT SUM(currencyBase(invoiceOpen(i.C_Invoice_ID,i.C_InvoicePaySchedule_ID),i.C_Currency_ID,i.DateInvoiced, i.AD_Client_ID,i.AD_Org_ID)*i.MultiplierAP) FROM C_Invoice_v i + WHERE i.C_BPartner_ID=bp.C_BPartner_ID AND i.IsPaid='N' AND i.DocStatus IN ('CO','CL')),0) - + COALESCE((SELECT SUM(currencyBase(Paymentavailable(p.C_Payment_ID),p.C_Currency_ID,p.DateTrx,p.AD_Client_ID,p.AD_Org_ID)) FROM C_Payment_v p + WHERE p.C_BPartner_ID=bp.C_BPartner_ID AND p.IsAllocated='N' + AND p.C_Charge_ID IS NULL AND p.DocStatus IN ('CO','CL')),0) +) +; + +SELECT register_migration_script('202011260936_IDEMPIERE-4567.sql') FROM dual +; + diff --git a/migration/i8.1z/postgresql/202011260936_IDEMPIERE-4567.sql b/migration/i8.1z/postgresql/202011260936_IDEMPIERE-4567.sql new file mode 100644 index 0000000000..05fbec026b --- /dev/null +++ b/migration/i8.1z/postgresql/202011260936_IDEMPIERE-4567.sql @@ -0,0 +1,30 @@ +-- IDEMPIERE-4567 Resetting payment allocation to a charge leaves wrong BP Balance +-- Nov 26, 2020, 9:35:38 AM CET +UPDATE C_BPartner bp +SET +SO_CreditUsed = + COALESCE((SELECT SUM(currencyBase(invoiceOpen(i.C_Invoice_ID,i.C_InvoicePaySchedule_ID),i.C_Currency_ID,i.DateInvoiced, i.AD_Client_ID,i.AD_Org_ID)) FROM C_Invoice_v i + WHERE i.C_BPartner_ID=bp.C_BPartner_ID AND i.IsSOTrx='Y' AND i.IsPaid='N' AND i.DocStatus IN ('CO','CL')),0), +TotalOpenBalance = + COALESCE((SELECT SUM(currencyBase(invoiceOpen(i.C_Invoice_ID,i.C_InvoicePaySchedule_ID),i.C_Currency_ID,i.DateInvoiced, i.AD_Client_ID,i.AD_Org_ID)*i.MultiplierAP) FROM C_Invoice_v i + WHERE i.C_BPartner_ID=bp.C_BPartner_ID AND i.IsPaid='N' AND i.DocStatus IN ('CO','CL')),0) - + COALESCE((SELECT SUM(currencyBase(Paymentavailable(p.C_Payment_ID),p.C_Currency_ID,p.DateTrx,p.AD_Client_ID,p.AD_Org_ID)) FROM C_Payment_v p + WHERE p.C_BPartner_ID=bp.C_BPartner_ID AND p.IsAllocated='N' + AND p.C_Charge_ID IS NULL AND p.DocStatus IN ('CO','CL')),0) +WHERE AD_Client_ID = 11 AND ( +SO_CreditUsed != + COALESCE((SELECT SUM(currencyBase(invoiceOpen(i.C_Invoice_ID,i.C_InvoicePaySchedule_ID),i.C_Currency_ID,i.DateInvoiced, i.AD_Client_ID,i.AD_Org_ID)) FROM C_Invoice_v i + WHERE i.C_BPartner_ID=bp.C_BPartner_ID AND i.IsSOTrx='Y' AND i.IsPaid='N' AND i.DocStatus IN ('CO','CL')),0) +OR +TotalOpenBalance != + COALESCE((SELECT SUM(currencyBase(invoiceOpen(i.C_Invoice_ID,i.C_InvoicePaySchedule_ID),i.C_Currency_ID,i.DateInvoiced, i.AD_Client_ID,i.AD_Org_ID)*i.MultiplierAP) FROM C_Invoice_v i + WHERE i.C_BPartner_ID=bp.C_BPartner_ID AND i.IsPaid='N' AND i.DocStatus IN ('CO','CL')),0) - + COALESCE((SELECT SUM(currencyBase(Paymentavailable(p.C_Payment_ID),p.C_Currency_ID,p.DateTrx,p.AD_Client_ID,p.AD_Org_ID)) FROM C_Payment_v p + WHERE p.C_BPartner_ID=bp.C_BPartner_ID AND p.IsAllocated='N' + AND p.C_Charge_ID IS NULL AND p.DocStatus IN ('CO','CL')),0) +) +; + +SELECT register_migration_script('202011260936_IDEMPIERE-4567.sql') FROM dual +; + diff --git a/org.adempiere.base/src/org/compiere/model/MAllocationHdr.java b/org.adempiere.base/src/org/compiere/model/MAllocationHdr.java index 9463781960..6729e7925c 100644 --- a/org.adempiere.base/src/org/compiere/model/MAllocationHdr.java +++ b/org.adempiere.base/src/org/compiere/model/MAllocationHdr.java @@ -18,7 +18,6 @@ package org.compiere.model; import java.io.File; import java.math.BigDecimal; -import java.math.RoundingMode; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Timestamp; @@ -56,9 +55,7 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction * */ private static final long serialVersionUID = -7787519874581251920L; - /** Tolerance Gain and Loss */ - private static final BigDecimal TOLERANCE = BigDecimal.valueOf(0.02); - + /** * Get Allocations of Payment * @param ctx context @@ -306,6 +303,8 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction return true; } // beforeSave + private List m_bps_beforeDelete = new ArrayList(); + /** * Before Delete. * @return true if acct was deleted @@ -327,17 +326,32 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction // Unlink getLines(true); - if (!updateBP(true)) - return false; + m_bps_beforeDelete.clear(); for (int i = 0; i < m_lines.length; i++) { MAllocationLine line = m_lines[i]; + int C_BPartner_ID = line.getC_BPartner_ID(); + if (! m_bps_beforeDelete.contains(C_BPartner_ID)) + m_bps_beforeDelete.add(C_BPartner_ID); line.deleteEx(true, trxName); } return true; } // beforeDelete + @Override + protected boolean afterDelete(boolean success) { + if (success) { + for (int C_BPartner_ID : m_bps_beforeDelete) { + MBPartner bpartner = new MBPartner(Env.getCtx(), C_BPartner_ID, get_TrxName()); + bpartner.setTotalOpenBalance(); + bpartner.saveEx(); + } + } + m_bps_beforeDelete.clear(); + return super.afterDelete(success); + } + /** * After Save * @param newRecord @@ -515,7 +529,7 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction // Link getLines(false); - if(!updateBP(isReversal())) + if(!updateBP()) return DocAction.STATUS_Invalid; for (int i = 0; i < m_lines.length; i++) @@ -570,7 +584,7 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction // Set lines to 0 MAllocationLine[] lines = getLines(true); - if(!updateBP(true)) + if(!updateBP()) return false; for (int i = 0; i < lines.length; i++) @@ -894,7 +908,7 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction // Unlink Invoices getLines(true); - if(!updateBP(true)) + if(!updateBP()) return false; for (int i = 0; i < m_lines.length; i++) @@ -918,227 +932,19 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction return true; } // reverse - private boolean updateBP(boolean reverse) + private boolean updateBP() { - + List bps = new ArrayList(); getLines(false); for (MAllocationLine line : m_lines) { - int C_Payment_ID = line.getC_Payment_ID(); int C_BPartner_ID = line.getC_BPartner_ID(); - int M_Invoice_ID = line.getC_Invoice_ID(); - - if ((C_BPartner_ID == 0) || ((M_Invoice_ID == 0) && (C_Payment_ID == 0))) - continue; - - boolean isSOTrxInvoice = false; - MInvoice invoice = M_Invoice_ID > 0 ? new MInvoice (getCtx(), M_Invoice_ID, get_TrxName()) : null; - if (M_Invoice_ID > 0) - isSOTrxInvoice = invoice.isSOTrx(); - - MBPartner bpartner = new MBPartner (getCtx(), line.getC_BPartner_ID(), get_TrxName()); - DB.getDatabase().forUpdate(bpartner, 0); - - BigDecimal allocAmt = line.getAmount().add(line.getDiscountAmt()).add(line.getWriteOffAmt()); - BigDecimal openBalanceDiff = Env.ZERO; - MClient client = MClient.get(getCtx(), getAD_Client_ID()); - - boolean paymentProcessed = false; - boolean paymentIsReceipt = false; - - // Retrieve payment information - if (C_Payment_ID > 0) - { - MPayment payment = null; - int convTypeID = 0; - Timestamp paymentDate = null; - - payment = new MPayment (getCtx(), C_Payment_ID, get_TrxName()); - convTypeID = payment.getC_ConversionType_ID(); - paymentDate = payment.getDateAcct(); - paymentProcessed = payment.isProcessed(); - paymentIsReceipt = payment.isReceipt(); - - // Adjust open amount with allocated amount. - if (paymentProcessed) - { - if (invoice != null) - { - // If payment is already processed, only adjust open balance by discount and write off amounts. - BigDecimal amt = MConversionRate.convertBase(getCtx(), line.getWriteOffAmt().add(line.getDiscountAmt()), - getC_Currency_ID(), paymentDate, convTypeID, getAD_Client_ID(), getAD_Org_ID()); - if (amt == null) - { - m_processMsg = MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingAllocationCurrencyToBaseCurrency", - getC_Currency_ID(), MClient.get(getCtx()).getC_Currency_ID(), convTypeID, paymentDate, get_TrxName()); - return false; - } - openBalanceDiff = openBalanceDiff.add(amt); - } - else - { - // Allocating payment to payment. - BigDecimal amt = MConversionRate.convertBase(getCtx(), allocAmt, - getC_Currency_ID(), paymentDate, convTypeID, getAD_Client_ID(), getAD_Org_ID()); - if (amt == null) - { - m_processMsg = MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingAllocationCurrencyToBaseCurrency", - getC_Currency_ID(), MClient.get(getCtx()).getC_Currency_ID(), convTypeID, paymentDate, get_TrxName()); - return false; - } - openBalanceDiff = openBalanceDiff.add(amt); - } - } else { - // If payment has not been processed, adjust open balance by entire allocated amount. - BigDecimal allocAmtBase = MConversionRate.convertBase(getCtx(), allocAmt, - getC_Currency_ID(), getDateAcct(), convTypeID, getAD_Client_ID(), getAD_Org_ID()); - if (allocAmtBase == null) - { - m_processMsg = MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingAllocationCurrencyToBaseCurrency", - getC_Currency_ID(), MClient.get(getCtx()).getC_Currency_ID(), convTypeID, getDateAcct(), get_TrxName()); - return false; - } - - openBalanceDiff = openBalanceDiff.add(allocAmtBase); - } + if (! bps.contains(C_BPartner_ID)) { + bps.add(C_BPartner_ID); + MBPartner bpartner = new MBPartner(Env.getCtx(), C_BPartner_ID, get_TrxName()); + bpartner.setTotalOpenBalance(); + bpartner.saveEx(); } - else if (invoice != null) - { - // adjust open balance by discount and write off amounts. - BigDecimal amt = MConversionRate.convertBase(getCtx(), allocAmt.negate(), - getC_Currency_ID(), invoice.getDateAcct(), invoice.getC_ConversionType_ID(), getAD_Client_ID(), getAD_Org_ID()); - if (amt == null) - { - m_processMsg = MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingAllocationCurrencyToBaseCurrency", - getC_Currency_ID(), MClient.get(getCtx()).getC_Currency_ID(), invoice.getC_ConversionType_ID(), invoice.getDateAcct(), get_TrxName()); - return false; - } - openBalanceDiff = openBalanceDiff.add(amt); - } - - // Adjust open amount for currency gain/loss - if ((invoice != null) && - ((getC_Currency_ID() != client.getC_Currency_ID()) || - (getC_Currency_ID() != invoice.getC_Currency_ID()))) - { - if (getC_Currency_ID() != invoice.getC_Currency_ID()) - { - allocAmt = MConversionRate.convert(getCtx(), allocAmt, - getC_Currency_ID(), invoice.getC_Currency_ID(), getDateAcct(), invoice.getC_ConversionType_ID(), getAD_Client_ID(), getAD_Org_ID()); - if (allocAmt == null) - { - m_processMsg = MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingAllocationCurrencyToInvoiceCurrency", - getC_Currency_ID(), invoice.getC_Currency_ID(), invoice.getC_ConversionType_ID(), getDateAcct(), get_TrxName()); - return false; - } - } - BigDecimal invAmtAccted = MConversionRate.convertBase(getCtx(), invoice.getGrandTotal(), - invoice.getC_Currency_ID(), invoice.getDateAcct(), invoice.getC_ConversionType_ID(), getAD_Client_ID(), getAD_Org_ID()); - if (invAmtAccted == null) - { - m_processMsg = MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingInvoiceCurrencyToBaseCurrency", - invoice.getC_Currency_ID(), MClient.get(getCtx()).getC_Currency_ID(), invoice.getC_ConversionType_ID(), invoice.getDateAcct(), get_TrxName()); - return false; - } - - BigDecimal allocAmtAccted = MConversionRate.convertBase(getCtx(), allocAmt, - invoice.getC_Currency_ID(), getDateAcct(), invoice.getC_ConversionType_ID(), getAD_Client_ID(), getAD_Org_ID()); - if (allocAmtAccted == null) - { - m_processMsg = MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingInvoiceCurrencyToBaseCurrency", - invoice.getC_Currency_ID(), MClient.get(getCtx()).getC_Currency_ID(), invoice.getC_ConversionType_ID(), getDateAcct(), get_TrxName()); - return false; - } - - if (allocAmt.compareTo(invoice.getGrandTotal()) == 0) - { - openBalanceDiff = openBalanceDiff.add(invAmtAccted).subtract(allocAmtAccted); - } - else - { - // allocation as a percentage of the invoice - double multiplier = allocAmt.doubleValue() / invoice.getGrandTotal().doubleValue(); - // Reduce Orig Invoice Accounted - invAmtAccted = invAmtAccted.multiply(BigDecimal.valueOf(multiplier)); - // Difference based on percentage of Orig Invoice - openBalanceDiff = openBalanceDiff.add(invAmtAccted).subtract(allocAmtAccted); // gain is negative - // ignore Tolerance - if (openBalanceDiff.abs().compareTo(TOLERANCE) < 0) - openBalanceDiff = Env.ZERO; - // Round - int precision = MCurrency.getStdPrecision(getCtx(), client.getC_Currency_ID()); - if (openBalanceDiff.scale() > precision) - openBalanceDiff = openBalanceDiff.setScale(precision, RoundingMode.HALF_UP); - } - } - - // Total Balance - BigDecimal newBalance = bpartner.getTotalOpenBalance(); - if (newBalance == null) - newBalance = Env.ZERO; - - BigDecimal originalBalance = new BigDecimal(newBalance.toString()); - - if (openBalanceDiff.signum() != 0) - { - if (reverse) - newBalance = newBalance.add(openBalanceDiff); - else - newBalance = newBalance.subtract(openBalanceDiff); - } - - // Update BP Credit Used only for Customer Invoices and for payment-to-payment allocations. - BigDecimal newCreditAmt = Env.ZERO; - if (isSOTrxInvoice || (invoice == null && paymentIsReceipt && paymentProcessed)) - { - if (invoice == null) - openBalanceDiff = openBalanceDiff.negate(); - - newCreditAmt = bpartner.getSO_CreditUsed(); - - if(reverse) - { - if (newCreditAmt == null) - newCreditAmt = openBalanceDiff; - else - newCreditAmt = newCreditAmt.add(openBalanceDiff); - } - else - { - if (newCreditAmt == null) - newCreditAmt = openBalanceDiff.negate(); - else - newCreditAmt = newCreditAmt.subtract(openBalanceDiff); - } - - if (log.isLoggable(Level.FINE)) - { - log.fine("TotalOpenBalance=" + bpartner.getTotalOpenBalance() + "(" + openBalanceDiff - + ", Credit=" + bpartner.getSO_CreditUsed() + "->" + newCreditAmt - + ", Balance=" + bpartner.getTotalOpenBalance() + " -> " + newBalance); - } - bpartner.setSO_CreditUsed(newCreditAmt); - } - else - { - if (log.isLoggable(Level.FINE)) - { - log.fine("TotalOpenBalance=" + bpartner.getTotalOpenBalance() + "(" + openBalanceDiff - + ", Balance=" + bpartner.getTotalOpenBalance() + " -> " + newBalance); - } - } - - if (newBalance.compareTo(originalBalance) != 0) - bpartner.setTotalOpenBalance(newBalance); - - bpartner.setSOCreditStatus(); - if (!bpartner.save(get_TrxName())) - { - m_processMsg = "Could not update Business Partner"; - return false; - } - } // for all lines - return true; } // updateBP diff --git a/org.adempiere.base/src/org/compiere/model/MBPartner.java b/org.adempiere.base/src/org/compiere/model/MBPartner.java index 535867e181..024c01599d 100644 --- a/org.adempiere.base/src/org/compiere/model/MBPartner.java +++ b/org.adempiere.base/src/org/compiere/model/MBPartner.java @@ -710,6 +710,7 @@ public class MBPartner extends X_C_BPartner implements ImmutablePOSupport */ public void setTotalOpenBalance () { + log.info(""); BigDecimal SO_CreditUsed = null; BigDecimal TotalOpenBalance = null; //AZ Goodwill -> BF2041226 : only count completed/closed docs. diff --git a/org.adempiere.ui/src/org/compiere/apps/form/Allocation.java b/org.adempiere.ui/src/org/compiere/apps/form/Allocation.java index 70db2235e3..a4026baa71 100644 --- a/org.adempiere.ui/src/org/compiere/apps/form/Allocation.java +++ b/org.adempiere.ui/src/org/compiere/apps/form/Allocation.java @@ -27,7 +27,6 @@ import org.adempiere.exceptions.AdempiereException; import org.compiere.minigrid.IMiniTable; import org.compiere.model.MAllocationHdr; import org.compiere.model.MAllocationLine; -import org.compiere.model.MBPartner; import org.compiere.model.MDocType; import org.compiere.model.MInvoice; import org.compiere.model.MPayment; @@ -808,9 +807,6 @@ public class Allocation if (log.isLoggable(Level.CONFIG)) log.config("Payment #" + i + (pay.isAllocated() ? " not" : " is") + " fully allocated"); } - MBPartner bpartner = new MBPartner(Env.getCtx(), m_C_BPartner_ID, trxName); - bpartner.setTotalOpenBalance(); - bpartner.saveEx(); paymentList.clear(); amountList.clear(); diff --git a/org.idempiere.test/src/org/idempiere/test/model/AllocationTest.java b/org.idempiere.test/src/org/idempiere/test/model/AllocationTest.java new file mode 100644 index 0000000000..90d0d6de1b --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/model/AllocationTest.java @@ -0,0 +1,336 @@ +/*********************************************************************** + * 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: * + * - Carlos Ruiz - globalqss * + **********************************************************************/ +package org.idempiere.test.model; + +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.sql.Timestamp; +import java.util.Properties; +import java.util.logging.LogRecord; + +import org.compiere.model.MAllocationHdr; +import org.compiere.model.MAllocationLine; +import org.compiere.model.MBPartner; +import org.compiere.model.MDocType; +import org.compiere.model.MInvoice; +import org.compiere.model.MInvoiceLine; +import org.compiere.model.MPayment; +import org.compiere.process.DocAction; +import org.compiere.process.ProcessInfo; +import org.compiere.util.CLogErrorBuffer; +import org.compiere.util.Env; +import org.compiere.util.TimeUtil; +import org.compiere.wf.MWorkflow; +import org.idempiere.test.AbstractTestCase; +import org.junit.jupiter.api.Test; + +/** + * @author Carlos Ruiz - globalqss + * + */ +public class AllocationTest extends AbstractTestCase { + + /** + * + */ + public AllocationTest() { + } + + final static int BP_C_AND_W = 117; + final static int BP_TREEFARM = 114; + + final static int PAYMENT_TERM_IMMEDIATE = 105; + final static int CHARGE_FREIGHT = 200000; + final static int CURRENCY_USD = 100; + final static int BANK_ACCOUNT_1234 = 100; + + /** + * https://idempiere.atlassian.net/browse/IDEMPIERE-4567 + */ + @Test + public void testAllocateCharge() { + int severeCount = 0; + LogRecord[] errorLogs = CLogErrorBuffer.get(true).getRecords(true); + if (errorLogs != null) + severeCount = errorLogs.length; + + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + // Get the OpenBalance of C&W + MBPartner bpartner = new MBPartner(ctx, BP_C_AND_W, trxName); + BigDecimal initialBalance = bpartner.getTotalOpenBalance(); + + // Pay $100 + MPayment payment1 = new MPayment(ctx, 0, trxName); + payment1.setC_BPartner_ID(BP_C_AND_W); + payment1.setC_DocType_ID(true); // Receipt + payment1.setDocStatus(DocAction.STATUS_Drafted); + payment1.setDocAction(DocAction.ACTION_Complete); + payment1.setPayAmt(Env.ONEHUNDRED); + payment1.setTenderType(MPayment.TENDERTYPE_Check); + payment1.setC_BankAccount_ID(BANK_ACCOUNT_1234); + payment1.setC_Currency_ID(CURRENCY_USD); + payment1.setDateTrx(TimeUtil.getDay(null)); + payment1.setDateAcct(TimeUtil.getDay(null)); + payment1.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(payment1, DocAction.ACTION_Complete); + payment1.load(trxName); + assertFalse(info.isError(), "Error processing payment: " + info.getSummary()); + assertEquals(DocAction.STATUS_Completed, payment1.getDocStatus(), "Payment document status is not completed: " + payment1.getDocStatus()); + assertTrue(payment1.isPosted(), "Payment not posted"); + + bpartner.load(trxName); + BigDecimal actualBalance = bpartner.getTotalOpenBalance(); + assertTrue(actualBalance.compareTo(initialBalance.subtract(Env.ONEHUNDRED)) == 0); + + // Create allocation to allocate payment to charge + MAllocationHdr alloc = new MAllocationHdr (Env.getCtx(), true, // manual + payment1.getDateTrx(), CURRENCY_USD, Env.getContext(Env.getCtx(), "#AD_User_Name"), trxName); + alloc.setAD_Org_ID(payment1.getAD_Org_ID()); + int doctypeAlloc = MDocType.getDocType("CMA"); + alloc.setC_DocType_ID(doctypeAlloc); + alloc.setDescription(alloc.getDescriptionForManualAllocation(payment1.getC_BPartner_ID(), trxName)); + alloc.saveEx(); + + MAllocationLine aLine1 = new MAllocationLine (alloc, Env.ONEHUNDRED, + Env.ZERO, Env.ZERO, Env.ZERO); + aLine1.setDocInfo(BP_C_AND_W, 0, 0); + aLine1.setPaymentInfo(payment1.getC_Payment_ID(), 0); + aLine1.saveEx(); + + MAllocationLine aLine2 = new MAllocationLine (alloc, Env.ONEHUNDRED.negate(), + Env.ZERO, Env.ZERO, Env.ZERO); + aLine2.setC_Charge_ID(CHARGE_FREIGHT); + aLine2.setC_BPartner_ID(BP_C_AND_W); + aLine2.saveEx(); + + assertTrue(alloc.processIt(DocAction.ACTION_Complete)); + alloc.saveEx(); + + payment1.load(trxName); + assertTrue(payment1.isAllocated(), "Payment not allocated"); + + bpartner.load(trxName); + actualBalance = bpartner.getTotalOpenBalance(); + assertTrue(actualBalance.compareTo(initialBalance) == 0); + + // Delete Allocation + MAllocationHdr[] alloc1 = MAllocationHdr.getOfPayment(ctx, payment1.getC_Payment_ID(), trxName); + assertTrue(alloc1[0].delete(true, trxName)); + + payment1.load(trxName); + assertFalse(payment1.isAllocated(), "Payment not allocated"); + + bpartner.load(trxName); + actualBalance = bpartner.getTotalOpenBalance(); + assertTrue(actualBalance.compareTo(initialBalance.subtract(Env.ONEHUNDRED)) == 0); + + errorLogs = CLogErrorBuffer.get(true).getRecords(true); + if (errorLogs != null) + assertEquals(severeCount, errorLogs.length, "Severe errors recorded in log: " + errorLogs.length); + + rollback(); + } + + @Test + public void testAllocateCustomerInvoice() { + int severeCount = 0; + LogRecord[] errorLogs = CLogErrorBuffer.get(true).getRecords(true); + if (errorLogs != null) + severeCount = errorLogs.length; + + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + // Get the OpenBalance of C&W + MBPartner bpartner = new MBPartner(ctx, BP_C_AND_W, trxName); + BigDecimal initialBalance = bpartner.getTotalOpenBalance(); + + // Create Invoice $100 + MInvoice invoice = new MInvoice(ctx, 0, trxName); + invoice.setBPartner(MBPartner.get(ctx, BP_C_AND_W)); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_ARInvoice); + invoice.setC_DocType_ID(invoice.getC_DocTypeTarget_ID()); // required to avoid runDocumentActionWorkflow exception + invoice.setPaymentRule(MInvoice.PAYMENTRULE_Check); + invoice.setC_PaymentTerm_ID(PAYMENT_TERM_IMMEDIATE); // Immediate + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + invoice.setDateInvoiced(today); + invoice.setDateAcct(today); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine line1 = new MInvoiceLine(invoice); + line1.setLine(10); + line1.setC_Charge_ID(CHARGE_FREIGHT); + line1.setQty(new BigDecimal("1")); + line1.setPrice(Env.ONEHUNDRED); + line1.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(trxName); + assertFalse(info.isError(), "Error processing invoice: " + info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus(), "Invoice document status is not completed: " + invoice.getDocStatus()); + assertTrue(Env.ONEHUNDRED.compareTo(invoice.getGrandTotal()) == 0, "Invoice grand total not as expected: " + invoice.getGrandTotal().toPlainString()); + assertTrue(invoice.isPosted(), "Invoice not posted"); + + bpartner.load(trxName); + BigDecimal actualBalance = bpartner.getTotalOpenBalance(); + assertTrue(actualBalance.compareTo(initialBalance.add(Env.ONEHUNDRED)) == 0); + + // Pay $100 + MPayment payment1 = new MPayment(ctx, 0, trxName); + payment1.setC_Invoice_ID(invoice.getC_Invoice_ID()); + payment1.setC_BPartner_ID(invoice.getC_BPartner_ID()); + payment1.setC_DocType_ID(true); // Receipt + payment1.setDocStatus(DocAction.STATUS_Drafted); + payment1.setDocAction(DocAction.ACTION_Complete); + payment1.setPayAmt(Env.ONEHUNDRED); + payment1.setTenderType(MPayment.TENDERTYPE_Check); + payment1.setC_BankAccount_ID(BANK_ACCOUNT_1234); + payment1.setC_Currency_ID(CURRENCY_USD); + payment1.setDateTrx(invoice.getDateInvoiced()); + payment1.setDateAcct(invoice.getDateInvoiced()); + payment1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(payment1, DocAction.ACTION_Complete); + payment1.load(trxName); + assertFalse(info.isError(), "Error processing payment: " + info.getSummary()); + assertEquals(DocAction.STATUS_Completed, payment1.getDocStatus(), "Payment document status is not completed: " + payment1.getDocStatus()); + assertEquals(false, invoice.isPaid(), "Invoice isPaid() is not false"); + assertTrue(payment1.isPosted(), "Payment not posted"); + + bpartner.load(trxName); + actualBalance = bpartner.getTotalOpenBalance(); + assertTrue(actualBalance.compareTo(initialBalance) == 0); + + // Delete Allocation + MAllocationHdr[] alloc1 = MAllocationHdr.getOfPayment(ctx, payment1.getC_Payment_ID(), trxName); + assertTrue(alloc1[0].delete(true, trxName)); + + bpartner.load(trxName); + actualBalance = bpartner.getTotalOpenBalance(); + assertTrue(actualBalance.compareTo(initialBalance) == 0); + + errorLogs = CLogErrorBuffer.get(true).getRecords(true); + if (errorLogs != null) + assertEquals(severeCount, errorLogs.length, "Severe errors recorded in log: " + errorLogs.length); + + rollback(); + } + + @Test + public void testAllocateVendorInvoice() { + int severeCount = 0; + LogRecord[] errorLogs = CLogErrorBuffer.get(true).getRecords(true); + if (errorLogs != null) + severeCount = errorLogs.length; + + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + // Get the OpenBalance of C&W + MBPartner bpartner = new MBPartner(ctx, BP_TREEFARM, trxName); + BigDecimal initialBalance = bpartner.getTotalOpenBalance(); + + // Create Invoice $100 + MInvoice invoice = new MInvoice(ctx, 0, trxName); + invoice.setBPartner(MBPartner.get(ctx, BP_TREEFARM)); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + invoice.setC_DocType_ID(invoice.getC_DocTypeTarget_ID()); // required to avoid runDocumentActionWorkflow exception + invoice.setPaymentRule(MInvoice.PAYMENTRULE_Check); + invoice.setC_PaymentTerm_ID(PAYMENT_TERM_IMMEDIATE); // Immediate + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + invoice.setDateInvoiced(today); + invoice.setDateAcct(today); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine line1 = new MInvoiceLine(invoice); + line1.setLine(10); + line1.setC_Charge_ID(CHARGE_FREIGHT); + line1.setQty(new BigDecimal("1")); + line1.setPrice(Env.ONEHUNDRED); + line1.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(trxName); + assertFalse(info.isError(), "Error processing invoice: " + info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus(), "Invoice document status is not completed: " + invoice.getDocStatus()); + assertTrue(Env.ONEHUNDRED.compareTo(invoice.getGrandTotal()) == 0, "Invoice grand total not as expected: " + invoice.getGrandTotal().toPlainString()); + assertTrue(invoice.isPosted(), "Invoice not posted"); + + bpartner.load(trxName); + BigDecimal actualBalance = bpartner.getTotalOpenBalance(); + assertTrue(actualBalance.compareTo(initialBalance.subtract(Env.ONEHUNDRED)) == 0); + + // Pay $100 + MPayment payment1 = new MPayment(ctx, 0, trxName); + payment1.setC_Invoice_ID(invoice.getC_Invoice_ID()); + payment1.setC_BPartner_ID(invoice.getC_BPartner_ID()); + payment1.setC_DocType_ID(false); // Payment + payment1.setDocStatus(DocAction.STATUS_Drafted); + payment1.setDocAction(DocAction.ACTION_Complete); + payment1.setPayAmt(Env.ONEHUNDRED); + payment1.setTenderType(MPayment.TENDERTYPE_Check); + payment1.setC_BankAccount_ID(BANK_ACCOUNT_1234); + payment1.setC_Currency_ID(CURRENCY_USD); + payment1.setDateTrx(invoice.getDateInvoiced()); + payment1.setDateAcct(invoice.getDateInvoiced()); + payment1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(payment1, DocAction.ACTION_Complete); + payment1.load(trxName); + assertFalse(info.isError(), "Error processing payment: " + info.getSummary()); + assertEquals(DocAction.STATUS_Completed, payment1.getDocStatus(), "Payment document status is not completed: " + payment1.getDocStatus()); + assertEquals(false, invoice.isPaid(), "Invoice isPaid() is not false"); + assertTrue(payment1.isPosted(), "Payment not posted"); + + bpartner.load(trxName); + actualBalance = bpartner.getTotalOpenBalance(); + assertTrue(actualBalance.compareTo(initialBalance) == 0); + + // Delete Allocation + MAllocationHdr[] alloc1 = MAllocationHdr.getOfPayment(ctx, payment1.getC_Payment_ID(), trxName); + assertTrue(alloc1[0].delete(true, trxName)); + + bpartner.load(trxName); + actualBalance = bpartner.getTotalOpenBalance(); + assertTrue(actualBalance.compareTo(initialBalance) == 0); + + errorLogs = CLogErrorBuffer.get(true).getRecords(true); + if (errorLogs != null) + assertEquals(severeCount, errorLogs.length, "Severe errors recorded in log: " + errorLogs.length); + + rollback(); + } + +}