IDEMPIERE-4127 GL Inventory Clearing - Minor rounding variances

This commit is contained in:
hengsin 2020-02-11 13:34:17 +08:00
parent 5b9f61ae52
commit 7173b78f68
3 changed files with 896 additions and 158 deletions

View File

@ -31,12 +31,12 @@ import org.compiere.model.MAllocationHdr;
import org.compiere.model.MAllocationLine;
import org.compiere.model.MCashLine;
import org.compiere.model.MConversionRate;
import org.compiere.model.MCurrency;
import org.compiere.model.MFactAcct;
import org.compiere.model.MInvoice;
import org.compiere.model.MInvoiceLine;
import org.compiere.model.MOrder;
import org.compiere.model.MPayment;
import org.compiere.model.MPeriod;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
@ -75,6 +75,9 @@ public class Doc_AllocationHdr extends Doc
private static final BigDecimal TOLERANCE = BigDecimal.valueOf(0.02);
/** Facts */
private ArrayList<Fact> m_facts = null;
BigDecimal gainLossAmt =Env.ZERO;
private BigDecimal cmGainLossAmt=Env.ZERO;
private ArrayList<FactLine> gainLossFactList;
/**
@ -174,6 +177,12 @@ public class Doc_AllocationHdr extends Doc
Fact fact = new Fact(this, as, Fact.POST_Actual);
Fact factForRGL = new Fact(this, as, Fact.POST_Actual); // dummy fact (not posted) to calculate Realized Gain & Loss
boolean isInterOrg = isInterOrg(as);
BigDecimal paymentSelectAmt = Env.ZERO;
BigDecimal totalAllocationSource = Env.ZERO;
MPayment payment = null;
int lineID = 0;
MAccount bpAcct = null; // Liability/Receivables
gainLossFactList = new ArrayList<FactLine>();
for (int i = 0; i < p_lines.length; i++)
{
@ -199,10 +208,7 @@ public class Doc_AllocationHdr extends Doc
FactLine fl = null;
FactLine flForRGL = null;
MAccount bpAcct = null; // Liability/Receivables
MAccount payAcct = null; // Payment Selection
//
MPayment payment = null;
if (line.getC_Payment_ID() != 0)
payment = new MPayment (getCtx(), line.getC_Payment_ID(), getTrxName());
MInvoice invoice = null;
@ -254,7 +260,8 @@ public class Doc_AllocationHdr extends Doc
// if not using clearing accounts, then don't post amtsource
// change the allocationsource to be writeoff + discount
allocationSource = line.getDiscountAmt().add(line.getWriteOffAmt());
payAcct = getPaymentAcct(as, line.getC_Payment_ID());
} else {
// Normal behavior -- unchanged if using clearing accounts
@ -262,11 +269,14 @@ public class Doc_AllocationHdr extends Doc
// Payment/Cash DR
if (line.getC_Payment_ID() != 0)
{
payAcct = getPaymentAcct(as, line.getC_Payment_ID());
fl = fact.createLine (line, payAcct,
fl = fact.createLine (line, getPaymentAcct(as, line.getC_Payment_ID()),
getC_Currency_ID(), line.getAmtSource(), null);
if (fl != null && payment != null)
fl.setAD_Org_ID(payment.getAD_Org_ID());
if (payment.getReversal_ID() > 0 )
paymentSelectAmt= paymentSelectAmt.add(fl.getAcctBalance().negate());
else
paymentSelectAmt= paymentSelectAmt.add(fl.getAcctBalance());
}
else if (line.getC_CashLine_ID() != 0)
{
@ -393,11 +403,11 @@ public class Doc_AllocationHdr extends Doc
// Payment/Cash CR
if (isUsingClearing && line.getC_Payment_ID() != 0) // Avoid usage of clearing accounts
{
payAcct = getPaymentAcct(as, line.getC_Payment_ID());
fl = fact.createLine (line, payAcct,
fl = fact.createLine (line, getPaymentAcct(as, line.getC_Payment_ID()),
getC_Currency_ID(), null, line.getAmtSource().negate());
if (fl != null && payment != null)
fl.setAD_Org_ID(payment.getAD_Org_ID());
paymentSelectAmt= paymentSelectAmt.add(fl.getAcctBalance().negate());
}
else if (isUsingClearing && line.getC_CashLine_ID() != 0) // Avoid usage of clearing accounts
{
@ -435,14 +445,31 @@ public class Doc_AllocationHdr extends Doc
&& (getC_Currency_ID() != as.getC_Currency_ID() // payment allocation in foreign currency
|| getC_Currency_ID() != line.getInvoiceC_Currency_ID())) // allocation <> invoice currency
{
p_Error = createRealizedGainLoss (line, as, fact, bpAcct, invoice, payAcct, payment,
allocationSourceForRGL, allocationAccounted);
p_Error = createRealizedGainLoss (line, as, fact, bpAcct, invoice,
allocationSource, allocationAccounted);
if (p_Error != null)
return null;
}
}
totalAllocationSource = totalAllocationSource.add(line.getAmtSource());
lineID = line.get_ID();
} // for all lines
// rounding correction
if (payment != null && getC_Currency_ID() != as.getC_Currency_ID() ) // payment allocation in foreign currency
{
p_Error = createPaymentGainLoss (as, fact, getPaymentAcct(as, payment.get_ID()), payment,
totalAllocationSource, paymentSelectAmt, lineID);
if (p_Error != null)
return null;
}
if (getC_Currency_ID() != as.getC_Currency_ID())
{
p_Error = createInvoiceRounding (as, fact, bpAcct);
if (p_Error != null)
return null;
}
// FR [ 1840016 ] Avoid usage of clearing accounts - subject to C_AcctSchema.IsPostIfClearingEqual
if ( (!as.isPostIfClearingEqual()) && p_lines.length > 0 && (!isInterOrg)) {
boolean allEquals = true;
@ -652,7 +679,7 @@ public class Doc_AllocationHdr extends Doc
}
catch (Exception e)
{
log.log(Level.SEVERE, sql, e);
throw new RuntimeException(e.getLocalizedMessage(), e);
}
finally
{
@ -697,21 +724,17 @@ public class Doc_AllocationHdr extends Doc
* Accounted Amount of the Allocation
* @param as accounting schema
* @param fact fact
* @param invAcct invoice account
* @param acct account
* @param invoice invoice
* @param payAcct payment account
* @param payment payment
* @param allocationSource source amt
* @param allocationAccounted acct amt
* @return Error Message or null if OK
*/
private String createRealizedGainLoss (DocLine line, MAcctSchema as, Fact fact, MAccount invAcct,
MInvoice invoice, MAccount payAcct, MPayment payment, BigDecimal allocationSource, BigDecimal allocationAccounted)
private String createRealizedGainLoss (DocLine line, MAcctSchema as, Fact fact, MAccount acct,
MInvoice invoice, BigDecimal allocationSource, BigDecimal allocationAccounted)
{
BigDecimal invoiceSource = null;
BigDecimal invoiceAccounted = null;
BigDecimal paymentSource = null;
BigDecimal paymentAccounted = null;
//
StringBuilder sql = new StringBuilder()
.append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
@ -732,129 +755,66 @@ public class Doc_AllocationHdr extends Doc
invoiceAccounted = (BigDecimal) valuesInv.get(3); // AmtAcctCr
}
}
// Requires that Invoice is Posted
if (invoiceSource == null || invoiceAccounted == null)
return "Gain/Loss - Invoice not posted yet";
//
String invoiceCur = MCurrency.get(getCtx(), invoice.getC_Currency_ID()).getISO_Code();
String allocCur = MCurrency.get(getCtx(), getC_Currency_ID()).getISO_Code();
StringBuilder descriptionInv = new StringBuilder("Invoice=(").append(invoiceCur).append(")").append(invoiceSource).append("/").append(invoiceAccounted)
.append(" - Allocation=(").append(allocCur).append(")").append(allocationSource).append("/").append(allocationAccounted);
if (log.isLoggable(Level.FINE)) log.fine(descriptionInv.toString());
StringBuilder description = new StringBuilder("Invoice=(").append(invoice.getC_Currency_ID()).append(")").append(invoiceSource).append("/").append(invoiceAccounted)
.append(" - Allocation=(").append(getC_Currency_ID()).append(")").append(allocationSource).append("/").append(allocationAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
// Allocation not Invoice Currency
BigDecimal allocationInvoiceSource = allocationSource;
if (getC_Currency_ID() != invoice.getC_Currency_ID())
{
allocationInvoiceSource = MConversionRate.convert(getCtx(),
BigDecimal allocationSourceNew = MConversionRate.convert(getCtx(),
allocationSource, getC_Currency_ID(),
invoice.getC_Currency_ID(), getDateAcct(),
invoice.getC_ConversionType_ID(), invoice.getAD_Client_ID(), invoice.getAD_Org_ID());
if (allocationInvoiceSource == null)
if (allocationSourceNew == null)
return "Gain/Loss - No Conversion from Allocation->Invoice";
StringBuilder d2 = new StringBuilder("Allocation=(").append(allocCur).append(")").append(allocationSource)
.append("->(").append(invoiceCur).append(")").append(allocationInvoiceSource);
StringBuilder d2 = new StringBuilder("Allocation=(").append(getC_Currency_ID()).append(")").append(allocationSource)
.append("->(").append(invoice.getC_Currency_ID()).append(")").append(allocationSourceNew);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
descriptionInv.append(" - ").append(d2);
description.append(" - ").append(d2);
allocationSource = allocationSourceNew;
}
BigDecimal invoiceDifference = null; // gain is negative
// Full Invoice in currency
if (allocationInvoiceSource.compareTo(invoiceSource) == 0)
BigDecimal acctDifference = null; // gain is negative
//reversal entry
if (allocationSource.signum() > 0 )
{
invoiceDifference = invoiceAccounted.subtract(allocationAccounted); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(invoiceDifference);
acctDifference = invoiceAccounted.subtract(allocationAccounted.abs());
}
// Full Payment in currency
if (allocationSource.compareTo(invoiceSource) == 0)
{
acctDifference = invoiceAccounted.subtract(allocationAccounted.abs()); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
descriptionInv.append(" - ").append(d2);
description.append(" - ").append(d2);
}
else // partial or MC
{
// percent of total payment
double multiplier = allocationInvoiceSource.doubleValue() / invoiceSource.doubleValue();
double multiplier = allocationSource.doubleValue() / invoiceSource.doubleValue();
// Reduce Orig Invoice Accounted
invoiceAccounted = invoiceAccounted.multiply(BigDecimal.valueOf(multiplier));
// Difference based on percentage of Orig Invoice
invoiceDifference = invoiceAccounted.subtract(allocationAccounted); // gain is negative
acctDifference = invoiceAccounted.subtract(allocationAccounted); // gain is negative
// ignore Tolerance
if (invoiceDifference.abs().compareTo(TOLERANCE) < 0)
invoiceDifference = Env.ZERO;
if (acctDifference.abs().compareTo(TOLERANCE) < 0)
acctDifference = Env.ZERO;
// Round
int precision = as.getStdPrecision();
if (invoiceDifference.scale() > precision)
invoiceDifference = invoiceDifference.setScale(precision, RoundingMode.HALF_UP);
StringBuilder d2 = new StringBuilder("(partial) = ").append(invoiceDifference).append(" - Multiplier=").append(multiplier);
if (acctDifference.scale() > precision)
acctDifference = acctDifference.setScale(precision, RoundingMode.HALF_UP);
StringBuilder d2 = new StringBuilder("(partial) = ").append(acctDifference).append(" - Multiplier=").append(multiplier);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
descriptionInv.append(" - ").append(d2);
description.append(" - ").append(d2);
}
// 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, RoundingMode.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)
if (acctDifference.signum() == 0)
{
log.fine("No Difference");
return null;
@ -862,51 +822,28 @@ public class Doc_AllocationHdr extends Doc
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
if (invoice.isCreditMemo() || invoice.getReversal_ID() > 0)
cmGainLossAmt = cmGainLossAmt.add(acctDifference);
else
gainLossAmt = gainLossAmt.add(acctDifference);
//
BigDecimal acctDifference = invoiceDifference.subtract(paymentDifference);
if (invoice.isSOTrx())
{
if (acctDifference.signum() != 0) {
FactLine fl = fact.createLine (line, loss, gain, as.getC_Currency_ID(), acctDifference);
StringBuilder description = new StringBuilder(descriptionInv);
if (paymentDifference.signum() != 0 && descriptionPay != null) {
description.append(" / ").append(descriptionPay);
}
fl.setDescription(description.toString());
fl.setAD_Org_ID(invoice.getAD_Org_ID());
}
if (invoiceDifference.signum() != 0) {
FactLine fl = fact.createLine (line, invAcct, as.getC_Currency_ID(), invoiceDifference.negate());
fl.setDescription(descriptionInv.toString());
fl.setAD_Org_ID(invoice.getAD_Org_ID());
}
if (paymentDifference.signum() != 0) {
FactLine fl = fact.createLine (line, payAcct, as.getC_Currency_ID(), paymentDifference);
fl.setDescription(descriptionPay.toString());
fl.setAD_Org_ID(invoice.getAD_Org_ID());
}
FactLine fl = fact.createLine (line, loss, gain, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
fl = fact.createLine (line, acct, as.getC_Currency_ID(), acctDifference.negate());
gainLossFactList.add(fl);
}
else
{
if (invoiceDifference.signum() != 0) {
FactLine fl = fact.createLine (line, invAcct, as.getC_Currency_ID(), invoiceDifference);
fl.setDescription(descriptionInv.toString());
fl.setAD_Org_ID(invoice.getAD_Org_ID());
}
if (paymentDifference.signum() != 0) {
FactLine fl = fact.createLine (line, payAcct, as.getC_Currency_ID(), paymentDifference.negate());
fl.setDescription(descriptionPay.toString());
fl.setAD_Org_ID(invoice.getAD_Org_ID());
}
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());
fl.setAD_Org_ID(invoice.getAD_Org_ID());
}
FactLine fl = fact.createLine (line, acct,
as.getC_Currency_ID(), acctDifference);
gainLossFactList.add(fl);
fl = fact.createLine (line, loss, gain,
as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
}
return null;
} // createRealizedGainLoss
@ -947,7 +884,7 @@ public class Doc_AllocationHdr extends Doc
// Get Source Amounts with account
String sql = "SELECT * "
+ "FROM Fact_Acct "
+ "WHERE AD_Table_ID=318 AND Record_ID=?" // Invoice
+ "WHERE AD_Table_ID=? AND Record_ID=?" // Invoice
+ " AND C_AcctSchema_ID=?"
+ " AND Line_ID IS NULL"; // header lines like tax or total
PreparedStatement pstmt = null;
@ -955,15 +892,16 @@ public class Doc_AllocationHdr extends Doc
try
{
pstmt = DB.prepareStatement(sql, getTrxName());
pstmt.setInt(1, line.getC_Invoice_ID());
pstmt.setInt(2, as.getC_AcctSchema_ID());
pstmt.setInt(1, MInvoice.Table_ID);
pstmt.setInt(2, line.getC_Invoice_ID());
pstmt.setInt(3, as.getC_AcctSchema_ID());
rs = pstmt.executeQuery();
while (rs.next())
tax.addInvoiceFact (new MFactAcct(getCtx(), rs, fact.get_TrxName()));
}
catch (Exception e)
{
log.log(Level.SEVERE, sql, e);
throw new RuntimeException(e.getLocalizedMessage(), e);
}
finally {
DB.close(rs, pstmt);
@ -982,6 +920,430 @@ public class Doc_AllocationHdr extends Doc
} // createTaxCorrection
/**************************************************************************
* Create Rounding Correction.
* Compares the Accounted Amount of the Payment to the
* Accounted Amount of the Allocation
* @param as accounting schema
* @param fact fact
* @param acct account
* @param payment payment
* @param paymentSource source amt
* @param paymentAccounted acct amt
* @return Error Message or null if OK
*/
private String createPaymentGainLoss (MAcctSchema as, Fact fact, MAccount acct,
MPayment payment, BigDecimal allocationSource, BigDecimal totalAllocationAccounted, int lineID)
{
BigDecimal paymentSource = null;
BigDecimal paymentAccounted = null;
//
StringBuilder sql = new StringBuilder()
.append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID=?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID = ? ")
.append(" AND PostingType='A'");
// For Payment
List<Object> valuesPay = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MPayment.Table_ID, payment.getC_Payment_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesPay != null) {
if (payment.isReceipt()) {
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 Allocation is Posted
if (paymentSource == null || paymentAccounted == null)
return null; //"Gain/Loss - Payment not posted yet";
//
StringBuilder description = new StringBuilder("Payment=(").append(payment.getC_Currency_ID()).append(")").append(paymentSource).append("/").append(paymentAccounted)
.append(" - Allocation=(").append(getC_Currency_ID()).append(")").append(allocationSource).append("/").append(totalAllocationAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
boolean isSameSourceDiffPeriod = false;
BigDecimal acctDifference = null; // gain is negative
// Full Payment in currency
if (allocationSource.abs().compareTo(paymentSource.abs()) == 0)
{
acctDifference = totalAllocationAccounted.subtract(paymentAccounted.abs()); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
// Different period
if (MPeriod.getC_Period_ID(getCtx(), payment.getDateAcct(), payment.getAD_Org_ID()) !=
MPeriod.getC_Period_ID(getCtx(), getDateAcct(), getAD_Org_ID()))
{
BigDecimal allocationAccounted0 = MConversionRate.convert(getCtx(),
allocationSource, getC_Currency_ID(),
as.getC_Currency_ID(), payment.getDateAcct(),
payment.getC_ConversionType_ID(), payment.getAD_Client_ID(), payment.getAD_Org_ID());
BigDecimal paymentAccounted0 = MConversionRate.convert(getCtx(),
paymentSource, getC_Currency_ID(),
as.getC_Currency_ID(), getDateAcct(),
getC_ConversionType_ID(), getAD_Client_ID(), getAD_Org_ID());
isSameSourceDiffPeriod = allocationAccounted0.abs().compareTo(paymentAccounted.abs()) == 0 &&
paymentAccounted0.abs().compareTo(totalAllocationAccounted.abs()) == 0;
}
}
if (acctDifference == null || acctDifference.signum() == 0)
{
log.fine("No Difference");
return null;
}
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
//
if (payment.isReceipt())
{
FactLine fl = fact.createLine (null, acct,as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
fl.setLine_ID(lineID);
if (!fact.isAcctBalanced())
{
if (!isSameSourceDiffPeriod && as.isCurrencyBalancing() && as.getC_Currency_ID() != payment.getC_Currency_ID() )
{
fact.createLine (null, as.getCurrencyBalancing_Acct(),as.getC_Currency_ID(), acctDifference);
} else
{
fl = fact.createLine (null, loss, gain,as.getC_Currency_ID(), acctDifference);
}
}
}
else
{
FactLine fl = fact.createLine (null, acct,as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
fl.setLine_ID(lineID);
if (!fact.isAcctBalanced())
{
if (!isSameSourceDiffPeriod && as.isCurrencyBalancing() && as.getC_Currency_ID() != payment.getC_Currency_ID() )
{
fact.createLine (null, as.getCurrencyBalancing_Acct(),as.getC_Currency_ID(), acctDifference.negate());
} else {
fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
}
}
}
return null;
}
/**************************************************************************
* Create Rounding Correction.
* Compares the Accounted Amount of the AR/AP Invoice to the
* Accounted Amount of the AR/AP Allocation
* @param as accounting schema
* @param fact fact
* @param bpAcct account
* @param payment payment
* @return Error Message or null if OK
*/
private String createInvoiceRounding(MAcctSchema as, Fact fact, MAccount bpAcct) {
PreparedStatement pstmt = null;
ResultSet rs = null;
// Invoice AR/AP
BigDecimal totalInvoiceSource = BigDecimal.ZERO;
BigDecimal totalInvoiceAccounted = BigDecimal.ZERO;
boolean isCMReversal =false ;
MInvoice invoice = null;
MPayment payment = null;
ArrayList<MInvoice> invList = new ArrayList<MInvoice>();
for (int i = 0; i < p_lines.length; i++)
{
DocLine_Allocation line = (DocLine_Allocation)p_lines[i];
if (line.getC_Invoice_ID() != 0)
invoice = new MInvoice (getCtx(), line.getC_Invoice_ID(), getTrxName());
if (line.getC_Payment_ID() != 0)
payment = new MPayment (getCtx(), line.getC_Payment_ID(), getTrxName());
if (invoice != null )
{
boolean isDebit = false;
// to cater for invoice reverse-accrual.
if (invoice.isSOTrx() && !invoice.isCreditMemo())
isDebit = true;
else if (!invoice.isSOTrx() && invoice.isCreditMemo() && invoice.getReversal_ID() > 0 )
isDebit = true;
else if (invoice.isSOTrx() && invoice.isCreditMemo() && invoice.getReversal_ID() == 0)
isDebit = true;
//
StringBuilder sql = new StringBuilder("SELECT ")
.append((isDebit )
? "SUM(AmtSourceDr), SUM(AmtAcctDr)" // so
: "SUM(AmtSourceCr), SUM(AmtAcctCr)") // po
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID=?") // Invoice
.append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'")
.append(" AND Account_ID= ? ");
pstmt = null;
rs = null;
try
{
pstmt = DB.prepareStatement(sql.toString(), getTrxName());
pstmt.setInt(1, MInvoice.Table_ID);
pstmt.setInt(2, invoice.getC_Invoice_ID());
pstmt.setInt(3, as.getC_AcctSchema_ID());
pstmt.setInt(4, bpAcct.getAccount_ID());
rs = pstmt.executeQuery();
if (rs.next())
{
BigDecimal invoiceSource = rs.getBigDecimal(1);
BigDecimal invoiceAccounted = rs.getBigDecimal(2);
if ( !invList.contains(invoice))
{
totalInvoiceSource =totalInvoiceSource.add(invoiceSource);
totalInvoiceAccounted =totalInvoiceAccounted.add(invoiceAccounted);
}
invList.add(invoice);
}
}
catch (Exception e)
{
throw new RuntimeException(e.getLocalizedMessage(), e);
}
finally {
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
}
}
BigDecimal allocInvoiceSource = BigDecimal.ZERO;
BigDecimal allocInvoiceAccounted = BigDecimal.ZERO;
MAllocationLine allocationLine = null;
FactLine[] factlines = fact.getLines();
boolean isExcludeCMGainLoss = false;
for (FactLine factLine : factlines) {
if (bpAcct != null) {
if (factLine.getAccount_ID() == bpAcct.getAccount_ID() )
{
if (factLine.getLine_ID() != 0 )
allocationLine = new MAllocationLine(getCtx(), factLine.getLine_ID(), getTrxName());
if (allocationLine != null)
invoice = allocationLine.getInvoice();
if (invoice.isSOTrx())
{
if (factLine.getC_Currency_ID() != as.getC_Currency_ID())
allocInvoiceSource = allocInvoiceSource.add(factLine.getAmtSourceCr());
if (!gainLossFactList.contains(factLine) && !invoice.isCreditMemo())
allocInvoiceAccounted = allocInvoiceAccounted.add(factLine.getAmtAcctCr());
if (!gainLossFactList.contains(factLine) && invoice.isCreditMemo() && invoice.getReversal_ID() > 0 ) {
allocInvoiceAccounted = allocInvoiceAccounted.add(factLine.getAmtAcctDr());
if (!invoice.getDateAcct().equals(getDateAcct()))
isCMReversal =true;
}
if (invoice!=null)
{
if (invoice.isCreditMemo() || invoice.getReversal_ID() > 0 ) {
allocInvoiceAccounted = allocInvoiceAccounted.add(cmGainLossAmt.abs());
cmGainLossAmt = Env.ZERO;
}
if (gainLossFactList.contains(factLine)) {
isExcludeCMGainLoss = true;
}
}
if (payment != null && payment.getReversal_ID() > 0 && !gainLossFactList.contains(factLine))
{
allocInvoiceSource = allocInvoiceSource.add(factLine.getAmtSourceDr());
allocInvoiceAccounted = allocInvoiceAccounted.add(factLine.getAmtAcctDr());
}
} else
{
if (as.getC_Currency_ID() != factLine.getC_Currency_ID())
allocInvoiceSource = allocInvoiceSource.add(factLine.getAmtSourceDr());
if (!gainLossFactList.contains(factLine) && !invoice.isCreditMemo())
allocInvoiceAccounted = allocInvoiceAccounted.add(factLine.getAmtAcctDr());
if (!gainLossFactList.contains(factLine) && invoice.isCreditMemo() && invoice.getReversal_ID() > 0 ) {
allocInvoiceAccounted = allocInvoiceAccounted.add(factLine.getAmtAcctCr());
// this is to cater for reverse-accrual.
if (!invoice.getDateAcct().equals(getDateAcct()))
isCMReversal =true;
}
if (invoice!=null )
{
if (invoice.isCreditMemo() || invoice.getReversal_ID() > 0 ) {
allocInvoiceAccounted = allocInvoiceAccounted.add(cmGainLossAmt.abs());
cmGainLossAmt = Env.ZERO;
}
if (gainLossFactList.contains(factLine)) {
isExcludeCMGainLoss = true;
}
}
}
}
}
}
BigDecimal acctDifference = null; // gain is negative
//
StringBuilder description = new StringBuilder("Invoice=(").append(getC_Currency_ID()).append(")").append(allocInvoiceSource).append("/").append(allocInvoiceAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
boolean isBPartnerAdjust = true;
if (allocInvoiceSource.abs().compareTo(totalInvoiceSource.abs()) == 0)
{
if (isExcludeCMGainLoss)
allocInvoiceAccounted = allocInvoiceAccounted.add(cmGainLossAmt);
if (payment != null && payment.getReversal_ID() > 0 )
allocInvoiceAccounted = allocInvoiceAccounted.subtract(gainLossAmt);
else
allocInvoiceAccounted = allocInvoiceAccounted.add(gainLossAmt);
if (isCMReversal)
acctDifference = totalInvoiceAccounted.subtract(allocInvoiceAccounted.abs());
else
acctDifference = allocInvoiceAccounted.subtract(totalInvoiceAccounted.abs()); // gain is positive for receipt
StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
} else{
MAllocationHdr[] allocations = MAllocationHdr.getOfInvoice(getCtx(), invoice.get_ID(), getTrxName());
for (MAllocationHdr alloc : allocations)
{
StringBuilder sql = new StringBuilder("SELECT ")
.append(invoice.isSOTrx()
? "SUM(AmtSourceCr), SUM(AmtAcctCr), SUM(AmtAcctDr)" // so
: "SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtAcctCr)") // po
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID=?") // allocation
.append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'")
.append(" AND Account_ID= ? ");
pstmt = null;
rs = null;
try
{
pstmt = DB.prepareStatement(sql.toString(), getTrxName());
pstmt.setInt(1, MAllocationHdr.Table_ID);
pstmt.setInt(2, alloc.get_ID());
pstmt.setInt(3, as.getC_AcctSchema_ID());
pstmt.setInt(4, bpAcct.getAccount_ID());
rs = pstmt.executeQuery();
if (rs.next())
{
BigDecimal allocateSource = rs.getBigDecimal(1);
BigDecimal allocateAccounted = rs.getBigDecimal(2);
BigDecimal allocateCredit = rs.getBigDecimal(3);
allocInvoiceSource =allocInvoiceSource.add(allocateSource != null ? allocateSource: BigDecimal.ZERO);
allocInvoiceAccounted =allocInvoiceAccounted.add(allocateAccounted != null ? allocateAccounted : BigDecimal.ZERO);
allocInvoiceAccounted= allocInvoiceAccounted.subtract(allocateCredit != null ? allocateCredit : BigDecimal.ZERO);
}
}
catch (Exception e)
{
throw new RuntimeException(e.getLocalizedMessage(), e);
}
finally {
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
}
double multiplier = allocInvoiceSource.doubleValue() / totalInvoiceSource.doubleValue();
// Reduce Orig Invoice Accounted
BigDecimal reduceOrigAccounted = totalInvoiceAccounted.multiply(BigDecimal.valueOf(multiplier));
if (reduceOrigAccounted.compareTo(totalInvoiceAccounted) < 0 )
totalInvoiceAccounted = reduceOrigAccounted;
if (isExcludeCMGainLoss)
allocInvoiceAccounted = allocInvoiceAccounted.add(cmGainLossAmt);
allocInvoiceAccounted = allocInvoiceAccounted.add(gainLossAmt);
// Difference based on percentage of Orig Invoice
acctDifference = allocInvoiceAccounted.subtract(totalInvoiceAccounted);
// ignore Tolerance
if (acctDifference.abs().compareTo(BigDecimal.valueOf(0.01)) < 0)
acctDifference = Env.ZERO;
// Round
int precision = as.getStdPrecision();
if (acctDifference.scale() > precision)
acctDifference = acctDifference.setScale(precision, RoundingMode.HALF_UP);
StringBuilder d2 = new StringBuilder("(partial) = ").append(acctDifference).append(" - Multiplier=").append(multiplier);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
}
if (acctDifference.signum() == 0)
{
log.fine("No Difference");
return null;
}
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
//
if (acctDifference.abs().compareTo(TOLERANCE) <= 0)
{
if (invoice.isSOTrx())
{
FactLine fl = null;
if (!isBPartnerAdjust)
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(),as.getC_Currency_ID(), acctDifference);
else
fl = fact.createLine (null, bpAcct,as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
if (!fact.isAcctBalanced())
{
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != invoice.getC_Currency_ID() )
{
fact.createLine (null, as.getCurrencyBalancing_Acct(),as.getC_Currency_ID(), acctDifference.negate());
} else
{
fl = fact.createLine (null, loss, gain,as.getC_Currency_ID(), acctDifference.negate());
}
}
}else
{
FactLine fl = null;
if (!isBPartnerAdjust)
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(),as.getC_Currency_ID(), acctDifference.negate());
else
fl = fact.createLine (null, bpAcct,as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
if (!fact.isAcctBalanced())
{
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != invoice.getC_Currency_ID() )
{
fact.createLine (null, as.getCurrencyBalancing_Acct(),as.getC_Currency_ID(), acctDifference);
} else {
fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference);
}
}
}
}
return null;
} // createInvoiceRounding
} // Doc_Allocation
/**

View File

@ -24,6 +24,7 @@ import java.sql.Savepoint;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
@ -43,6 +44,7 @@ import org.compiere.model.MMatchInv;
import org.compiere.model.MOrderLandedCostAllocation;
import org.compiere.model.ProductCost;
import org.compiere.model.X_M_Cost;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Trx;
@ -286,6 +288,21 @@ public class Doc_MatchInv extends Doc
cr.setQty(getQty().multiply(multiplier).negate());
}
}
// Rounding correction
if (m_receiptLine != null && m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
p_Error = createReceiptGainLoss(as, fact, getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as), m_receiptLine.getParent(), dr.getAmtSourceDr(), dr.getAmtAcctDr());
if (p_Error != null)
return null;
}
if (m_invoiceLine != null && m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) // in foreign currency
{
p_Error = createInvoiceGainLoss(as, fact, expense, m_invoiceLine.getParent(), cr.getAmtSourceCr(), cr.getAmtAcctCr());
if (p_Error != null)
return null;
}
if (m_matchInv.getReversal_ID() == 0)
{
cr.setC_Activity_ID(m_invoiceLine.getC_Activity_ID());
@ -831,4 +848,361 @@ public class Doc_MatchInv extends Doc
factLine.setM_Product_ID(m_invoiceLine.getM_Product_ID());
factLine.setQty(getQty());
}
private String createInvoiceGainLoss(MAcctSchema as, Fact fact, MAccount acct,
MInvoice invoice, BigDecimal matchInvSource, BigDecimal matchInvAccounted)
{
BigDecimal invoiceSource = null;
BigDecimal invoiceAccounted = null;
//
StringBuilder sql = new StringBuilder()
.append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID=?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID=?")
.append(" AND PostingType='A'");
// For Invoice
List<Object> valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MInvoice.Table_ID, invoice.getC_Invoice_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesInv != null) {
invoiceSource = (BigDecimal) valuesInv.get(0); // AmtSourceDr
invoiceAccounted = (BigDecimal) valuesInv.get(1); // AmtAcctDr
if (invoiceSource.signum() == 0 && invoiceAccounted.signum() == 0) {
invoiceSource = (BigDecimal) valuesInv.get(2); // AmtSourceCr
invoiceAccounted = (BigDecimal) valuesInv.get(3); // AmtAcctCr
}
}
// Requires that Invoice is Posted
if (invoiceSource == null || invoiceAccounted == null)
return null;
//
if (m_matchInv.getReversal_ID() == 0 || m_matchInv.get_ID() < m_matchInv.getReversal_ID())
{
String matchInvLineSql = "SELECT M_MatchInv_ID FROM M_MatchInv "
+ "WHERE C_InvoiceLine_ID IN (SELECT C_InvoiceLine_ID FROM C_InvoiceLine WHERE C_Invoice_ID=?) "
+ "AND COALESCE(Reversal_ID,0)=0";
List<List<Object>> list = DB.getSQLArrayObjectsEx(getTrxName(), matchInvLineSql, invoice.get_ID());
StringBuffer s = new StringBuffer();
if (list == null)
return null;
for (int index=0; index < list.size(); index++)
{
List<Object> l = list.get(index);
s.append(l.get(0));
if (index != list.size()-1)
s.append(",");
}
sql = new StringBuilder()
.append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID IN (").append(s).append(")")
.append(" AND Record_ID <> ?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID=?")
.append(" AND PostingType='A'");
}
else
{
sql = new StringBuilder()
.append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID IN (").append(m_matchInv.getReversal_ID()).append(")")
.append(" AND Record_ID <> ?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID=?")
.append(" AND PostingType='A'");
}
BigDecimal acctDifference = null; // gain is negative
// For Match Invoice
valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MMatchInv.Table_ID, get_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesInv != null)
{
BigDecimal totalAmtSourceDr = (BigDecimal) valuesInv.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtAcctDr = (BigDecimal) valuesInv.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
BigDecimal totalAmtSourceCr = (BigDecimal) valuesInv.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
BigDecimal totalAmtAcctCr = (BigDecimal) valuesInv.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
}
else if (totalAmtSourceCr.signum() == 0 && totalAmtAcctCr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
}
else
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
acctDifference = totalAmtAcctCr.negate();
}
else
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
acctDifference = totalAmtSourceDr.negate();
}
}
}
StringBuilder description = new StringBuilder("Invoice=(").append(invoice.getC_Currency_ID()).append(")").append(invoiceSource).append("/").append(invoiceAccounted)
.append(" - MatchInv=(").append(getC_Currency_ID()).append(")").append(matchInvSource).append("/").append(matchInvAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
//
// Full Payment in currency
if (acctDifference == null && matchInvSource.compareTo(invoiceSource) == 0)
{
acctDifference = matchInvAccounted.subtract(invoiceAccounted.abs()); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
}
if (acctDifference == null || acctDifference.signum() == 0)
{
log.fine("No Difference");
return null;
}
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
//
if (invoice.isSOTrx())
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
updateFactLine(fl);
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != invoice.getC_Currency_ID()) {
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference);
} else {
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference);
}
fl.setDescription(description.toString());
updateFactLine(fl);
}
else
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
updateFactLine(fl);
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != invoice.getC_Currency_ID()) {
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference.negate());
} else {
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
}
fl.setDescription(description.toString());
updateFactLine(fl);
}
return null;
} // createInvoiceGainLoss
private String createReceiptGainLoss(MAcctSchema as, Fact fact, MAccount acct,
MInOut receipt, BigDecimal matchInvSource, BigDecimal matchInvAccounted)
{
BigDecimal receiptSource = null;
BigDecimal receiptAccounted = null;
//
StringBuilder sql = new StringBuilder()
.append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID=?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID=?")
.append(" AND PostingType='A'");
// For Receipt
List<Object> valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MInOut.Table_ID, receipt.getM_InOut_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesInv != null) {
receiptSource = (BigDecimal) valuesInv.get(0); // AmtSourceDr
receiptAccounted = (BigDecimal) valuesInv.get(1); // AmtAcctDr
if (receiptSource.signum() == 0 && receiptAccounted.signum() == 0) {
receiptSource = (BigDecimal) valuesInv.get(2); // AmtSourceCr
receiptAccounted = (BigDecimal) valuesInv.get(3); // AmtAcctCr
}
}
// Requires that Receipt is Posted
if (receiptSource == null || receiptAccounted == null)
return null;
//
if (m_matchInv.getReversal_ID() == 0 || m_matchInv.get_ID() < m_matchInv.getReversal_ID())
{
String matchInvLineSql = "SELECT M_MatchInv_ID FROM M_MatchInv "
+ "WHERE M_InOutLine_ID IN (SELECT M_InOutLine_ID FROM M_InOutLine WHERE M_InOut_ID=?) "
+ "AND COALESCE(Reversal_ID,0)=0";
List<List<Object>> list = DB.getSQLArrayObjectsEx(getTrxName(), matchInvLineSql, receipt.get_ID());
StringBuffer s = new StringBuffer();
if (list == null)
return null;
for (int index=0; index < list.size(); index++)
{
List<Object> l = list.get(index);
s.append(l.get(0));
if (index != list.size()-1)
s.append(",");
}
sql = new StringBuilder()
.append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID IN (").append(s).append(")")
.append(" AND Record_ID <> ?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID=?")
.append(" AND PostingType='A'");
}
else
{
sql = new StringBuilder()
.append("SELECT SUM(AmtSourceDr), SUM(AmtAcctDr), SUM(AmtSourceCr), SUM(AmtAcctCr)")
.append(" FROM Fact_Acct ")
.append("WHERE AD_Table_ID=? AND Record_ID IN (").append(m_matchInv.getReversal_ID()).append(")")
.append(" AND Record_ID <> ?")
.append(" AND C_AcctSchema_ID=?")
.append(" AND Account_ID=?")
.append(" AND PostingType='A'");
}
BigDecimal acctDifference = null; // gain is negative
// For Match Invoice
valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MMatchInv.Table_ID, get_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesInv != null)
{
BigDecimal totalAmtSourceDr = (BigDecimal) valuesInv.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtAcctDr = (BigDecimal) valuesInv.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
BigDecimal totalAmtSourceCr = (BigDecimal) valuesInv.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
BigDecimal totalAmtAcctCr = (BigDecimal) valuesInv.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
}
else if (totalAmtSourceCr.signum() == 0 && totalAmtAcctCr.signum() == 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
}
else
{
if (m_matchInv.getReversal_ID() == 0 || m_matchInv.get_ID() < m_matchInv.getReversal_ID())
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr);
}
else
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr).subtract(totalAmtAcctDr);
}
}
else
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
matchInvSource = matchInvSource.add(totalAmtSourceDr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctDr);
acctDifference = totalAmtAcctCr.negate();
}
else
{
matchInvSource = matchInvSource.add(totalAmtSourceCr);
matchInvAccounted = matchInvAccounted.add(totalAmtAcctCr);
acctDifference = totalAmtAcctDr.negate();
}
}
}
}
StringBuilder description = new StringBuilder("InOut=(").append(m_invoiceLine.getParent().getC_Currency_ID()).append(")").append(receiptSource).append("/").append(receiptAccounted)
.append(" - MatchInv=(").append(getC_Currency_ID()).append(")").append(matchInvSource).append("/").append(matchInvAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
// Full Payment in currency
if (acctDifference == null && matchInvSource.compareTo(receiptSource) == 0)
{
acctDifference = matchInvAccounted.subtract(receiptAccounted.abs()); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
}
if (acctDifference == null || acctDifference.signum() == 0)
{
log.fine("No Difference");
return null;
}
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
//
if (!receipt.isSOTrx())
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
updateFactLine(fl);
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != m_invoiceLine.getParent().getC_Currency_ID()) {
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference);
} else {
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference);
}
fl.setDescription(description.toString());
updateFactLine(fl);
}
else
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
updateFactLine(fl);
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != m_invoiceLine.getParent().getC_Currency_ID()) {
fl = fact.createLine (null, as.getCurrencyBalancing_Acct(), as.getC_Currency_ID(), acctDifference.negate());
} else {
fl = fact.createLine (null, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
}
fl.setDescription(description.toString());
updateFactLine(fl);
}
return null;
} // createReceiptGainLoss
} // Doc_MatchInv

View File

@ -1124,6 +1124,8 @@ public final class FactLine extends X_Fact_Acct
if (MMovement.Table_ID == AD_Table_ID)
sql.append(" AND M_Locator_ID=?");
// end MZ
sql.append(" ORDER BY Fact_Acct_ID ");
PreparedStatement pstmt = null;
ResultSet rs = null;
try