/******************************************************************************
* Product: Adempiere ERP & CRM Smart Business Solution *
* Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. 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., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA *
* or via info@compiere.org or http://www.compiere.org/license.html *
*****************************************************************************/
package org.compiere.acct;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.compiere.model.MAccount;
import org.compiere.model.MAcctSchema;
import org.compiere.model.MAcctSchemaElement;
import org.compiere.model.MAllocationHdr;
import org.compiere.model.MAllocationLine;
import org.compiere.model.MCashLine;
import org.compiere.model.MCharge;
import org.compiere.model.MConversionRate;
import org.compiere.model.MDocType;
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.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
/**
* Post Allocation Documents.
*
* Table: C_AllocationHdr
* Document Types: CMA
*
* @author Jorg Janke
* @version $Id: Doc_Allocation.java,v 1.6 2006/07/30 00:53:33 jjanke Exp $
*
* FR [ 1840016 ] Avoid usage of clearing accounts - subject to C_AcctSchema.IsPostIfClearingEqual
* Avoid posting if Receipt and both accounts Unallocated Cash and Receivable are equal
* Avoid posting if Payment and both accounts Payment Select and Liability are equal
*
* @author phib
* BF [ 2019262 ] Allocation posting currency gain/loss omits line reference
*
*/
public class Doc_AllocationHdr extends Doc
{
/**
* Constructor
* @param as accounting schema
* @param rs record
* @param trxName trx
*/
public Doc_AllocationHdr (MAcctSchema as, ResultSet rs, String trxName)
{
super (as, MAllocationHdr.class, rs, DOCTYPE_Allocation, trxName);
} // Doc_Allocation
/** Tolerance G&L */
private static final BigDecimal TOLERANCE = BigDecimal.valueOf(0.02);
/** Facts */
private ArrayList m_facts = null;
private ArrayList invGainLossFactLines = null;
private ArrayList payGainLossFactLines = null;
/**
* Load Specific Document Details
* @return error message or null
*/
protected String loadDocumentDetails ()
{
MAllocationHdr alloc = (MAllocationHdr)getPO();
setDateDoc(alloc.getDateTrx());
// Contained Objects
p_lines = loadLines(alloc);
return null;
} // loadDocumentDetails
/**
* Load Invoice Line
* @param alloc header
* @return DocLine Array
*/
private DocLine[] loadLines(MAllocationHdr alloc)
{
ArrayList list = new ArrayList();
MAllocationLine[] lines = alloc.getLines(false);
for (int i = 0; i < lines.length; i++)
{
MAllocationLine line = lines[i];
DocLine_Allocation docLine = new DocLine_Allocation(line, this);
// Get Payment Conversion Rate
if (line.getC_Payment_ID() != 0)
{
MPayment payment = new MPayment (getCtx(), line.getC_Payment_ID(), getTrxName());
int C_ConversionType_ID = payment.getC_ConversionType_ID();
docLine.setC_ConversionType_ID(C_ConversionType_ID);
if (payment.isOverrideCurrencyRate())
docLine.setCurrencyRate(payment.getCurrencyRate());
}
else if (line.getC_Invoice_ID() != 0)
{
MInvoice invoice = new MInvoice (getCtx(), line.getC_Invoice_ID(), getTrxName());
int C_ConversionType_ID = invoice.getC_ConversionType_ID();
docLine.setC_ConversionType_ID(C_ConversionType_ID);
if (invoice.isOverrideCurrencyRate())
docLine.setCurrencyRate(invoice.getCurrencyRate());
}
//
if (log.isLoggable(Level.FINE)) log.fine(docLine.toString());
list.add (docLine);
}
// Return Array
DocLine[] dls = new DocLine[list.size()];
list.toArray(dls);
return dls;
} // loadLines
/**
* Get Source Currency Balance - subtracts line and tax amounts from total - no rounding
* @return positive amount, if total invoice is bigger than lines
*/
public BigDecimal getBalance()
{
BigDecimal retValue = Env.ZERO;
return retValue;
} // getBalance
/**
* Create Facts (the accounting logic) for
* CMA.
*
* AR_Invoice_Payment
* UnAllocatedCash DR
* or C_Prepayment
* DiscountExp DR
* WriteOff DR
* Receivables CR
* AR_Invoice_Cash
* CashTransfer DR
* DiscountExp DR
* WriteOff DR
* Receivables CR
*
* AP_Invoice_Payment
* Liability DR
* DiscountRev CR
* WriteOff CR
* PaymentSelect CR
* or V_Prepayment
* AP_Invoice_Cash
* Liability DR
* DiscountRev CR
* WriteOff CR
* CashTransfer CR
* CashBankTransfer
* -
* ==============================
* Realized Gain and Loss
* AR/AP DR CR
* Realized G/L DR CR
*
*
*
* Tax needs to be corrected for discount and write-off;
* Currency gain and loss is realized here.
* @param as accounting schema
* @return Fact
*/
public ArrayList createFacts (MAcctSchema as)
{
m_facts = new ArrayList();
invGainLossFactLines = new ArrayList();
payGainLossFactLines = new ArrayList();
// Do not create fact lines for reversal of invoice
if (p_lines.length == 2)
{
DocLine_Allocation line1 = (DocLine_Allocation)p_lines[0];
DocLine_Allocation line2 = (DocLine_Allocation)p_lines[1];
if (line1.getC_Payment_ID() == 0 && line1.getC_Order_ID() == 0 && line1.getC_CashLine_ID() == 0 && line1.getC_Invoice_ID() > 0
&& line2.getC_Payment_ID() == 0 && line2.getC_Order_ID() == 0 && line2.getC_CashLine_ID() == 0 && line2.getC_Invoice_ID() > 0)
{
MInvoice invoice1 = new MInvoice(Env.getCtx(), line1.getC_Invoice_ID(), getTrxName());
MInvoice invoice2 = new MInvoice(Env.getCtx(), line2.getC_Invoice_ID(), getTrxName());
if (invoice1.getGrandTotal().equals(invoice2.getGrandTotal().negate())
&& invoice2.getReversal_ID() == invoice1.getC_Invoice_ID())
{
return m_facts;
}
}
}
// create Fact Header
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);
MAccount bpAcct = null; // Liability/Receivables
MAccount bpAcctAr = null;
MAccount bpAcctAp = null;
for (int i = 0; i < p_lines.length; i++)
{
DocLine_Allocation line = (DocLine_Allocation)p_lines[i];
setC_BPartner_ID(line.getC_BPartner_ID());
// CashBankTransfer - all references null and Discount/WriteOff = 0
if (line.getC_Payment_ID() != 0
&& line.getC_Invoice_ID() == 0 && line.getC_Order_ID() == 0
&& line.getC_CashLine_ID() == 0 && line.getC_BPartner_ID() == 0
&& Env.ZERO.compareTo(line.getDiscountAmt()) == 0
&& Env.ZERO.compareTo(line.getWriteOffAmt()) == 0)
continue;
// Receivables/Liability Amt
BigDecimal allocationSource = line.getAmtSource()
.add(line.getDiscountAmt())
.add(line.getWriteOffAmt());
BigDecimal allocationSourceForRGL = allocationSource; // for realized gain & loss purposes
BigDecimal allocationAccounted = Env.ZERO; // AR/AP balance corrected
@SuppressWarnings("unused")
BigDecimal allocationAccountedForRGL = Env.ZERO; // for realized gain & loss purposes
FactLine fl = null;
FactLine flForRGL = null;
//
MPayment payment = null;
if (line.getC_Payment_ID() != 0)
payment = new MPayment (getCtx(), line.getC_Payment_ID(), getTrxName());
MInvoice invoice = null;
if (line.getC_Invoice_ID() != 0)
invoice = new MInvoice (getCtx(), line.getC_Invoice_ID(), getTrxName());
BigDecimal allocPayAccounted = Env.ZERO;
BigDecimal allocPaySource = Env.ZERO;
// No Invoice
if (invoice == null)
{
// adaxa-pb: allocate to charges
// Charge Only
if (line.getC_Invoice_ID() == 0 && line.getC_Payment_ID() == 0 && line.getC_Charge_ID() != 0 )
{
fl = fact.createLine (line, line.getChargeAccount(as, line.getAmtSource()),
getC_Currency_ID(), line.getAmtSource());
}
// Payment Only
else if (line.getC_Invoice_ID() == 0 && line.getC_Payment_ID() != 0)
{
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());
allocPayAccounted = allocPayAccounted.add(fl.getAcctBalance());
}
}
else
{
p_Error = "Cannot determine SO/PO";
log.log(Level.SEVERE, p_Error);
return null;
}
}
// Sales Invoice
else if (invoice.isSOTrx())
{
// Avoid usage of clearing accounts
// If both accounts Unallocated Cash and Receivable are equal
// then don't post
MAccount acct_unallocated_cash = null;
if (line.getC_Payment_ID() != 0)
acct_unallocated_cash = getPaymentAcct(as, line.getC_Payment_ID());
else if (line.getC_CashLine_ID() != 0)
acct_unallocated_cash = getCashAcct(as, line.getC_CashLine_ID());
MAccount acct_receivable = getAccount(Doc.ACCTTYPE_C_Receivable, as);
if ((!as.isPostIfClearingEqual()) && acct_unallocated_cash != null && acct_unallocated_cash.equals(acct_receivable) && (!isInterOrg)) {
// if not using clearing accounts, then don't post amtsource
// change the allocationsource to be writeoff + discount
allocationSource = line.getDiscountAmt().add(line.getWriteOffAmt());
} else {
// Normal behavior -- unchanged if using clearing accounts
// Payment/Cash DR
if (line.getC_Payment_ID() != 0)
{
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 )
allocPayAccounted = allocPayAccounted.add(fl.getAcctBalance().negate());
else
allocPayAccounted = allocPayAccounted.add(fl.getAcctBalance());
}
}
else if (line.getC_CashLine_ID() != 0)
{
fl = fact.createLine (line, getCashAcct(as, line.getC_CashLine_ID()),
getC_Currency_ID(), line.getAmtSource(), null);
MCashLine cashLine = new MCashLine (getCtx(), line.getC_CashLine_ID(), getTrxName());
if (fl != null && cashLine.get_ID() != 0)
fl.setAD_Org_ID(cashLine.getAD_Org_ID());
}
}
// End Avoid usage of clearing accounts
// Discount DR
if (Env.ZERO.compareTo(line.getDiscountAmt()) != 0)
{
fl = fact.createLine (line, getAccount(Doc.ACCTTYPE_DiscountExp, as),
getC_Currency_ID(), line.getDiscountAmt(), null);
if (fl != null && payment != null)
fl.setAD_Org_ID(payment.getAD_Org_ID());
}
// Write off DR
if (Env.ZERO.compareTo(line.getWriteOffAmt()) != 0)
{
fl = fact.createLine (line, getAccount(Doc.ACCTTYPE_WriteOff, as),
getC_Currency_ID(), line.getWriteOffAmt(), null);
if (fl != null && payment != null)
fl.setAD_Org_ID(payment.getAD_Org_ID());
}
// AR Invoice Amount CR
if (as.isAccrual())
{
if (bpAcctAr == null)
bpAcctAr = getAccount(Doc.ACCTTYPE_C_Receivable, as);
bpAcct = bpAcctAr;
fl = fact.createLine (line, bpAcct,
getC_Currency_ID(), null, allocationSource); // payment currency
if (fl != null)
allocationAccounted = fl.getAcctBalance().negate();
if (fl != null && invoice != null)
fl.setAD_Org_ID(invoice.getAD_Org_ID());
// for Realized Gain & Loss
flForRGL = factForRGL.createLine (line, bpAcct,
getC_Currency_ID(), null, allocationSourceForRGL); // payment currency
if (flForRGL != null)
allocationAccountedForRGL = flForRGL.getAcctBalance().negate();
}
else // Cash Based
{
allocationAccounted = createCashBasedAcct (as, fact,
invoice, allocationSource);
allocationAccountedForRGL = allocationAccounted;
}
}
// Purchase Invoice
else
{
// Avoid usage of clearing accounts
// If both accounts Payment Select and Liability are equal
// then don't post
MAccount acct_payment_select = null;
if (line.getC_Payment_ID() != 0)
acct_payment_select = getPaymentAcct(as, line.getC_Payment_ID());
else if (line.getC_CashLine_ID() != 0)
acct_payment_select = getCashAcct(as, line.getC_CashLine_ID());
MAccount acct_liability = getAccount(Doc.ACCTTYPE_V_Liability, as);
boolean isUsingClearing = true;
// Save original allocation source for realized gain & loss purposes
allocationSourceForRGL = allocationSourceForRGL.negate();
if ((!as.isPostIfClearingEqual()) && acct_payment_select != null && acct_payment_select.equals(acct_liability) && (!isInterOrg)) {
// if not using clearing accounts, then don't post amtsource
// change the allocationsource to be writeoff + discount
allocationSource = line.getDiscountAmt().add(line.getWriteOffAmt());
isUsingClearing = false;
}
// End Avoid usage of clearing accounts
allocationSource = allocationSource.negate(); // allocation is negative
// AP Invoice Amount DR
if (as.isAccrual())
{
if (bpAcctAp == null)
bpAcctAp = getAccount(Doc.ACCTTYPE_V_Liability, as);
bpAcct = bpAcctAp;
fl = fact.createLine (line, bpAcct,
getC_Currency_ID(), allocationSource, null); // payment currency
if (fl != null)
allocationAccounted = fl.getAcctBalance();
if (fl != null && invoice != null)
fl.setAD_Org_ID(invoice.getAD_Org_ID());
// for Realized Gain & Loss
flForRGL = factForRGL.createLine (line, bpAcct,
getC_Currency_ID(), allocationSourceForRGL, null); // payment currency
if (flForRGL != null)
allocationAccountedForRGL = flForRGL.getAcctBalance();
}
else // Cash Based
{
allocationAccounted = createCashBasedAcct (as, fact,
invoice, allocationSource);
allocationAccountedForRGL = allocationAccounted;
}
// Discount CR
if (Env.ZERO.compareTo(line.getDiscountAmt()) != 0)
{
fl = fact.createLine (line, getAccount(Doc.ACCTTYPE_DiscountRev, as),
getC_Currency_ID(), null, line.getDiscountAmt().negate());
if (fl != null && payment != null)
fl.setAD_Org_ID(payment.getAD_Org_ID());
}
// Write off CR
if (Env.ZERO.compareTo(line.getWriteOffAmt()) != 0)
{
fl = fact.createLine (line, getAccount(Doc.ACCTTYPE_WriteOff, as),
getC_Currency_ID(), null, line.getWriteOffAmt().negate());
if (fl != null && payment != null)
fl.setAD_Org_ID(payment.getAD_Org_ID());
}
// Payment/Cash CR
if (isUsingClearing && line.getC_Payment_ID() != 0) // Avoid usage of clearing accounts
{
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());
if (fl != null)
allocPayAccounted = allocPayAccounted.add(fl.getAcctBalance().negate());
}
else if (isUsingClearing && line.getC_CashLine_ID() != 0) // Avoid usage of clearing accounts
{
fl = fact.createLine (line, getCashAcct(as, line.getC_CashLine_ID()),
getC_Currency_ID(), null, line.getAmtSource().negate());
MCashLine cashLine = new MCashLine (getCtx(), line.getC_CashLine_ID(), getTrxName());
if (fl != null && cashLine.get_ID() != 0)
fl.setAD_Org_ID(cashLine.getAD_Org_ID());
}
}
// VAT Tax Correction
if (invoice != null && as.isTaxCorrection())
{
BigDecimal taxCorrectionAmt = Env.ZERO;
if (as.isTaxCorrectionDiscount())
taxCorrectionAmt = line.getDiscountAmt();
if (as.isTaxCorrectionWriteOff())
taxCorrectionAmt = taxCorrectionAmt.add(line.getWriteOffAmt());
//
if (taxCorrectionAmt.signum() != 0)
{
if (!createTaxCorrection(as, fact, line,
getAccount(invoice.isSOTrx() ? Doc.ACCTTYPE_DiscountExp : Doc.ACCTTYPE_DiscountRev, as),
getAccount(Doc.ACCTTYPE_WriteOff, as), invoice.isSOTrx()))
{
p_Error = "Cannot create Tax correction";
return null;
}
}
}
// Realized Gain & Loss
if (invoice != null && as.isAccrual()
&& (getC_Currency_ID() != as.getC_Currency_ID() // payment allocation in foreign currency
|| getC_Currency_ID() != line.getInvoiceC_Currency_ID())) // allocation <> invoice currency
{
p_Error = createInvoiceGainLoss (line, as, fact, bpAcct, invoice,
allocationSource, allocationAccounted);
if (p_Error != null)
return null;
}
allocPaySource = allocPaySource.add(line.getAmtSource());
if (payment != null && getC_Currency_ID() != as.getC_Currency_ID())
{
p_Error = createPaymentGainLoss (line, as, fact, getPaymentAcct(as, payment.get_ID()), payment,
allocPaySource, allocPayAccounted);
if (p_Error != null)
return null;
}
} // for all lines
// rounding adjustment
if (getC_Currency_ID() != as.getC_Currency_ID())
{
p_Error = createInvoiceRoundingCorrection (as, fact, bpAcctAr, bpAcctAp);
if (p_Error != null)
return null;
p_Error = createPaymentRoundingCorrection (as, fact);
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;
// more than one line (i.e. crossing one payment+ with a payment-, or an invoice against a credit memo)
// verify if the sum of all facts is zero net
FactLine[] factlines = fact.getLines();
BigDecimal netBalance = Env.ZERO;
FactLine prevFactLine = null;
for (FactLine factLine : factlines) {
netBalance = netBalance.add(factLine.getAmtSourceDr()).subtract(factLine.getAmtSourceCr());
if (prevFactLine != null) {
if (! equalFactLineIDs(prevFactLine, factLine)) {
allEquals = false;
break;
}
}
prevFactLine = factLine;
}
if (netBalance.compareTo(Env.ZERO) == 0 && allEquals) {
// delete the postings
for (FactLine factline : factlines)
fact.remove(factline);
}
}
if (getC_Currency_ID() != as.getC_Currency_ID())
balanceAccounting(as, fact);
// reset line info
setC_BPartner_ID(0);
//
m_facts.add(fact);
return m_facts;
} // createFact
/**
* Verify if the posting involves two or more organizations
* @return true if there are more than one org involved on the posting
*/
private boolean isInterOrg(MAcctSchema as) {
MAcctSchemaElement elementorg = as.getAcctSchemaElement(MAcctSchemaElement.ELEMENTTYPE_Organization);
if (elementorg == null || !elementorg.isBalanced()) {
// no org element or not need to be balanced
return false;
}
if (p_lines.length <= 0) {
// no lines
return false;
}
int startorg = p_lines[0].getAD_Org_ID();
// validate if the allocation involves more than one org
for (int i = 0; i < p_lines.length; i++) {
DocLine_Allocation line = (DocLine_Allocation)p_lines[i];
int orgpayment = startorg;
MPayment payment = null;
if (line.getC_Payment_ID() != 0) {
payment = new MPayment (getCtx(), line.getC_Payment_ID(), getTrxName());
orgpayment = payment.getAD_Org_ID();
}
int orginvoice = startorg;
MInvoice invoice = null;
if (line.getC_Invoice_ID() != 0) {
invoice = new MInvoice (getCtx(), line.getC_Invoice_ID(), getTrxName());
orginvoice = invoice.getAD_Org_ID();
}
int orgcashline = startorg;
MCashLine cashline = null;
if (line.getC_CashLine_ID() != 0) {
cashline = new MCashLine (getCtx(), line.getC_CashLine_ID(), getTrxName());
orgcashline = cashline.getAD_Org_ID();
}
int orgorder = startorg;
MOrder order = null;
if (line.getC_Order_ID() != 0) {
order = new MOrder (getCtx(), line.getC_Order_ID(), getTrxName());
orgorder = order.getAD_Org_ID();
}
if ( line.getAD_Org_ID() != startorg
|| orgpayment != startorg
|| orginvoice != startorg
|| orgcashline != startorg
|| orgorder != startorg)
return true;
}
return false;
}
/**
* Compare the dimension ID's from two factlines
* @param allEquals
* @param prevFactLine
* @param factLine
* @return boolean indicating if both dimension ID's are equal
*/
private boolean equalFactLineIDs(FactLine prevFactLine, FactLine factLine) {
return (factLine.getA_Asset_ID() == prevFactLine.getA_Asset_ID()
&& factLine.getAccount_ID() == prevFactLine.getAccount_ID()
&& factLine.getAD_Client_ID() == prevFactLine.getAD_Client_ID()
&& factLine.getAD_Org_ID() == prevFactLine.getAD_Org_ID()
&& factLine.getAD_OrgTrx_ID() == prevFactLine.getAD_OrgTrx_ID()
&& factLine.getC_AcctSchema_ID() == prevFactLine.getC_AcctSchema_ID()
&& factLine.getC_Activity_ID() == prevFactLine.getC_Activity_ID()
&& factLine.getC_BPartner_ID() == prevFactLine.getC_BPartner_ID()
&& factLine.getC_Campaign_ID() == prevFactLine.getC_Campaign_ID()
&& factLine.getC_Currency_ID() == prevFactLine.getC_Currency_ID()
&& factLine.getC_LocFrom_ID() == prevFactLine.getC_LocFrom_ID()
&& factLine.getC_LocTo_ID() == prevFactLine.getC_LocTo_ID()
&& factLine.getC_Period_ID() == prevFactLine.getC_Period_ID()
&& factLine.getC_Project_ID() == prevFactLine.getC_Project_ID()
&& factLine.getC_ProjectPhase_ID() == prevFactLine.getC_ProjectPhase_ID()
&& factLine.getC_ProjectTask_ID() == prevFactLine.getC_ProjectTask_ID()
&& factLine.getC_SalesRegion_ID() == prevFactLine.getC_SalesRegion_ID()
&& factLine.getC_SubAcct_ID() == prevFactLine.getC_SubAcct_ID()
&& factLine.getC_Tax_ID() == prevFactLine.getC_Tax_ID()
&& factLine.getC_UOM_ID() == prevFactLine.getC_UOM_ID()
&& factLine.getGL_Budget_ID() == prevFactLine.getGL_Budget_ID()
&& factLine.getGL_Category_ID() == prevFactLine.getGL_Category_ID()
&& factLine.getM_Locator_ID() == prevFactLine.getM_Locator_ID()
&& factLine.getM_Product_ID() == prevFactLine.getM_Product_ID()
&& factLine.getUserElement1_ID() == prevFactLine.getUserElement1_ID()
&& factLine.getUserElement2_ID() == prevFactLine.getUserElement2_ID()
&& factLine.getUser1_ID() == prevFactLine.getUser1_ID()
&& factLine.getUser2_ID() == prevFactLine.getUser2_ID());
}
/**
* Create Cash Based Acct
* @param as accounting schema
* @param fact fact
* @param invoice invoice
* @param allocationSource allocation amount (incl discount, writeoff)
* @return Accounted Amt
*/
private BigDecimal createCashBasedAcct (MAcctSchema as, Fact fact, MInvoice invoice,
BigDecimal allocationSource)
{
BigDecimal allocationAccounted = Env.ZERO;
// Multiplier
double percent = invoice.getGrandTotal().doubleValue() / allocationSource.doubleValue();
if (percent > 0.99 && percent < 1.01)
percent = 1.0;
if (log.isLoggable(Level.CONFIG)) log.config("Multiplier=" + percent + " - GrandTotal=" + invoice.getGrandTotal()
+ " - Allocation Source=" + allocationSource);
// Get Invoice Postings
Doc_Invoice docInvoice = (Doc_Invoice)Doc.get(as,
MInvoice.Table_ID, invoice.getC_Invoice_ID(), getTrxName());
docInvoice.loadDocumentDetails();
allocationAccounted = docInvoice.createFactCash(as, fact, BigDecimal.valueOf(percent));
if (log.isLoggable(Level.CONFIG)) log.config("Allocation Accounted=" + allocationAccounted);
// Cash Based Commitment Release
if (as.isCreatePOCommitment() && !invoice.isSOTrx())
{
MInvoiceLine[] lines = invoice.getLines();
for (int i = 0; i < lines.length; i++)
{
Fact factC = Doc_Order.getCommitmentRelease(as, this,
lines[i].getQtyInvoiced(), lines[i].getC_InvoiceLine_ID(), BigDecimal.valueOf(percent));
if (factC == null)
return null;
m_facts.add(factC);
}
} // Commitment
return allocationAccounted;
} // createCashBasedAcct
/**
* Get Payment (Unallocated Payment or Payment Selection) Acct of Bank Account
* @param as accounting schema
* @param C_Payment_ID payment
* @return acct
*/
private MAccount getPaymentAcct (MAcctSchema as, int C_Payment_ID)
{
setC_BankAccount_ID(0);
// Doc.ACCTTYPE_UnallocatedCash (AR) or C_Prepayment
// or Doc.ACCTTYPE_PaymentSelect (AP) or V_Prepayment
int accountType = Doc.ACCTTYPE_UnallocatedCash;
//
int C_Charge_ID = 0;
String sql = "SELECT p.C_BankAccount_ID, d.DocBaseType, p.IsReceipt, p.IsPrepayment, p.C_Charge_ID "
+ "FROM C_Payment p INNER JOIN C_DocType d ON (p.C_DocType_ID=d.C_DocType_ID) "
+ "WHERE C_Payment_ID=?";
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, getTrxName());
pstmt.setInt (1, C_Payment_ID);
rs = pstmt.executeQuery ();
if (rs.next ())
{
setC_BankAccount_ID(rs.getInt(1));
C_Charge_ID = rs.getInt(5); // Charge
if (DOCTYPE_APPayment.equals(rs.getString(2)))
accountType = Doc.ACCTTYPE_PaymentSelect;
// Prepayment
if ("Y".equals(rs.getString(4))) // Prepayment
{
if ("Y".equals(rs.getString(3))) // Receipt
accountType = Doc.ACCTTYPE_C_Prepayment;
else
accountType = Doc.ACCTTYPE_V_Prepayment;
}
}
}
catch (Exception e)
{
throw new RuntimeException(e.getLocalizedMessage(), e);
}
finally
{
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
//
if (getC_BankAccount_ID() <= 0)
{
log.log(Level.SEVERE, "NONE for C_Payment_ID=" + C_Payment_ID);
return null;
}
if (C_Charge_ID != 0)
return MCharge.getAccount(C_Charge_ID, as);
return getAccount (accountType, as);
} // getPaymentAcct
/**
* Get Cash (Transfer) Acct of CashBook
* @param as accounting schema
* @param C_CashLine_ID
* @return acct
*/
private MAccount getCashAcct (MAcctSchema as, int C_CashLine_ID)
{
String sql = "SELECT c.C_CashBook_ID "
+ "FROM C_Cash c, C_CashLine cl "
+ "WHERE c.C_Cash_ID=cl.C_Cash_ID AND cl.C_CashLine_ID=?";
setC_CashBook_ID(DB.getSQLValue(null, sql, C_CashLine_ID));
if (getC_CashBook_ID() <= 0)
{
log.log(Level.SEVERE, "NONE for C_CashLine_ID=" + C_CashLine_ID);
return null;
}
return getAccount(Doc.ACCTTYPE_CashTransfer, as);
} // getCashAcct
/**
* Create Tax Correction.
* Requirement: Adjust the tax amount, if you did not receive the full
* amount of the invoice (payment discount, write-off).
* Applies to many countries with VAT.
*
* Example:
* Invoice: Net $100 + Tax1 $15 + Tax2 $5 = Total $120
* Payment: $115 (i.e. $5 underpayment)
* Tax Adjustment = Tax1 = 0.63 (15/120*5) Tax2 = 0.21 (5/120/5)
*
* @param as accounting schema
* @param fact fact
* @param line Allocation line
* @param DiscountAccount discount acct
* @param WriteOffAccoint write off acct
* @return true if created
*/
private boolean createTaxCorrection (MAcctSchema as, Fact fact,
DocLine_Allocation line,
MAccount DiscountAccount, MAccount WriteOffAccoint, boolean isSOTrx)
{
if (log.isLoggable(Level.INFO)) log.info (line.toString());
BigDecimal discount = Env.ZERO;
if (as.isTaxCorrectionDiscount())
discount = line.getDiscountAmt();
BigDecimal writeOff = Env.ZERO;
if (as.isTaxCorrectionWriteOff())
writeOff = line.getWriteOffAmt();
Doc_AllocationTax tax = new Doc_AllocationTax (
DiscountAccount, discount, WriteOffAccoint, writeOff, isSOTrx);
// Get Source Amounts with account
String sql = "SELECT * "
+ "FROM Fact_Acct "
+ "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;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement(sql, getTrxName());
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)
{
throw new RuntimeException(e.getLocalizedMessage(), e);
}
finally {
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
// Invoice Not posted
if (tax.getLineCount() == 0)
{
log.warning ("Invoice not posted yet - " + line);
return false;
}
// size = 1 if no tax
if (tax.getLineCount() < 2)
return true;
return tax.createEntries (as, fact, line);
} // createTaxCorrection
/**
* Create Realized Gain & Loss.
* Compares the Accounted Amount of the Invoice to the
* Accounted Amount of the Allocation.
* @param line Allocation line
* @param as accounting schema
* @param fact fact
* @param acct account
* @param invoice invoice
* @param allocationSource source amt
* @param allocationAccounted acct amt
* @return Error Message or null if OK
*/
private String createInvoiceGainLoss (DocLine line, MAcctSchema as, Fact fact, MAccount acct,
MInvoice invoice, BigDecimal allocationSource, BigDecimal allocationAccounted)
{
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 valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MInvoice.Table_ID, invoice.getC_Invoice_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesInv != null && valuesInv.size() >= 4) {
if (invoice.getReversal_ID() == 0 || invoice.get_ID() < invoice.getReversal_ID())
{
if (hasDebitTradeAmt(invoice)) {
invoiceSource = (BigDecimal) valuesInv.get(0); // AmtSourceDr
invoiceAccounted = (BigDecimal) valuesInv.get(1); // AmtAcctDr
} else {
invoiceSource = (BigDecimal) valuesInv.get(2); // AmtSourceCr
invoiceAccounted = (BigDecimal) valuesInv.get(3); // AmtAcctCr
}
}
else
{
if (hasDebitTradeAmt(invoice)) {
invoiceSource = (BigDecimal) valuesInv.get(2); // AmtSourceCr
invoiceAccounted = (BigDecimal) valuesInv.get(3); // AmtAcctCr
} else {
invoiceSource = (BigDecimal) valuesInv.get(0); // AmtSourceDr
invoiceAccounted = (BigDecimal) valuesInv.get(1); // AmtAcctDr
}
}
}
// Requires that Invoice is Posted
if (invoiceSource == null || invoiceAccounted == null)
return "Gain/Loss - Invoice not posted yet";
//
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());
BigDecimal acctDifference = null; // gain is negative
// Full Payment in currency
if (allocationSource.abs().compareTo(invoiceSource.abs()) == 0)
{
acctDifference = invoiceAccounted.abs().subtract(allocationAccounted.abs()); // gain is negative
StringBuilder d2 = new StringBuilder("(full) = ").append(acctDifference);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
}
else // partial or MC
{
BigDecimal allocationAccounted0 = MConversionRate.convert(getCtx(),
allocationSource, getC_Currency_ID(),
as.getC_Currency_ID(), invoice.getDateAcct(),
invoice.getC_ConversionType_ID(), invoice.getAD_Client_ID(), invoice.getAD_Org_ID());
acctDifference = allocationAccounted0.abs().subtract(allocationAccounted.abs());
// ignore Tolerance
if (acctDifference.abs().compareTo(TOLERANCE) < 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);
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());
//
MAllocationHdr alloc = (MAllocationHdr) getPO();
if (alloc.getReversal_ID() == 0 || alloc.get_ID() < alloc.getReversal_ID())
{
if (hasDebitTradeAmt(invoice))
{
FactLine fl = fact.createLine (line, loss, gain, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
invGainLossFactLines.add(fl);
fl = fact.createLine (line, acct, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
}
else
{
FactLine fl = fact.createLine (line, acct, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
fl = fact.createLine (line, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
invGainLossFactLines.add(fl);
}
}
else
{
if (hasDebitTradeAmt(invoice))
{
FactLine fl = fact.createLine (line, acct, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
fl = fact.createLine (line, gain, loss, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
invGainLossFactLines.add(fl);
}
else
{
FactLine fl = fact.createLine (line, gain, loss, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
invGainLossFactLines.add(fl);
fl = fact.createLine (line, acct, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
}
}
return null;
}
/**
* Create Realized Gain & Loss.
* Compares the Accounted Amount of the Payment to the
* Accounted Amount of the Allocation.
* @param line Allocation line
* @param as accounting schema
* @param fact fact
* @param acct account
* @param payment payment
* @param allocationSource source amt
* @param allocationAccounted acct amt
* @return Error Message or null if OK
*/
private String createPaymentGainLoss (DocLine line, MAcctSchema as, Fact fact, MAccount acct,
MPayment payment, BigDecimal allocationSource, BigDecimal allocationAccounted)
{
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 valuesPay = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MPayment.Table_ID, payment.getC_Payment_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesPay != null && valuesPay.size() >= 4) {
paymentSource = (BigDecimal) valuesPay.get(0); // AmtSourceDr
paymentAccounted = (BigDecimal) valuesPay.get(1); // AmtAcctDr
if (paymentSource != null && paymentAccounted != null) {
if (paymentSource.signum() == 0 && paymentAccounted.signum() == 0) {
paymentSource = (BigDecimal) valuesPay.get(2); // AmtSourceCr
paymentAccounted = (BigDecimal) valuesPay.get(3); // AmtAcctCr
}
}
}
// 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(allocationAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
BigDecimal acctDifference = null; // gain is negative
// Full Payment in currency
if (allocationSource.abs().compareTo(paymentSource.abs()) == 0)
{
acctDifference = allocationAccounted.abs().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);
}
else
{
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());
acctDifference = allocationAccounted.abs().subtract(allocationAccounted0.abs());
// ignore Tolerance
if (acctDifference.abs().compareTo(TOLERANCE) < 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);
if (log.isLoggable(Level.FINE)) log.fine(d2.toString());
description.append(" - ").append(d2);
}
if (acctDifference == null || acctDifference.signum() == 0)
{
if (log.isLoggable(Level.FINE))
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() && payment.getPayAmt().signum() >= 0) || (!payment.isReceipt() && payment.getPayAmt().signum() < 0))
{
FactLine fl = fact.createLine (line, acct, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
fl = fact.createLine (line, loss, gain, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
payGainLossFactLines.add(fl);
}
else
{
FactLine fl = fact.createLine (line, acct, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
fl = fact.createLine (line, loss, gain, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
payGainLossFactLines.add(fl);
}
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 acct account
* @return Error Message or null if OK
*/
private String createInvoiceRoundingCorrection (MAcctSchema as, Fact fact, MAccount acctAr, MAccount acctAp)
{
Map invList = new HashMap<>();
Map htInvAllocLine = new HashMap<>();
for (int i = 0; i < p_lines.length; i++)
{
MInvoice invoice = null;
DocLine_Allocation line = (DocLine_Allocation)p_lines[i];
if (line.getC_Invoice_ID() == 0)
continue;
if (invList.containsKey(line.getC_Invoice_ID())){
log.severe(line.getC_Invoice_ID() + ":same invoice included in more than one allocation line");
}else {
invoice = new MInvoice (getCtx(), line.getC_Invoice_ID(), getTrxName());
invList.put(invoice.getC_Invoice_ID(), invoice);
htInvAllocLine.put(invoice.getC_Invoice_ID(), line.get_ID());
}
}
Map htInvSource = new HashMap<>();
Map htInvAccounted = new HashMap<>();
for (MInvoice invoice : invList.values())
{
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'");
MAccount acct = invoice.isSOTrx() ? acctAr : acctAp;
// For Invoice
List valuesInv = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MInvoice.Table_ID, invoice.getC_Invoice_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID());
if (valuesInv != null && valuesInv.size() >= 4) {
BigDecimal invoiceSource = null;
BigDecimal invoiceAccounted = null;
if (invoice.getReversal_ID() == 0 || invoice.get_ID() < invoice.getReversal_ID())
{
if (hasDebitTradeAmt(invoice)) {
invoiceSource = (BigDecimal) valuesInv.get(0); // AmtSourceDr
invoiceAccounted = (BigDecimal) valuesInv.get(1); // AmtAcctDr
} else {
invoiceSource = (BigDecimal) valuesInv.get(2); // AmtSourceCr
invoiceAccounted = (BigDecimal) valuesInv.get(3); // AmtAcctCr
}
}
else
{
if (hasDebitTradeAmt(invoice)) {
invoiceSource = (BigDecimal) valuesInv.get(2); // AmtSourceCr
invoiceAccounted = (BigDecimal) valuesInv.get(3); // AmtAcctCr
} else {
invoiceSource = (BigDecimal) valuesInv.get(0); // AmtSourceDr
invoiceAccounted = (BigDecimal) valuesInv.get(1); // AmtAcctDr
}
}
htInvSource.put(invoice.getC_Invoice_ID(), invoiceSource);
htInvAccounted.put(invoice.getC_Invoice_ID(), invoiceAccounted);
}
}
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
Map htTotalAmtSourceDr = new HashMap<>();
Map htTotalAmtAcctDr = new HashMap<>();
Map htTotalAmtSourceCr = new HashMap<>();
Map htTotalAmtAcctCr = new HashMap<>();
FactLine[] factlines = fact.getLines();
for (FactLine factLine : factlines)
{
if (factLine.getLine_ID() > 0)
{
MAllocationLine allocationLine = new MAllocationLine(getCtx(), factLine.getLine_ID(), getTrxName());
if (allocationLine.getC_Invoice_ID() > 0)
{
MInvoice invoice = invList.get(allocationLine.getC_Invoice_ID());
MAccount acct = invoice.isSOTrx() ? acctAr : acctAp;
if (factLine.getAccount_ID() == acct.getAccount_ID())
{
BigDecimal totalAmtSourceDr = htTotalAmtSourceDr.get(allocationLine.getC_Invoice_ID());
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtAcctDr = htTotalAmtAcctDr.get(allocationLine.getC_Invoice_ID());
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
BigDecimal totalAmtSourceCr = htTotalAmtSourceCr.get(allocationLine.getC_Invoice_ID());
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
BigDecimal totalAmtAcctCr = htTotalAmtAcctCr.get(allocationLine.getC_Invoice_ID());
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
totalAmtSourceDr = totalAmtSourceDr.add(factLine.getAmtSourceDr());
totalAmtAcctDr = totalAmtAcctDr.add(factLine.getAmtAcctDr());
totalAmtSourceCr = totalAmtSourceCr.add(factLine.getAmtSourceCr());
totalAmtAcctCr = totalAmtAcctCr.add(factLine.getAmtAcctCr());
htTotalAmtSourceDr.put(allocationLine.getC_Invoice_ID(), totalAmtSourceDr);
htTotalAmtAcctDr.put(allocationLine.getC_Invoice_ID(), totalAmtAcctDr);
htTotalAmtSourceCr.put(allocationLine.getC_Invoice_ID(), totalAmtSourceCr);
htTotalAmtAcctCr.put(allocationLine.getC_Invoice_ID(), totalAmtAcctCr);
}
else if (factLine.getAccount_ID() == gain.getAccount_ID() || factLine.getAccount_ID() == loss.getAccount_ID())
{
if (!invGainLossFactLines.contains(factLine))
continue;
BigDecimal totalAmtSourceDr = htTotalAmtSourceDr.get(allocationLine.getC_Invoice_ID());
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtSourceCr = htTotalAmtSourceCr.get(allocationLine.getC_Invoice_ID());
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtSourceDr = totalAmtSourceDr.subtract(factLine.getAmtSourceCr());
totalAmtSourceCr = totalAmtSourceCr.subtract(factLine.getAmtSourceDr());
htTotalAmtSourceDr.put(allocationLine.getC_Invoice_ID(), totalAmtSourceDr);
htTotalAmtSourceCr.put(allocationLine.getC_Invoice_ID(), totalAmtSourceCr);
}
}
}
}
Map htAllocInvSource = new HashMap<>();
Map htAllocInvAccounted = new HashMap<>();
for (MInvoice invoice : invList.values())
{
BigDecimal allocateSource = Env.ZERO;
BigDecimal allocateAccounted = Env.ZERO;
BigDecimal totalAmtSourceDr = htTotalAmtSourceDr.get(invoice.getC_Invoice_ID());
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtAcctDr = htTotalAmtAcctDr.get(invoice.getC_Invoice_ID());
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
BigDecimal totalAmtSourceCr = htTotalAmtSourceCr.get(invoice.getC_Invoice_ID());
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
BigDecimal totalAmtAcctCr = htTotalAmtAcctCr.get(invoice.getC_Invoice_ID());
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0)
{
allocateSource = allocateSource.add(totalAmtSourceCr);
allocateAccounted = allocateAccounted.add(totalAmtAcctCr);
}
else if (totalAmtSourceCr.signum() == 0 && totalAmtAcctCr.signum() == 0)
{
allocateSource = allocateSource.add(totalAmtSourceDr);
allocateAccounted = allocateAccounted.add(totalAmtAcctDr);
}
else
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
allocateSource = allocateSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr);
allocateAccounted = allocateAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr);
}
else
{
allocateSource = allocateSource.add(totalAmtSourceCr).subtract(totalAmtSourceDr);
allocateAccounted = allocateAccounted.add(totalAmtAcctCr).subtract(totalAmtAcctDr);
}
}
MAllocationHdr[] allocations = MAllocationHdr.getOfInvoice(getCtx(), invoice.get_ID(), getTrxName());
for (MAllocationHdr alloc : allocations)
{
if (alloc.get_ID() == get_ID())
continue;
BigDecimal currencyAdjustment = Env.ZERO;
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=?") // allocation
.append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'")
.append(" AND Account_ID=?")
.append(" AND Line_ID IN (SELECT C_AllocationLine_ID FROM C_AllocationLine WHERE C_AllocationHdr_ID=? AND C_Invoice_ID=?)");
MAccount acct = invoice.isSOTrx() ? acctAr : acctAp;
// For Allocation
List valuesAlloc = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(), acct.getAccount_ID(), alloc.get_ID(), invoice.getC_Invoice_ID());
if (valuesAlloc != null && valuesAlloc.size() >= 4) {
totalAmtSourceDr = (BigDecimal) valuesAlloc.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
totalAmtAcctDr = (BigDecimal) valuesAlloc.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
totalAmtSourceCr = (BigDecimal) valuesAlloc.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtAcctCr = (BigDecimal) valuesAlloc.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0)
{
allocateSource = allocateSource.add(totalAmtSourceCr);
allocateAccounted = allocateAccounted.add(totalAmtAcctCr);
}
else if (totalAmtSourceCr.signum() == 0 && totalAmtAcctCr.signum() == 0)
{
allocateSource = allocateSource.add(totalAmtSourceDr);
allocateAccounted = allocateAccounted.add(totalAmtAcctDr);
}
else
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
allocateSource = allocateSource.add(totalAmtSourceDr);
allocateAccounted = allocateAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr);
currencyAdjustment = currencyAdjustment.add(totalAmtAcctCr);
}
else
{
allocateSource = allocateSource.add(totalAmtSourceCr);
allocateAccounted = allocateAccounted.add(totalAmtAcctCr).subtract(totalAmtAcctDr);
currencyAdjustment = currencyAdjustment.add(totalAmtAcctDr);
}
}
}
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=?") // allocation
.append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'")
.append(" AND (Account_ID=? OR Account_ID=? OR Account_ID=?)")
.append(" AND Description LIKE 'Invoice%'")
.append(" AND Line_ID IN (SELECT C_AllocationLine_ID FROM C_AllocationLine WHERE C_AllocationHdr_ID=? AND C_Invoice_ID=?)");
// For Allocation
valuesAlloc = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(),
gain.getAccount_ID(), loss.getAccount_ID(), as.getCurrencyBalancing_Acct().getAccount_ID(),
alloc.get_ID(), invoice.getC_Invoice_ID());
if (valuesAlloc != null && valuesAlloc.size() >= 4) {
totalAmtSourceDr = (BigDecimal) valuesAlloc.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
totalAmtAcctDr = (BigDecimal) valuesAlloc.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
totalAmtSourceCr = (BigDecimal) valuesAlloc.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtAcctCr = (BigDecimal) valuesAlloc.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
allocateSource = allocateSource.subtract(totalAmtSourceDr).subtract(totalAmtSourceCr).add(currencyAdjustment);
}
}
htAllocInvSource.put(invoice.getC_Invoice_ID(), allocateSource);
htAllocInvAccounted.put(invoice.getC_Invoice_ID(), allocateAccounted);
}
for (MInvoice invoice : invList.values())
{
MAccount acct = invoice.isSOTrx() ? acctAr : acctAp;
BigDecimal invSource = htInvSource.get(invoice.getC_Invoice_ID());
if (invSource == null)
invSource = Env.ZERO;
BigDecimal invAccounted = htInvAccounted.get(invoice.getC_Invoice_ID());
if (invAccounted == null)
invAccounted = Env.ZERO;
BigDecimal allocInvSource = htAllocInvSource.get(invoice.getC_Invoice_ID());
if (allocInvSource == null)
allocInvSource = Env.ZERO;
BigDecimal allocInvAccounted = htAllocInvAccounted.get(invoice.getC_Invoice_ID());
if (allocInvAccounted == null)
allocInvAccounted = Env.ZERO;
StringBuilder description = new StringBuilder("Invoice=(").append(getC_Currency_ID()).append(")").append(invSource).append("/").append(invAccounted)
.append(" - Allocation=(").append(getC_Currency_ID()).append(")").append(allocInvSource).append("/").append(allocInvAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
BigDecimal acctDifference = null;
if (allocInvSource.abs().compareTo(invSource.abs()) == 0)
{
acctDifference = allocInvAccounted.abs().subtract(invAccounted.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");
continue;
}
//
Integer C_AllocationLine_ID = htInvAllocLine.get(invoice.getC_Invoice_ID());
MAllocationHdr alloc = (MAllocationHdr) getPO();
if (alloc.getReversal_ID() == 0 || alloc.get_ID() < alloc.getReversal_ID())
{
if (hasDebitTradeAmt(invoice))
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
if (!fact.isAcctBalanced())
{
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());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
}
}
else
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
if (!fact.isAcctBalanced())
{
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());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
}
}
}
else
{
if (hasDebitTradeAmt(invoice))
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
if (!fact.isAcctBalanced())
{
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, gain, loss, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
}
}
else
{
FactLine fl = fact.createLine (null, acct, as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
if (!fact.isAcctBalanced())
{
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, gain, loss, as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
}
}
}
}
return null;
} // createInvoiceRounding
/**
* Create Rounding Correction.
* Compares the Accounted Amount of the Payment to the
* Accounted Amount of the Allocation
* @param as accounting schema
* @param fact fact
* @return Error Message or null if OK
*/
private String createPaymentRoundingCorrection (MAcctSchema as, Fact fact)
{
List payList = new ArrayList();
Map htPayAllocLine = new HashMap<>();
for (int i = 0; i < p_lines.length; i++)
{
MPayment payment = null;
DocLine_Allocation line = (DocLine_Allocation) p_lines[i];
if (line.getC_Payment_ID() != 0)
{
payment = new MPayment (getCtx(), line.getC_Payment_ID(), getTrxName());
if (!payList.contains(payment))
payList.add(payment);
htPayAllocLine.put(payment.getC_Payment_ID(), line.get_ID());
}
}
Map htPayAcct = new HashMap<>();
Map htPaySource = new HashMap<>();
Map htPayAccounted = new HashMap<>();
for (MPayment payment : payList)
{
htPayAcct.put(payment.getC_Payment_ID(), getPaymentAcct(as, payment.getC_Payment_ID()));
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 valuesPay = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MPayment.Table_ID, payment.getC_Payment_ID(), as.getC_AcctSchema_ID(), htPayAcct.get(payment.getC_Payment_ID()).getAccount_ID());
if (valuesPay != null && valuesPay.size() >= 4) {
BigDecimal paymentSource = (BigDecimal) valuesPay.get(0); // AmtSourceDr
BigDecimal paymentAccounted = (BigDecimal) valuesPay.get(1); // AmtAcctDr
if (paymentSource != null && paymentAccounted != null) {
if (paymentSource.signum() == 0 && paymentAccounted.signum() == 0) {
paymentSource = (BigDecimal) valuesPay.get(2); // AmtSourceCr
paymentAccounted = (BigDecimal) valuesPay.get(3); // AmtAcctCr
}
htPaySource.put(payment.getC_Payment_ID(), paymentSource);
htPayAccounted.put(payment.getC_Payment_ID(), paymentAccounted);
}
}
}
MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct());
MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct());
Map htTotalAmtSourceDr = new HashMap<>();
Map htTotalAmtAcctDr = new HashMap<>();
Map htTotalAmtSourceCr = new HashMap<>();
Map htTotalAmtAcctCr = new HashMap<>();
FactLine[] factlines = fact.getLines();
for (FactLine factLine : factlines)
{
if (factLine.getLine_ID() > 0)
{
MAllocationLine allocationLine = new MAllocationLine(getCtx(), factLine.getLine_ID(), getTrxName());
if (allocationLine.getC_Payment_ID() > 0)
{
if (factLine.getAccount_ID() == htPayAcct.get(allocationLine.getC_Payment_ID()).getAccount_ID())
{
BigDecimal totalAmtSourceDr = htTotalAmtSourceDr.get(allocationLine.getC_Payment_ID());
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtAcctDr = htTotalAmtAcctDr.get(allocationLine.getC_Payment_ID());
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
BigDecimal totalAmtSourceCr = htTotalAmtSourceCr.get(allocationLine.getC_Payment_ID());
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
BigDecimal totalAmtAcctCr = htTotalAmtAcctCr.get(allocationLine.getC_Payment_ID());
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
totalAmtSourceDr = totalAmtSourceDr.add(factLine.getAmtSourceDr());
totalAmtAcctDr = totalAmtAcctDr.add(factLine.getAmtAcctDr());
totalAmtSourceCr = totalAmtSourceCr.add(factLine.getAmtSourceCr());
totalAmtAcctCr = totalAmtAcctCr.add(factLine.getAmtAcctCr());
htTotalAmtSourceDr.put(allocationLine.getC_Payment_ID(), totalAmtSourceDr);
htTotalAmtAcctDr.put(allocationLine.getC_Payment_ID(), totalAmtAcctDr);
htTotalAmtSourceCr.put(allocationLine.getC_Payment_ID(), totalAmtSourceCr);
htTotalAmtAcctCr.put(allocationLine.getC_Payment_ID(), totalAmtAcctCr);
}
else if (factLine.getAccount_ID() == gain.getAccount_ID() || factLine.getAccount_ID() == loss.getAccount_ID())
{
if (!payGainLossFactLines.contains(factLine))
continue;
BigDecimal totalAmtSourceDr = htTotalAmtSourceDr.get(allocationLine.getC_Payment_ID());
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtSourceCr = htTotalAmtSourceCr.get(allocationLine.getC_Payment_ID());
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtSourceDr = totalAmtSourceDr.subtract(factLine.getAmtSourceCr());
totalAmtSourceCr = totalAmtSourceCr.subtract(factLine.getAmtSourceDr());
htTotalAmtSourceDr.put(allocationLine.getC_Payment_ID(), totalAmtSourceDr);
htTotalAmtSourceCr.put(allocationLine.getC_Payment_ID(), totalAmtSourceCr);
}
}
}
}
Map htAllocPaySource = new HashMap<>();
Map htAllocPayAccounted = new HashMap<>();
for (MPayment payment : payList)
{
BigDecimal allocateSource = Env.ZERO;
BigDecimal allocateAccounted = Env.ZERO;
BigDecimal totalAmtSourceDr = htTotalAmtSourceDr.get(payment.getC_Payment_ID());
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
BigDecimal totalAmtAcctDr = htTotalAmtAcctDr.get(payment.getC_Payment_ID());
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
BigDecimal totalAmtSourceCr = htTotalAmtSourceCr.get(payment.getC_Payment_ID());
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
BigDecimal totalAmtAcctCr = htTotalAmtAcctCr.get(payment.getC_Payment_ID());
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0)
{
allocateSource = allocateSource.add(totalAmtSourceCr);
allocateAccounted = allocateAccounted.add(totalAmtAcctCr);
}
else if (totalAmtSourceCr.signum() == 0 && totalAmtAcctCr.signum() == 0)
{
allocateSource = allocateSource.add(totalAmtSourceDr);
allocateAccounted = allocateAccounted.add(totalAmtAcctDr);
}
else
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
allocateSource = allocateSource.add(totalAmtSourceDr).subtract(totalAmtSourceCr);
allocateAccounted = allocateAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr);
}
else
{
allocateSource = allocateSource.add(totalAmtSourceCr).subtract(totalAmtSourceDr);
allocateAccounted = allocateAccounted.add(totalAmtAcctCr).subtract(totalAmtAcctDr);
}
}
MAllocationHdr[] allocations = MAllocationHdr.getOfPayment(getCtx(), payment.get_ID(), getTrxName());
for (MAllocationHdr alloc : allocations)
{
if (alloc.get_ID() == get_ID())
continue;
BigDecimal currencyAdjustment = Env.ZERO;
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=?") // allocation
.append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'")
.append(" AND Account_ID=?")
.append(" AND Line_ID IN (SELECT C_AllocationLine_ID FROM C_AllocationLine WHERE C_AllocationHdr_ID=? AND C_Payment_ID=?)");
// For Allocation
List valuesAlloc = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(), htPayAcct.get(payment.getC_Payment_ID()).getAccount_ID(), alloc.get_ID(), payment.getC_Payment_ID());
if (valuesAlloc != null && valuesAlloc.size() >= 4) {
totalAmtSourceDr = (BigDecimal) valuesAlloc.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
totalAmtAcctDr = (BigDecimal) valuesAlloc.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
totalAmtSourceCr = (BigDecimal) valuesAlloc.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtAcctCr = (BigDecimal) valuesAlloc.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
if (totalAmtSourceDr.signum() == 0 && totalAmtAcctDr.signum() == 0)
{
allocateSource = allocateSource.add(totalAmtSourceCr);
allocateAccounted = allocateAccounted.add(totalAmtAcctCr);
}
else if (totalAmtSourceCr.signum() == 0 && totalAmtAcctCr.signum() == 0)
{
allocateSource = allocateSource.add(totalAmtSourceDr);
allocateAccounted = allocateAccounted.add(totalAmtAcctDr);
}
else
{
if (totalAmtAcctDr.compareTo(totalAmtAcctCr) > 0)
{
allocateSource = allocateSource.add(totalAmtSourceDr);
allocateAccounted = allocateAccounted.add(totalAmtAcctDr).subtract(totalAmtAcctCr);
currencyAdjustment = currencyAdjustment.add(totalAmtAcctCr);
}
else
{
allocateSource = allocateSource.add(totalAmtSourceCr);
allocateAccounted = allocateAccounted.add(totalAmtAcctCr).subtract(totalAmtAcctDr);
currencyAdjustment = currencyAdjustment.add(totalAmtAcctDr);
}
}
}
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=?") // allocation
.append(" AND C_AcctSchema_ID=?")
.append(" AND PostingType='A'")
.append(" AND (Account_ID=? OR Account_ID=? OR Account_ID=?)")
.append(" AND Description LIKE 'Payment%'")
.append(" AND Line_ID IN (SELECT C_AllocationLine_ID FROM C_AllocationLine WHERE C_AllocationHdr_ID=? AND C_Payment_ID=?)");
// For Allocation
valuesAlloc = DB.getSQLValueObjectsEx(getTrxName(), sql.toString(),
MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(),
gain.getAccount_ID(), loss.getAccount_ID(), as.getCurrencyBalancing_Acct().getAccount_ID(),
alloc.get_ID(), payment.getC_Payment_ID());
if (valuesAlloc != null && valuesAlloc.size() >= 4) {
totalAmtSourceDr = (BigDecimal) valuesAlloc.get(0);
if (totalAmtSourceDr == null)
totalAmtSourceDr = Env.ZERO;
totalAmtAcctDr = (BigDecimal) valuesAlloc.get(1);
if (totalAmtAcctDr == null)
totalAmtAcctDr = Env.ZERO;
totalAmtSourceCr = (BigDecimal) valuesAlloc.get(2);
if (totalAmtSourceCr == null)
totalAmtSourceCr = Env.ZERO;
totalAmtAcctCr = (BigDecimal) valuesAlloc.get(3);
if (totalAmtAcctCr == null)
totalAmtAcctCr = Env.ZERO;
allocateSource = allocateSource.subtract(totalAmtSourceDr).subtract(totalAmtSourceCr).add(currencyAdjustment);
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != payment.getC_Currency_ID())
;
else
allocateAccounted = allocateAccounted.add(currencyAdjustment);
}
}
htAllocPaySource.put(payment.getC_Payment_ID(), allocateSource);
htAllocPayAccounted.put(payment.getC_Payment_ID(), allocateAccounted);
}
for (MPayment payment : payList)
{
BigDecimal paySource = htPaySource.get(payment.getC_Payment_ID());
if (paySource == null)
paySource = Env.ZERO;
BigDecimal payAccounted = htPayAccounted.get(payment.getC_Payment_ID());
if (payAccounted == null)
payAccounted = Env.ZERO;
BigDecimal allocPaySource = htAllocPaySource.get(payment.getC_Payment_ID());
if (allocPaySource == null)
allocPaySource = Env.ZERO;
BigDecimal allocPayAccounted = htAllocPayAccounted.get(payment.getC_Payment_ID());
if (allocPayAccounted == null)
allocPayAccounted = Env.ZERO;
StringBuilder description = new StringBuilder("Payment=(").append(getC_Currency_ID()).append(")").append(paySource).append("/").append(payAccounted)
.append(" - Allocation=(").append(getC_Currency_ID()).append(")").append(allocPaySource).append("/").append(allocPayAccounted);
if (log.isLoggable(Level.FINE)) log.fine(description.toString());
BigDecimal acctDifference = null;
if (allocPaySource.abs().compareTo(paySource.abs()) == 0)
{
acctDifference = allocPayAccounted.abs().subtract(payAccounted.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");
continue;
}
//
Integer C_AllocationLine_ID = htPayAllocLine.get(payment.getC_Payment_ID());
if ((payment.isReceipt() && payment.getPayAmt().signum() >= 0) || (!payment.isReceipt() && payment.getPayAmt().signum() < 0))
{
FactLine fl = fact.createLine (null, htPayAcct.get(payment.getC_Payment_ID()), as.getC_Currency_ID(), acctDifference.negate());
fl.setDescription(description.toString());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
if (!fact.isAcctBalanced())
{
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != payment.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());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
}
}
else
{
FactLine fl = fact.createLine (null, htPayAcct.get(payment.getC_Payment_ID()), as.getC_Currency_ID(), acctDifference);
fl.setDescription(description.toString());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
if (!fact.isAcctBalanced())
{
if (as.isCurrencyBalancing() && as.getC_Currency_ID() != payment.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());
fl.setLine_ID(C_AllocationLine_ID == null ? 0 : C_AllocationLine_ID);
}
}
}
return null;
}
/**
* Balance Accounting
* @param as accounting schema
* @param fact
* @return fact line
*/
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;
}
/**
* Has Debit Receivables/Payables Trade Amount
* @param invoice
* @return true
*/
private boolean hasDebitTradeAmt(MInvoice invoice)
{
return (invoice.isSOTrx() && invoice.getGrandTotal().signum() >= 0 && !invoice.isCreditMemo())
|| (invoice.isSOTrx() && invoice.getGrandTotal().signum() < 0 && invoice.isCreditMemo())
|| (!invoice.isSOTrx() && invoice.getGrandTotal().signum() >= 0 && invoice.isCreditMemo())
|| (!invoice.isSOTrx() && invoice.getGrandTotal().signum() < 0 && !invoice.isCreditMemo());
}
} // Doc_Allocation
/**
* Allocation Document Tax Handing
*
* @author Jorg Janke
* @version $Id: Doc_Allocation.java,v 1.6 2006/07/30 00:53:33 jjanke Exp $
*/
class Doc_AllocationTax
{
/**
* Allocation Tax Adjustment
* @param DiscountAccount discount acct
* @param DiscountAmt discount amt
* @param WriteOffAccount write off acct
* @param WriteOffAmt write off amt
*/
public Doc_AllocationTax (MAccount DiscountAccount, BigDecimal DiscountAmt,
MAccount WriteOffAccount, BigDecimal WriteOffAmt, boolean isSOTrx)
{
m_DiscountAccount = DiscountAccount;
m_DiscountAmt = DiscountAmt;
m_WriteOffAccount = WriteOffAccount;
m_WriteOffAmt = WriteOffAmt;
m_IsSOTrx = isSOTrx;
} // Doc_AllocationTax
private static final CLogger log = CLogger.getCLogger(Doc_AllocationTax.class);
private MAccount m_DiscountAccount;
private BigDecimal m_DiscountAmt;
private MAccount m_WriteOffAccount;
private BigDecimal m_WriteOffAmt;
private boolean m_IsSOTrx;
private ArrayList m_facts = new ArrayList();
private int m_totalIndex = 0;
/**
* Add Invoice Fact Line
* @param fact fact line
*/
public void addInvoiceFact (MFactAcct fact)
{
m_facts.add(fact);
} // addInvoiceLine
/**
* Get Line Count
* @return number of lines
*/
public int getLineCount()
{
return m_facts.size();
} // getLineCount
/**
* Create Accounting Entries
* @param as account schema
* @param fact fact to add lines
* @param line line
* @return true if created
*/
public boolean createEntries (MAcctSchema as, Fact fact, DocLine line)
{
// get total index (the Receivables/Liabilities line)
BigDecimal total = Env.ZERO;
for (int i = 0; i < m_facts.size(); i++)
{
MFactAcct factAcct = (MFactAcct)m_facts.get(i);
if ( (factAcct.getAmtSourceDr().signum() > 0 && factAcct.getAmtSourceDr().compareTo(total) > 0)
|| (factAcct.getAmtSourceDr().signum() < 0 && factAcct.getAmtSourceDr().compareTo(total) < 0))
{
total = factAcct.getAmtSourceDr();
m_totalIndex = i;
}
if ( (factAcct.getAmtSourceCr().signum() > 0 && factAcct.getAmtSourceCr().compareTo(total) > 0)
|| (factAcct.getAmtSourceCr().signum() < 0 && factAcct.getAmtSourceCr().compareTo(total) < 0))
{
total = factAcct.getAmtSourceCr();
m_totalIndex = i;
}
}
MFactAcct factAcct = (MFactAcct)m_facts.get(m_totalIndex);
if (log.isLoggable(Level.INFO)) log.info ("Total Invoice = " + total + " - " + factAcct);
int precision = as.getStdPrecision();
for (int i = 0; i < m_facts.size(); i++)
{
// No Tax Line
if (i == m_totalIndex)
continue;
factAcct = (MFactAcct)m_facts.get(i);
if (log.isLoggable(Level.INFO)) log.info (i + ": " + factAcct);
// Create Tax Account
MAccount taxAcct = factAcct.getMAccount();
if (taxAcct == null || taxAcct.get_ID() == 0)
{
log.severe ("Tax Account not found/created");
return false;
}
Doc doc = DocManager.getDocument(as, MInvoice.Table_ID, factAcct.getRecord_ID(), line.getPO().get_TrxName());
MDocType dt = new MDocType(Env.getCtx(), (doc!=null)?doc.getC_DocType_ID():-1, line.getPO().get_TrxName());
String docBaseType=(dt.getC_DocType_ID()>0)?dt.getDocBaseType():"";
// Discount Amount
if (m_DiscountAmt.signum() != 0)
{
// Original Tax is DR - need to correct it CR
if (Env.ZERO.compareTo(factAcct.getAmtSourceDr()) != 0)
{
BigDecimal amount = calcAmount(factAcct.getAmtSourceDr(),
total, m_DiscountAmt, precision);
if (amount.signum() != 0)
{
//for sales actions
if (m_IsSOTrx) {
if(docBaseType.equals(MDocType.DOCBASETYPE_ARCreditMemo)) {
fact.createLine (line, m_DiscountAccount,
as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount.negate());
}else {
fact.createLine (line, m_DiscountAccount,
as.getC_Currency_ID(), amount, null);
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount);
}
} else {
//for purchase actions
if(docBaseType.equals(MDocType.DOCBASETYPE_APCreditMemo)) {
fact.createLine (line, m_DiscountAccount,
as.getC_Currency_ID(), amount, null);
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount);
} else {
fact.createLine (line, m_DiscountAccount,
as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount.negate());
}
}
}
}
// Original Tax is CR - need to correct it DR
else
{
BigDecimal amount = calcAmount(factAcct.getAmtSourceCr(),
total, m_DiscountAmt, precision);
if (amount.signum() != 0)
{
// for sales actions
if (m_IsSOTrx) {
if(docBaseType.equals(MDocType.DOCBASETYPE_ARCreditMemo)) {
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, m_DiscountAccount,
as.getC_Currency_ID(), null, amount.negate());
}else {
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount, null);
fact.createLine (line, m_DiscountAccount,
as.getC_Currency_ID(), null, amount);
}
} else {
if(docBaseType.equals(MDocType.DOCBASETYPE_APCreditMemo)) {
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount, null);
fact.createLine (line, m_DiscountAccount,
as.getC_Currency_ID(), null, amount);
}else {
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, m_DiscountAccount,
as.getC_Currency_ID(), null, amount.negate());
}
}
}
}
} // Discount
// WriteOff Amount
if (m_WriteOffAmt.signum() != 0)
{
// Original Tax is DR - need to correct it CR
if (Env.ZERO.compareTo(factAcct.getAmtSourceDr()) != 0)
{
BigDecimal amount = calcAmount(factAcct.getAmtSourceDr(),
total, m_WriteOffAmt, precision);
if (amount.signum() != 0)
{
if (m_IsSOTrx) {
if(docBaseType.equals(MDocType.DOCBASETYPE_ARCreditMemo)) {
fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount.negate());
} else {
fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), amount, null);
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount);
}
} else {
if(docBaseType.equals(MDocType.DOCBASETYPE_APCreditMemo)) {
fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), amount, null);
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount);
} else {
fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), null, amount.negate());
}
}
}
}
// Original Tax is CR - need to correct it DR
else
{
BigDecimal amount = calcAmount(factAcct.getAmtSourceCr(),
total, m_WriteOffAmt, precision);
if (amount.signum() != 0)
{
if(m_IsSOTrx) {
if(docBaseType.equals(MDocType.DOCBASETYPE_ARCreditMemo)) {
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), null, amount.negate());
} else {
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount, null);
fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), null, amount);
}
} else {
if(docBaseType.equals(MDocType.DOCBASETYPE_APCreditMemo)) {
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount, null);
fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), null, amount);
} else {
fact.createLine (line, taxAcct,
as.getC_Currency_ID(), amount.negate(), null);
fact.createLine (line, m_WriteOffAccount,
as.getC_Currency_ID(), null, amount.negate());
}
}
}
}
} // WriteOff
} // for all lines
return true;
} // createEntries
/**
* Calc Amount tax / total * amt
* @param tax tax
* @param total total
* @param amt reduction amt
* @param precision precision
* @return tax / total * amt
*/
private BigDecimal calcAmount (BigDecimal tax, BigDecimal total, BigDecimal amt, int precision)
{
if (log.isLoggable(Level.FINE)) log.fine("Amt=" + amt + " - Total=" + total + ", Tax=" + tax);
if (tax.signum() == 0
|| total.signum() == 0
|| amt.signum() == 0)
return Env.ZERO;
//
BigDecimal multiplier = tax.divide(total, 10, RoundingMode.HALF_UP);
BigDecimal retValue = multiplier.multiply(amt);
if (retValue.scale() > precision)
retValue = retValue.setScale(precision, RoundingMode.HALF_UP);
if (log.isLoggable(Level.FINE)) log.fine(retValue + " (Mult=" + multiplier + "(Prec=" + precision + ")");
return retValue;
} // calcAmount
} // Doc_AllocationTax