IDEMPIERE-4696 Wrong GL Currency Balancing in payment allocation for payment reverse/accrual (#621)
* IDEMPIERE-4696 Wrong GL Currency Balancing in payment allocation for payment reverse/accrual
This commit is contained in:
parent
61478ad594
commit
27f21e0384
|
@ -510,6 +510,9 @@ public class Doc_AllocationHdr extends Doc
|
|||
fact.remove(factline);
|
||||
}
|
||||
}
|
||||
|
||||
if (getC_Currency_ID() != as.getC_Currency_ID())
|
||||
balanceAccounting(as, fact);
|
||||
|
||||
// reset line info
|
||||
setC_BPartner_ID(0);
|
||||
|
@ -1718,6 +1721,37 @@ public class Doc_AllocationHdr extends Doc
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Balance Accounting
|
||||
* @param as accounting schema
|
||||
* @param fact
|
||||
* @return
|
||||
*/
|
||||
private FactLine balanceAccounting(MAcctSchema as, Fact fact)
|
||||
{
|
||||
FactLine line = null;
|
||||
if (!fact.isAcctBalanced())
|
||||
{
|
||||
MAccount gain = MAccount.get(as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
|
||||
MAccount loss = MAccount.get(as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
|
||||
|
||||
BigDecimal totalAmtAcctDr = Env.ZERO;
|
||||
BigDecimal totalAmtAcctCr = Env.ZERO;
|
||||
for (FactLine factLine : fact.getLines())
|
||||
{
|
||||
totalAmtAcctDr = totalAmtAcctDr.add(factLine.getAmtAcctDr());
|
||||
totalAmtAcctCr = totalAmtAcctCr.add(factLine.getAmtAcctCr());
|
||||
}
|
||||
|
||||
BigDecimal acctDifference = totalAmtAcctDr.subtract(totalAmtAcctCr);
|
||||
if (as.isCurrencyBalancing() && acctDifference.abs().compareTo(TOLERANCE) < 0)
|
||||
line = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference.negate());
|
||||
else
|
||||
line = fact.createLine(null, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
|
||||
}
|
||||
return line;
|
||||
}
|
||||
} // Doc_Allocation
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,17 +30,29 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Calendar;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import org.compiere.acct.Doc;
|
||||
import org.compiere.acct.DocManager;
|
||||
import org.compiere.model.MAccount;
|
||||
import org.compiere.model.MAcctSchema;
|
||||
import org.compiere.model.MAllocationHdr;
|
||||
import org.compiere.model.MAllocationLine;
|
||||
import org.compiere.model.MBPartner;
|
||||
import org.compiere.model.MBankAccount;
|
||||
import org.compiere.model.MConversionRate;
|
||||
import org.compiere.model.MCurrency;
|
||||
import org.compiere.model.MDocType;
|
||||
import org.compiere.model.MFactAcct;
|
||||
import org.compiere.model.MInvoice;
|
||||
import org.compiere.model.MInvoiceLine;
|
||||
import org.compiere.model.MPayment;
|
||||
import org.compiere.model.PO;
|
||||
import org.compiere.model.Query;
|
||||
import org.compiere.process.DocAction;
|
||||
import org.compiere.process.DocumentEngine;
|
||||
import org.compiere.process.ProcessInfo;
|
||||
import org.compiere.util.CLogErrorBuffer;
|
||||
import org.compiere.util.Env;
|
||||
|
@ -333,4 +345,268 @@ public class AllocationTest extends AbstractTestCase {
|
|||
rollback();
|
||||
}
|
||||
|
||||
@Test
|
||||
/**
|
||||
* https://idempiere.atlassian.net/browse/IDEMPIERE-4696
|
||||
*/
|
||||
public void testPaymentReversePosting() {
|
||||
MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc.
|
||||
Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date");
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTimeInMillis(currentDate.getTime());
|
||||
cal.add(Calendar.DAY_OF_MONTH, -1);
|
||||
Timestamp date1 = new Timestamp(cal.getTimeInMillis());
|
||||
Timestamp date2 = currentDate;
|
||||
|
||||
int C_ConversionType_ID = 201; // Company
|
||||
|
||||
MCurrency usd = MCurrency.get(100); // USD
|
||||
MCurrency euro = MCurrency.get("EUR"); // EUR
|
||||
BigDecimal eurToUsd1 = new BigDecimal(30);
|
||||
MConversionRate cr1 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date1, eurToUsd1, false);
|
||||
|
||||
BigDecimal eurToUsd2 = new BigDecimal(31);
|
||||
MConversionRate cr2 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date2, eurToUsd2, false);
|
||||
|
||||
try {
|
||||
String whereClause = "AD_Org_ID=? AND C_Currency_ID=?";
|
||||
MBankAccount ba = new Query(Env.getCtx(),MBankAccount.Table_Name, whereClause, getTrxName())
|
||||
.setParameters(Env.getAD_Org_ID(Env.getCtx()), usd.getC_Currency_ID())
|
||||
.setOrderBy("IsDefault DESC")
|
||||
.first();
|
||||
assertTrue(ba != null, "@NoAccountOrgCurrency@");
|
||||
|
||||
BigDecimal payAmt = new BigDecimal(1000);
|
||||
MPayment payment = createReceiptPayment(bpartner.getC_BPartner_ID(), ba.getC_BankAccount_ID(), date1, euro.getC_Currency_ID(), C_ConversionType_ID, payAmt);
|
||||
completeDocument(payment);
|
||||
postDocument(payment);
|
||||
|
||||
reverseAccrualDocument(payment);
|
||||
MPayment reversalPayment = new MPayment(Env.getCtx(), payment.getReversal_ID(), getTrxName());
|
||||
postDocument(reversalPayment);
|
||||
|
||||
MAllocationHdr[] allocations = MAllocationHdr.getOfPayment(Env.getCtx(), payment.getC_Payment_ID(), getTrxName());
|
||||
assertTrue(allocations.length == 1);
|
||||
|
||||
MAllocationHdr allocation = allocations[0];
|
||||
postDocument(allocation);
|
||||
|
||||
MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx()));
|
||||
for (MAcctSchema as : ass) {
|
||||
if (as.getC_Currency_ID() != usd.getC_Currency_ID())
|
||||
continue;
|
||||
|
||||
Doc doc = DocManager.getDocument(as, MAllocationHdr.Table_ID, allocation.get_ID(), getTrxName());
|
||||
doc.setC_BankAccount_ID(ba.getC_BankAccount_ID());
|
||||
MAccount acctUC = doc.getAccount(Doc.ACCTTYPE_UnallocatedCash, as);
|
||||
MAccount acctLoss = MAccount.get(as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
|
||||
BigDecimal ucAmtAcctDr = new BigDecimal(30000);
|
||||
BigDecimal ucAmtAcctCr = new BigDecimal(31000);
|
||||
BigDecimal lossAmtAcct = new BigDecimal(1000);
|
||||
|
||||
whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID
|
||||
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocation.get_ID()
|
||||
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID();
|
||||
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName());
|
||||
for (int id : ids) {
|
||||
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
|
||||
if (acctUC.getAccount_ID() == fa.getAccount_ID()) {
|
||||
if (fa.getAmtAcctDr().signum() > 0)
|
||||
assertTrue(fa.getAmtAcctDr().compareTo(ucAmtAcctDr) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + ucAmtAcctDr.toPlainString());
|
||||
else if (fa.getAmtAcctDr().signum() < 0)
|
||||
assertTrue(fa.getAmtAcctDr().compareTo(ucAmtAcctCr.negate()) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + ucAmtAcctCr.negate().toPlainString());
|
||||
else if (fa.getAmtAcctCr().signum() > 0)
|
||||
assertTrue(fa.getAmtAcctCr().compareTo(ucAmtAcctCr) == 0, fa.getAmtAcctCr().toPlainString() + "!=" + ucAmtAcctCr.toPlainString());
|
||||
}
|
||||
else if (acctLoss.getAccount_ID() == fa.getAccount_ID())
|
||||
assertTrue(fa.getAmtAcctDr().compareTo(lossAmtAcct) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + lossAmtAcct.toPlainString());
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
deleteConversionRate(cr1);
|
||||
deleteConversionRate(cr2);
|
||||
|
||||
rollback();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllocatePaymentPosting() {
|
||||
MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc.
|
||||
Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date");
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTimeInMillis(currentDate.getTime());
|
||||
cal.add(Calendar.DAY_OF_MONTH, -1);
|
||||
Timestamp date1 = new Timestamp(cal.getTimeInMillis());
|
||||
Timestamp date2 = currentDate;
|
||||
|
||||
int C_ConversionType_ID = 201; // Company
|
||||
|
||||
MCurrency usd = MCurrency.get(100); // USD
|
||||
MCurrency euro = MCurrency.get("EUR"); // EUR
|
||||
BigDecimal eurToUsd1 = new BigDecimal(30);
|
||||
MConversionRate cr1 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date1, eurToUsd1, false);
|
||||
|
||||
BigDecimal eurToUsd2 = new BigDecimal(31);
|
||||
MConversionRate cr2 = createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, date2, eurToUsd2, false);
|
||||
|
||||
try {
|
||||
String whereClause = "AD_Org_ID=? AND C_Currency_ID=?";
|
||||
MBankAccount ba = new Query(Env.getCtx(),MBankAccount.Table_Name, whereClause, getTrxName())
|
||||
.setParameters(Env.getAD_Org_ID(Env.getCtx()), usd.getC_Currency_ID())
|
||||
.setOrderBy("IsDefault DESC")
|
||||
.first();
|
||||
assertTrue(ba != null, "@NoAccountOrgCurrency@");
|
||||
|
||||
BigDecimal payAmt = new BigDecimal(1000);
|
||||
MPayment payment1 = createReceiptPayment(bpartner.getC_BPartner_ID(), ba.getC_BankAccount_ID(), date1, euro.getC_Currency_ID(), C_ConversionType_ID, payAmt);
|
||||
completeDocument(payment1);
|
||||
postDocument(payment1);
|
||||
|
||||
MPayment payment2 = createReceiptPayment(bpartner.getC_BPartner_ID(), ba.getC_BankAccount_ID(), date2, euro.getC_Currency_ID(), C_ConversionType_ID, payAmt.negate());
|
||||
completeDocument(payment2);
|
||||
postDocument(payment2);
|
||||
|
||||
MAllocationHdr alloc = new MAllocationHdr(Env.getCtx(), true, date2, euro.getC_Currency_ID(), Env.getContext(Env.getCtx(), Env.AD_USER_NAME), getTrxName());
|
||||
alloc.setAD_Org_ID(payment2.getAD_Org_ID());
|
||||
int doctypeAlloc = MDocType.getDocType("CMA");
|
||||
alloc.setC_DocType_ID(doctypeAlloc);
|
||||
alloc.setDescription(alloc.getDescriptionForManualAllocation(payment2.getC_BPartner_ID(), getTrxName()));
|
||||
alloc.saveEx();
|
||||
|
||||
MAllocationLine aLine1 = new MAllocationLine(alloc, payment1.getPayAmt(), Env.ZERO, Env.ZERO, Env.ZERO);
|
||||
aLine1.setDocInfo(payment1.getC_BPartner_ID(), 0, 0);
|
||||
aLine1.setPaymentInfo(payment1.getC_Payment_ID(), 0);
|
||||
aLine1.saveEx();
|
||||
|
||||
MAllocationLine aLine2 = new MAllocationLine(alloc, payment2.getPayAmt(), Env.ZERO, Env.ZERO, Env.ZERO);
|
||||
aLine2.setDocInfo(payment2.getC_BPartner_ID(), 0, 0);
|
||||
aLine2.setPaymentInfo(payment2.getC_Payment_ID(), 0);
|
||||
aLine2.saveEx();
|
||||
|
||||
completeDocument(alloc);
|
||||
postDocument(alloc);
|
||||
|
||||
MAllocationHdr[] allocations = MAllocationHdr.getOfPayment(Env.getCtx(), payment1.getC_Payment_ID(), getTrxName());
|
||||
assertTrue(allocations.length == 1);
|
||||
|
||||
MAllocationHdr allocation = allocations[0];
|
||||
postDocument(allocation);
|
||||
|
||||
MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx()));
|
||||
for (MAcctSchema as : ass) {
|
||||
if (as.getC_Currency_ID() != usd.getC_Currency_ID())
|
||||
continue;
|
||||
|
||||
Doc doc = DocManager.getDocument(as, MAllocationHdr.Table_ID, allocation.get_ID(), getTrxName());
|
||||
doc.setC_BankAccount_ID(ba.getC_BankAccount_ID());
|
||||
MAccount acctUC = doc.getAccount(Doc.ACCTTYPE_UnallocatedCash, as);
|
||||
MAccount acctLoss = MAccount.get(as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
|
||||
BigDecimal ucAmtAcctDr = new BigDecimal(30000);
|
||||
BigDecimal ucAmtAcctCr = new BigDecimal(31000);
|
||||
BigDecimal lossAmtAcct = new BigDecimal(1000);
|
||||
|
||||
whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID
|
||||
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocation.get_ID()
|
||||
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID();
|
||||
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName());
|
||||
for (int id : ids) {
|
||||
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
|
||||
if (acctUC.getAccount_ID() == fa.getAccount_ID()) {
|
||||
if (fa.getAmtAcctDr().signum() > 0)
|
||||
assertTrue(fa.getAmtAcctDr().compareTo(ucAmtAcctDr) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + ucAmtAcctDr.toPlainString());
|
||||
else if (fa.getAmtAcctDr().signum() < 0)
|
||||
assertTrue(fa.getAmtAcctDr().compareTo(ucAmtAcctCr.negate()) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + ucAmtAcctCr.negate().toPlainString());
|
||||
else if (fa.getAmtAcctCr().signum() > 0)
|
||||
assertTrue(fa.getAmtAcctCr().compareTo(ucAmtAcctCr) == 0, fa.getAmtAcctCr().toPlainString() + "!=" + ucAmtAcctCr.toPlainString());
|
||||
}
|
||||
else if (acctLoss.getAccount_ID() == fa.getAccount_ID())
|
||||
assertTrue(fa.getAmtAcctDr().compareTo(lossAmtAcct) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + lossAmtAcct.toPlainString());
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
deleteConversionRate(cr1);
|
||||
deleteConversionRate(cr2);
|
||||
|
||||
rollback();
|
||||
}
|
||||
}
|
||||
|
||||
private MConversionRate createConversionRate(int C_Currency_ID, int C_Currency_ID_To, int C_ConversionType_ID,
|
||||
Timestamp date, BigDecimal rate, boolean isMultiplyRate) {
|
||||
MConversionRate cr = new MConversionRate(Env.getCtx(), 0, null);
|
||||
cr.setC_Currency_ID(C_Currency_ID);
|
||||
cr.setC_Currency_ID_To(C_Currency_ID_To);
|
||||
cr.setC_ConversionType_ID(C_ConversionType_ID);
|
||||
cr.setValidFrom(date);
|
||||
cr.setValidTo(date);
|
||||
if (isMultiplyRate)
|
||||
cr.setMultiplyRate(rate);
|
||||
else
|
||||
cr.setDivideRate(rate);
|
||||
cr.saveEx();
|
||||
return cr;
|
||||
}
|
||||
|
||||
private void deleteConversionRate(MConversionRate cr) {
|
||||
String whereClause = "ValidFrom=? AND ValidTo=? "
|
||||
+ "AND C_Currency_ID=? AND C_Currency_ID_To=? "
|
||||
+ "AND C_ConversionType_ID=? "
|
||||
+ "AND AD_Client_ID=? AND AD_Org_ID=?";
|
||||
MConversionRate reciprocal = new Query(Env.getCtx(), MConversionRate.Table_Name, whereClause, null)
|
||||
.setParameters(cr.getValidFrom(), cr.getValidTo(),
|
||||
cr.getC_Currency_ID_To(), cr.getC_Currency_ID(),
|
||||
cr.getC_ConversionType_ID(),
|
||||
cr.getAD_Client_ID(), cr.getAD_Org_ID())
|
||||
.firstOnly();
|
||||
if (reciprocal != null)
|
||||
reciprocal.deleteEx(true);
|
||||
cr.deleteEx(true);
|
||||
}
|
||||
|
||||
private MPayment createReceiptPayment(int C_BPartner_ID, int C_BankAccount_ID, Timestamp date, int C_Currency_ID, int C_ConversionType_ID, BigDecimal payAmt) {
|
||||
MPayment payment = new MPayment(Env.getCtx(), 0, getTrxName());
|
||||
payment.setC_BankAccount_ID(C_BankAccount_ID);
|
||||
payment.setC_DocType_ID(true);
|
||||
payment.setDateTrx(date);
|
||||
payment.setDateAcct(date);
|
||||
payment.setC_BPartner_ID(C_BPartner_ID);
|
||||
payment.setPayAmt(payAmt);
|
||||
payment.setC_Currency_ID(C_Currency_ID);
|
||||
payment.setC_ConversionType_ID(C_ConversionType_ID);
|
||||
payment.setTenderType(MPayment.TENDERTYPE_Check);
|
||||
payment.setDocStatus(DocAction.STATUS_Drafted);
|
||||
payment.setDocAction(DocAction.ACTION_Complete);
|
||||
payment.saveEx();
|
||||
return payment;
|
||||
}
|
||||
|
||||
private void completeDocument(PO po) {
|
||||
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(po, DocAction.ACTION_Complete);
|
||||
po.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
String docStatus = (String) po.get_Value("DocStatus");
|
||||
assertEquals(DocAction.STATUS_Completed, docStatus, DocAction.STATUS_Completed + " != " + docStatus);
|
||||
}
|
||||
|
||||
private void reverseAccrualDocument(PO po) {
|
||||
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(po, DocAction.ACTION_Reverse_Accrual);
|
||||
po.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
String docStatus = (String) po.get_Value("DocStatus");
|
||||
assertEquals(DocAction.STATUS_Reversed, docStatus, DocAction.STATUS_Reversed + " != " + docStatus);
|
||||
}
|
||||
|
||||
private void postDocument(PO po) {
|
||||
if (!po.get_ValueAsBoolean("Posted")) {
|
||||
String error = DocumentEngine.postImmediate(Env.getCtx(), po.getAD_Client_ID(), po.get_Table_ID(), po.get_ID(), false, getTrxName());
|
||||
assertTrue(error == null, error);
|
||||
}
|
||||
po.load(getTrxName());
|
||||
assertTrue(po.get_ValueAsBoolean("Posted"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue