IDEMPIERE-4567 Resetting payment allocation to a charge leaves wrong … (#414)

* IDEMPIERE-4567 Resetting payment allocation to a charge leaves wrong BP Balance

MAllocationHdr.updateBP was broken, the form Payment Allocation worked just because it was calling bpartner.setTotalOpenBalance() at the end
so, I refactored the MAllocationHdr.updateBP and before/afterDelete to call bpartner.setTotalOpenBalance instead if the broken algorithm

Unit test added for allocating and deleting a customer invoice, a vendor invoice, and a charge

* IDEMPIERE-4567 Resetting payment allocation to a charge leaves wrong BP Balance

- Fix peer review changes requested by @hengsin
- Detected double call to bpartner.setTotalOpenBalance in WAllocation
- Detected some business partners in GardenWorld with wrong TotalOpenBalance or SO_CreditUsed, added migration script to fix it, it made more complicate to write unit tests as the data was wrong
This commit is contained in:
Carlos Ruiz 2020-11-26 10:08:05 +01:00 committed by GitHub
parent 6eeabc49ba
commit 460f7116a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 430 additions and 228 deletions

View File

@ -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
;

View File

@ -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
;

View File

@ -18,7 +18,6 @@ package org.compiere.model;
import java.io.File; import java.io.File;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.Timestamp; import java.sql.Timestamp;
@ -56,9 +55,7 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction
* *
*/ */
private static final long serialVersionUID = -7787519874581251920L; private static final long serialVersionUID = -7787519874581251920L;
/** Tolerance Gain and Loss */
private static final BigDecimal TOLERANCE = BigDecimal.valueOf(0.02);
/** /**
* Get Allocations of Payment * Get Allocations of Payment
* @param ctx context * @param ctx context
@ -306,6 +303,8 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction
return true; return true;
} // beforeSave } // beforeSave
private List<Integer> m_bps_beforeDelete = new ArrayList<Integer>();
/** /**
* Before Delete. * Before Delete.
* @return true if acct was deleted * @return true if acct was deleted
@ -327,17 +326,32 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction
// Unlink // Unlink
getLines(true); getLines(true);
if (!updateBP(true))
return false;
m_bps_beforeDelete.clear();
for (int i = 0; i < m_lines.length; i++) for (int i = 0; i < m_lines.length; i++)
{ {
MAllocationLine line = m_lines[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); line.deleteEx(true, trxName);
} }
return true; return true;
} // beforeDelete } // 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 * After Save
* @param newRecord * @param newRecord
@ -515,7 +529,7 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction
// Link // Link
getLines(false); getLines(false);
if(!updateBP(isReversal())) if(!updateBP())
return DocAction.STATUS_Invalid; return DocAction.STATUS_Invalid;
for (int i = 0; i < m_lines.length; i++) 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 // Set lines to 0
MAllocationLine[] lines = getLines(true); MAllocationLine[] lines = getLines(true);
if(!updateBP(true)) if(!updateBP())
return false; return false;
for (int i = 0; i < lines.length; i++) for (int i = 0; i < lines.length; i++)
@ -894,7 +908,7 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction
// Unlink Invoices // Unlink Invoices
getLines(true); getLines(true);
if(!updateBP(true)) if(!updateBP())
return false; return false;
for (int i = 0; i < m_lines.length; i++) for (int i = 0; i < m_lines.length; i++)
@ -918,227 +932,19 @@ public class MAllocationHdr extends X_C_AllocationHdr implements DocAction
return true; return true;
} // reverse } // reverse
private boolean updateBP(boolean reverse) private boolean updateBP()
{ {
List<Integer> bps = new ArrayList<Integer>();
getLines(false); getLines(false);
for (MAllocationLine line : m_lines) { for (MAllocationLine line : m_lines) {
int C_Payment_ID = line.getC_Payment_ID();
int C_BPartner_ID = line.getC_BPartner_ID(); int C_BPartner_ID = line.getC_BPartner_ID();
int M_Invoice_ID = line.getC_Invoice_ID(); if (! bps.contains(C_BPartner_ID)) {
bps.add(C_BPartner_ID);
if ((C_BPartner_ID == 0) || ((M_Invoice_ID == 0) && (C_Payment_ID == 0))) MBPartner bpartner = new MBPartner(Env.getCtx(), C_BPartner_ID, get_TrxName());
continue; bpartner.setTotalOpenBalance();
bpartner.saveEx();
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);
}
} }
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 } // for all lines
return true; return true;
} // updateBP } // updateBP

View File

@ -710,6 +710,7 @@ public class MBPartner extends X_C_BPartner implements ImmutablePOSupport
*/ */
public void setTotalOpenBalance () public void setTotalOpenBalance ()
{ {
log.info("");
BigDecimal SO_CreditUsed = null; BigDecimal SO_CreditUsed = null;
BigDecimal TotalOpenBalance = null; BigDecimal TotalOpenBalance = null;
//AZ Goodwill -> BF2041226 : only count completed/closed docs. //AZ Goodwill -> BF2041226 : only count completed/closed docs.

View File

@ -27,7 +27,6 @@ import org.adempiere.exceptions.AdempiereException;
import org.compiere.minigrid.IMiniTable; import org.compiere.minigrid.IMiniTable;
import org.compiere.model.MAllocationHdr; import org.compiere.model.MAllocationHdr;
import org.compiere.model.MAllocationLine; import org.compiere.model.MAllocationLine;
import org.compiere.model.MBPartner;
import org.compiere.model.MDocType; import org.compiere.model.MDocType;
import org.compiere.model.MInvoice; import org.compiere.model.MInvoice;
import org.compiere.model.MPayment; 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") if (log.isLoggable(Level.CONFIG)) log.config("Payment #" + i + (pay.isAllocated() ? " not" : " is")
+ " fully allocated"); + " fully allocated");
} }
MBPartner bpartner = new MBPartner(Env.getCtx(), m_C_BPartner_ID, trxName);
bpartner.setTotalOpenBalance();
bpartner.saveEx();
paymentList.clear(); paymentList.clear();
amountList.clear(); amountList.clear();

View File

@ -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();
}
}