IDEMPIERE-3779 AP2-573 Payment allocation posting wrongly for multi-currency if allocation date <> payment date

This commit is contained in:
Carlos Ruiz 2018-08-29 13:02:21 +02:00
parent 82623d4399
commit e9f31283c6
2 changed files with 225 additions and 136 deletions

View File

@ -19,7 +19,8 @@ package org.compiere.acct;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import org.compiere.model.MAccount; import org.compiere.model.MAccount;
@ -28,7 +29,8 @@ import org.compiere.model.MAcctSchemaElement;
import org.compiere.model.MAllocationHdr; import org.compiere.model.MAllocationHdr;
import org.compiere.model.MAllocationLine; import org.compiere.model.MAllocationLine;
import org.compiere.model.MCashLine; import org.compiere.model.MCashLine;
import org.compiere.model.MConversionRate; import org.compiere.model.MConversionRate;
import org.compiere.model.MCurrency;
import org.compiere.model.MFactAcct; import org.compiere.model.MFactAcct;
import org.compiere.model.MInvoice; import org.compiere.model.MInvoice;
import org.compiere.model.MInvoiceLine; import org.compiere.model.MInvoiceLine;
@ -197,6 +199,7 @@ public class Doc_AllocationHdr extends Doc
FactLine fl = null; FactLine fl = null;
FactLine flForRGL = null; FactLine flForRGL = null;
MAccount bpAcct = null; // Liability/Receivables MAccount bpAcct = null; // Liability/Receivables
MAccount payAcct = null; // Payment Selection
// //
MPayment payment = null; MPayment payment = null;
if (line.getC_Payment_ID() != 0) if (line.getC_Payment_ID() != 0)
@ -258,8 +261,9 @@ public class Doc_AllocationHdr extends Doc
// Payment/Cash DR // Payment/Cash DR
if (line.getC_Payment_ID() != 0) if (line.getC_Payment_ID() != 0)
{ {
fl = fact.createLine (line, getPaymentAcct(as, line.getC_Payment_ID()), payAcct = getPaymentAcct(as, line.getC_Payment_ID());
fl = fact.createLine (line, payAcct,
getC_Currency_ID(), line.getAmtSource(), null); getC_Currency_ID(), line.getAmtSource(), null);
if (fl != null && payment != null) if (fl != null && payment != null)
fl.setAD_Org_ID(payment.getAD_Org_ID()); fl.setAD_Org_ID(payment.getAD_Org_ID());
@ -388,8 +392,9 @@ public class Doc_AllocationHdr extends Doc
} }
// Payment/Cash CR // Payment/Cash CR
if (isUsingClearing && line.getC_Payment_ID() != 0) // Avoid usage of clearing accounts if (isUsingClearing && line.getC_Payment_ID() != 0) // Avoid usage of clearing accounts
{ {
fl = fact.createLine (line, getPaymentAcct(as, line.getC_Payment_ID()), payAcct = getPaymentAcct(as, line.getC_Payment_ID());
fl = fact.createLine (line, payAcct,
getC_Currency_ID(), null, line.getAmtSource().negate()); getC_Currency_ID(), null, line.getAmtSource().negate());
if (fl != null && payment != null) if (fl != null && payment != null)
fl.setAD_Org_ID(payment.getAD_Org_ID()); fl.setAD_Org_ID(payment.getAD_Org_ID());
@ -430,7 +435,7 @@ public class Doc_AllocationHdr extends Doc
&& (getC_Currency_ID() != as.getC_Currency_ID() // payment allocation in foreign currency && (getC_Currency_ID() != as.getC_Currency_ID() // payment allocation in foreign currency
|| getC_Currency_ID() != line.getInvoiceC_Currency_ID())) // allocation <> invoice currency || getC_Currency_ID() != line.getInvoiceC_Currency_ID())) // allocation <> invoice currency
{ {
p_Error = createRealizedGainLoss (line, as, fact, bpAcct, invoice, p_Error = createRealizedGainLoss (line, as, fact, bpAcct, invoice, payAcct, payment,
allocationSource, allocationAccounted); allocationSource, allocationAccounted);
if (p_Error != null) if (p_Error != null)
return null; return null;
@ -692,102 +697,164 @@ public class Doc_AllocationHdr extends Doc
* Accounted Amount of the Allocation * Accounted Amount of the Allocation
* @param as accounting schema * @param as accounting schema
* @param fact fact * @param fact fact
* @param acct account * @param invAcct invoice account
* @param invoice invoice * @param invoice invoice
* @param payAcct payment account
* @param payment payment
* @param allocationSource source amt * @param allocationSource source amt
* @param allocationAccounted acct amt * @param allocationAccounted acct amt
* @return Error Message or null if OK * @return Error Message or null if OK
*/ */
private String createRealizedGainLoss (DocLine line, MAcctSchema as, Fact fact, MAccount acct, private String createRealizedGainLoss (DocLine line, MAcctSchema as, Fact fact, MAccount invAcct,
MInvoice invoice, BigDecimal allocationSource, BigDecimal allocationAccounted) MInvoice invoice, MAccount payAcct, MPayment payment, BigDecimal allocationSource, BigDecimal allocationAccounted)
{ {
BigDecimal invoiceSource = null; BigDecimal invoiceSource = null;
BigDecimal invoiceAccounted = null; BigDecimal invoiceAccounted = null;
BigDecimal paymentSource = null;
BigDecimal paymentAccounted = null;
// //
StringBuilder sql = new StringBuilder("SELECT ") StringBuilder sql = new StringBuilder()
.append(invoice.isSOTrx() .append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
? "SUM(AmtSourceDr), SUM(AmtAcctDr)" // so
: "SUM(AmtSourceCr), SUM(AmtAcctCr)") // po
.append(" FROM Fact_Acct ") .append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=318 AND Record_ID=?") // Invoice .append("WHERE AD_Table_ID=? AND Record_ID=?")
.append(" AND C_AcctSchema_ID=?") .append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'"); .append(" AND PostingType='A'");
//AND C_Currency_ID=102
PreparedStatement pstmt = null; // For Invoice
ResultSet rs = null; List<Object> valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
try MInvoice.Table_ID, invoice.getC_Invoice_ID(), as.getC_AcctSchema_ID());
{ if (valuesInv != null) {
pstmt = DB.prepareStatement(sql.toString(), getTrxName()); if (invoice.isSOTrx()) {
pstmt.setInt(1, invoice.getC_Invoice_ID()); invoiceSource = (BigDecimal) valuesInv.get(0); // AmtSourceDr
pstmt.setInt(2, as.getC_AcctSchema_ID()); invoiceAccounted = (BigDecimal) valuesInv.get(1); // AmtAcctDr
rs = pstmt.executeQuery(); } else {
if (rs.next()) invoiceSource = (BigDecimal) valuesInv.get(2); // AmtSourceCr
{ invoiceAccounted = (BigDecimal) valuesInv.get(3); // AmtAcctCr
invoiceSource = rs.getBigDecimal(1); }
invoiceAccounted = rs.getBigDecimal(2);
}
}
catch (Exception e)
{
log.log(Level.SEVERE, sql.toString(), e);
}
finally {
DB.close(rs, pstmt);
rs = null; pstmt = null;
} }
// Requires that Invoice is Posted // Requires that Invoice is Posted
if (invoiceSource == null || invoiceAccounted == null) if (invoiceSource == null || invoiceAccounted == null)
return "Gain/Loss - Invoice not posted yet"; return "Gain/Loss - Invoice not posted yet";
// //
StringBuilder description = new StringBuilder("Invoice=(").append(invoice.getC_Currency_ID()).append(")").append(invoiceSource).append("/").append(invoiceAccounted) String invoiceCur = MCurrency.get(getCtx(), invoice.getC_Currency_ID()).getISO_Code();
.append(" - Allocation=(").append(getC_Currency_ID()).append(")").append(allocationSource).append("/").append(allocationAccounted); String allocCur = MCurrency.get(getCtx(), getC_Currency_ID()).getISO_Code();
if (log.isLoggable(Level.FINE)) log.fine(description.toString()); StringBuilder descriptionInv = new StringBuilder("Invoice=(").append(invoiceCur).append(")").append(invoiceSource).append("/").append(invoiceAccounted)
// Allocation not Invoice Currency .append(" - Allocation=(").append(allocCur).append(")").append(allocationSource).append("/").append(allocationAccounted);
if (log.isLoggable(Level.FINE)) log.fine(descriptionInv.toString());
// Allocation not Invoice Currency
BigDecimal allocationInvoiceSource = allocationSource;
if (getC_Currency_ID() != invoice.getC_Currency_ID()) if (getC_Currency_ID() != invoice.getC_Currency_ID())
{ {
BigDecimal allocationSourceNew = MConversionRate.convert(getCtx(), allocationInvoiceSource = MConversionRate.convert(getCtx(),
allocationSource, getC_Currency_ID(), allocationSource, getC_Currency_ID(),
invoice.getC_Currency_ID(), getDateAcct(), invoice.getC_Currency_ID(), getDateAcct(),
invoice.getC_ConversionType_ID(), invoice.getAD_Client_ID(), invoice.getAD_Org_ID()); invoice.getC_ConversionType_ID(), invoice.getAD_Client_ID(), invoice.getAD_Org_ID());
if (allocationSourceNew == null) if (allocationInvoiceSource == null)
return "Gain/Loss - No Conversion from Allocation->Invoice"; return "Gain/Loss - No Conversion from Allocation->Invoice";
StringBuilder d2 = new StringBuilder("Allocation=(").append(getC_Currency_ID()).append(")").append(allocationSource) StringBuilder d2 = new StringBuilder("Allocation=(").append(allocCur).append(")").append(allocationSource)
.append("->(").append(invoice.getC_Currency_ID()).append(")").append(allocationSourceNew); .append("->(").append(invoiceCur).append(")").append(allocationInvoiceSource);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString()); if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2); descriptionInv.append(" - ").append(d2);
allocationSource = allocationSourceNew;
} }
BigDecimal acctDifference = null; // gain is negative BigDecimal invoiceDifference = null; // gain is negative
// Full Payment in currency // Full Invoice in currency
if (allocationSource.compareTo(invoiceSource) == 0) if (allocationInvoiceSource.compareTo(invoiceSource) == 0)
{ {
acctDifference = invoiceAccounted.subtract(allocationAccounted); // gain is negative invoiceDifference = invoiceAccounted.subtract(allocationAccounted); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference); StringBuilder d2 = new StringBuilder("(full) = ").append(invoiceDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString()); if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2); descriptionInv.append(" - ").append(d2);
} }
else // partial or MC else // partial or MC
{ {
// percent of total payment // percent of total payment
double multiplier = allocationSource.doubleValue() / invoiceSource.doubleValue(); double multiplier = allocationInvoiceSource.doubleValue() / invoiceSource.doubleValue();
// Reduce Orig Invoice Accounted // Reduce Orig Invoice Accounted
invoiceAccounted = invoiceAccounted.multiply(BigDecimal.valueOf(multiplier)); invoiceAccounted = invoiceAccounted.multiply(BigDecimal.valueOf(multiplier));
// Difference based on percentage of Orig Invoice // Difference based on percentage of Orig Invoice
acctDifference = invoiceAccounted.subtract(allocationAccounted); // gain is negative invoiceDifference = invoiceAccounted.subtract(allocationAccounted); // gain is negative
// ignore Tolerance // ignore Tolerance
if (acctDifference.abs().compareTo(TOLERANCE) < 0) if (invoiceDifference.abs().compareTo(TOLERANCE) < 0)
acctDifference = Env.ZERO; invoiceDifference = Env.ZERO;
// Round // Round
int precision = as.getStdPrecision(); int precision = as.getStdPrecision();
if (acctDifference.scale() > precision) if (invoiceDifference.scale() > precision)
acctDifference = acctDifference.setScale(precision, BigDecimal.ROUND_HALF_UP); invoiceDifference = invoiceDifference.setScale(precision, BigDecimal.ROUND_HALF_UP);
StringBuilder d2 = new StringBuilder("(partial) = ").append(acctDifference).append(" - Multiplier=").append(multiplier); StringBuilder d2 = new StringBuilder("(partial) = ").append(invoiceDifference).append(" - Multiplier=").append(multiplier);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString()); if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2); descriptionInv.append(" - ").append(d2);
} }
if (acctDifference.signum() == 0) // For Payment
BigDecimal paymentDifference = Env.ZERO;
StringBuilder descriptionPay = null;
if (payment != null && payment.getC_Payment_ID() > 0) {
List<Object> valuesPay = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MPayment.Table_ID, payment.getC_Payment_ID(), as.getC_AcctSchema_ID());
if (valuesPay != null) {
if (invoice.isSOTrx()) {
paymentSource = (BigDecimal) valuesPay.get(2); // AmtSourceCr
paymentAccounted = (BigDecimal) valuesPay.get(3); // AmtAcctCr
} else {
paymentSource = (BigDecimal) valuesPay.get(0); // AmtSourceDr
paymentAccounted = (BigDecimal) valuesPay.get(1); // AmtAcctDr
}
}
// Requires that Payment is Posted
if (paymentSource == null || paymentAccounted == null)
return "Gain/Loss - Payment not posted yet";
//
String paymentCur = MCurrency.get(getCtx(), payment.getC_Currency_ID()).getISO_Code();
descriptionPay = new StringBuilder("Payment=(").append(paymentCur).append(")").append(paymentSource).append("/").append(paymentAccounted)
.append(" - Allocation=(").append(allocCur).append(")").append(allocationSource).append("/").append(allocationAccounted);
if (log.isLoggable(Level.FINE)) log.fine(descriptionPay.toString());
// Allocation not Payment Currency
BigDecimal allocationPaymentSource = allocationSource;
if (getC_Currency_ID() != payment.getC_Currency_ID())
{
allocationPaymentSource = MConversionRate.convert(getCtx(),
allocationSource, getC_Currency_ID(),
payment.getC_Currency_ID(), getDateAcct(),
payment.getC_ConversionType_ID(), payment.getAD_Client_ID(), payment.getAD_Org_ID());
if (allocationPaymentSource == null)
return "Gain/Loss - No Conversion from Allocation->Payment";
StringBuilder d2 = new StringBuilder("Allocation=(").append(allocCur).append(")").append(allocationSource)
.append("->(").append(paymentCur).append(")").append(allocationPaymentSource);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
descriptionPay.append(" - ").append(d2);
}
// Full Payment in currency
if (allocationPaymentSource.compareTo(paymentSource) == 0)
{
paymentDifference = paymentAccounted.subtract(allocationAccounted); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(paymentDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
descriptionPay.append(" - ").append(d2);
}
else // partial or MC
{
// percent of total payment
double multiplier = allocationPaymentSource.doubleValue() / paymentSource.doubleValue();
// Reduce Orig Payment Accounted
paymentAccounted = paymentAccounted.multiply(BigDecimal.valueOf(multiplier));
// Difference based on percentage of Orig Payment
paymentDifference = paymentAccounted.subtract(allocationAccounted); // gain is negative
// ignore Tolerance
if (paymentDifference.abs().compareTo(TOLERANCE) < 0)
paymentDifference = Env.ZERO;
// Round
int precision = as.getStdPrecision();
if (paymentDifference.scale() > precision)
paymentDifference = paymentDifference.setScale(precision, BigDecimal.ROUND_HALF_UP);
StringBuilder d2 = new StringBuilder("(partial) = ").append(paymentDifference).append(" - Multiplier=").append(multiplier);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
descriptionPay.append(" - ").append(d2);
}
}
if (invoiceDifference.signum() == 0 && paymentDifference.signum() == 0)
{ {
log.fine("No Difference"); log.fine("No Difference");
return null; return null;
@ -795,23 +862,45 @@ public class Doc_AllocationHdr extends Doc
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct()); MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
// //
BigDecimal acctDifference = invoiceDifference.subtract(paymentDifference);
if (invoice.isSOTrx()) if (invoice.isSOTrx())
{ {
FactLine fl = fact.createLine (line, loss, gain, if (acctDifference.signum() != 0) {
as.getC_Currency_ID(), acctDifference); FactLine fl = fact.createLine (line, loss, gain, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString()); StringBuilder description = new StringBuilder(descriptionInv);
fact.createLine (line, acct, if (paymentDifference.signum() != 0 && descriptionPay != null) {
as.getC_Currency_ID(), acctDifference.negate()); description.append(" / ").append(descriptionPay);
fl.setDescription(description.toString()); }
fl.setDescription(description.toString());
}
if (invoiceDifference.signum() != 0) {
FactLine fl = fact.createLine (line, invAcct, as.getC_Currency_ID(), invoiceDifference.negate());
fl.setDescription(descriptionInv.toString());
}
if (paymentDifference.signum() != 0) {
FactLine fl = fact.createLine (line, payAcct, as.getC_Currency_ID(), paymentDifference);
fl.setDescription(descriptionPay.toString());
}
} }
else else
{ {
fact.createLine (line, acct, if (invoiceDifference.signum() != 0) {
as.getC_Currency_ID(), acctDifference); FactLine fl = fact.createLine (line, invAcct, as.getC_Currency_ID(), invoiceDifference);
@SuppressWarnings("unused") fl.setDescription(descriptionInv.toString());
FactLine fl = fact.createLine (line, loss, gain, }
as.getC_Currency_ID(), acctDifference.negate()); if (paymentDifference.signum() != 0) {
FactLine fl = fact.createLine (line, payAcct, as.getC_Currency_ID(), paymentDifference.negate());
fl.setDescription(descriptionPay.toString());
}
if (acctDifference.signum() != 0) {
FactLine fl = fact.createLine (line, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
StringBuilder description = new StringBuilder(descriptionInv);
if (paymentDifference.signum() != 0 && descriptionPay != null) {
description.append(" / ").append(descriptionPay);
}
fl.setDescription(description.toString());
}
} }
return null; return null;
} // createRealizedGainLoss } // createRealizedGainLoss
@ -1040,49 +1129,49 @@ class Doc_AllocationTax
} // Discount } // Discount
// WriteOff Amount // WriteOff Amount
if (m_WriteOffAmt.signum() != 0) if (m_WriteOffAmt.signum() != 0)
{ {
// Original Tax is DR - need to correct it CR // Original Tax is DR - need to correct it CR
if (Env.ZERO.compareTo(factAcct.getAmtSourceDr()) != 0) if (Env.ZERO.compareTo(factAcct.getAmtSourceDr()) != 0)
{ {
BigDecimal amount = calcAmount(factAcct.getAmtSourceDr(), BigDecimal amount = calcAmount(factAcct.getAmtSourceDr(),
total, m_WriteOffAmt, precision); total, m_WriteOffAmt, precision);
if (amount.signum() != 0) if (amount.signum() != 0)
{ {
if (m_IsSOTrx) { if (m_IsSOTrx) {
fact.createLine (line, m_WriteOffAccount, fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), amount, null); as.getC_Currency_ID(), amount, null);
fact.createLine (line, taxAcct, fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount); as.getC_Currency_ID(), null, amount);
} else { } else {
fact.createLine (line, m_WriteOffAccount, fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), amount.negate(), null); as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, taxAcct, fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount.negate()); as.getC_Currency_ID(), null, amount.negate());
} }
} }
} }
// Original Tax is CR - need to correct it DR // Original Tax is CR - need to correct it DR
else else
{ {
BigDecimal amount = calcAmount(factAcct.getAmtSourceCr(), BigDecimal amount = calcAmount(factAcct.getAmtSourceCr(),
total, m_WriteOffAmt, precision); total, m_WriteOffAmt, precision);
if (amount.signum() != 0) if (amount.signum() != 0)
{ {
if(m_IsSOTrx) { if(m_IsSOTrx) {
fact.createLine (line, taxAcct, fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount, null); as.getC_Currency_ID(), amount, null);
fact.createLine (line, m_WriteOffAccount, fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), null, amount); as.getC_Currency_ID(), null, amount);
} else { } else {
fact.createLine (line, taxAcct, fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount.negate(), null); as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, m_WriteOffAccount, fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), null, amount.negate()); as.getC_Currency_ID(), null, amount.negate());
} }
} }
} }
} // WriteOff } // WriteOff
} // for all lines } // for all lines
return true; return true;

View File

@ -728,7 +728,7 @@ public final class FactLine extends X_Fact_Acct
Timestamp convDate = getDateAcct(); Timestamp convDate = getDateAcct();
if ( m_docLine != null && ( m_doc instanceof Doc_BankStatement || m_doc instanceof Doc_AllocationHdr ) ) if (m_docLine != null && m_doc instanceof Doc_BankStatement)
convDate = m_docLine.getDateConv(); convDate = m_docLine.getDateConv();