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:
parent
6eeabc49ba
commit
460f7116a2
|
@ -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
|
||||
;
|
||||
|
|
@ -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
|
||||
;
|
||||
|
|
@ -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<Integer> m_bps_beforeDelete = new ArrayList<Integer>();
|
||||
|
||||
/**
|
||||
* 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<Integer> bps = new ArrayList<Integer>();
|
||||
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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue