/******************************************************************************
* 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.model;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Savepoint;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import org.adempiere.base.Core;
import org.adempiere.exceptions.AdempiereException;
import org.adempiere.util.IReservationTracer;
import org.adempiere.util.IReservationTracerFactory;
import org.compiere.acct.Doc;
import org.compiere.process.DocAction;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Trx;
import org.compiere.util.Util;
import org.compiere.util.ValueNamePair;
/**
* Match PO Model.
*
* Created when processing Shipment or Order
* - Updates Order (delivered, invoiced)
* - Creates PPV acct
*
* @author Jorg Janke
* @version $Id: MMatchPO.java,v 1.3 2006/07/30 00:51:03 jjanke Exp $
*
* @author Bayu Cahya, Sistematika
* BF [ 2240484 ] Re MatchingPO, MMatchPO doesn't contains Invoice info
*
* @author Teo Sarca, www.arhipac.ro
* BF [ 2314749 ] MatchPO not considering currency PriceMatchDifference
*
* @author Armen Rizal, Goodwill Consulting
* BF [ 2215840 ] MatchPO Bug Collection
* BF [ 2858043 ] Correct Included Tax in Average Costing
*
* @author victor.perez@e-evolution.com, e-Evolution http://www.e-evolution.com
* FR [ 2520591 ] Support multiples calendar for Org
* @see https://sourceforge.net/p/adempiere/feature-requests/631/
*/
public class MMatchPO extends X_M_MatchPO
{
/**
* generated serial id
*/
private static final long serialVersionUID = 487498668807522050L;
/**
* Get PO Match with order/invoice
* @param ctx context
* @param C_OrderLine_ID order
* @param C_InvoiceLine_ID invoice
* @param trxName transaction
* @return array of matches
*/
public static MMatchPO[] get (Properties ctx,
int C_OrderLine_ID, int C_InvoiceLine_ID, String trxName)
{
if (C_OrderLine_ID == 0 || C_InvoiceLine_ID == 0)
return new MMatchPO[]{};
//
String sql = "SELECT * FROM M_MatchPO WHERE C_OrderLine_ID=? AND C_InvoiceLine_ID=?";
ArrayList list = new ArrayList();
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, trxName);
pstmt.setInt (1, C_OrderLine_ID);
pstmt.setInt (2, C_InvoiceLine_ID);
rs = pstmt.executeQuery ();
while (rs.next ())
list.add (new MMatchPO (ctx, rs, trxName));
}
catch (Exception e)
{
s_log.log(Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
MMatchPO[] retValue = new MMatchPO[list.size()];
list.toArray (retValue);
return retValue;
} // get
/**
* Get PO Match of Receipt Line
* @param ctx context
* @param M_InOutLine_ID receipt
* @param trxName transaction
* @return array of matches
*/
public static MMatchPO[] get (Properties ctx,
int M_InOutLine_ID, String trxName)
{
if (M_InOutLine_ID == 0)
return new MMatchPO[]{};
//
String sql = "SELECT * FROM M_MatchPO WHERE M_InOutLine_ID=?";
ArrayList list = new ArrayList();
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, trxName);
pstmt.setInt (1, M_InOutLine_ID);
rs = pstmt.executeQuery ();
while (rs.next ())
list.add (new MMatchPO (ctx, rs, trxName));
}
catch (Exception e)
{
s_log.log(Level.SEVERE, sql, e);
if (e instanceof RuntimeException)
{
throw (RuntimeException)e;
}
else
{
throw new IllegalStateException(e);
}
}
finally
{
DB.close(rs, pstmt);
}
MMatchPO[] retValue = new MMatchPO[list.size()];
list.toArray (retValue);
return retValue;
} // get
/**
* Get PO Matches of receipt
* @param ctx context
* @param M_InOut_ID receipt
* @param trxName transaction
* @return array of matches
*/
public static MMatchPO[] getInOut (Properties ctx,
int M_InOut_ID, String trxName)
{
if (M_InOut_ID == 0)
return new MMatchPO[]{};
//
String sql = "SELECT * FROM M_MatchPO m"
+ " INNER JOIN M_InOutLine l ON (m.M_InOutLine_ID=l.M_InOutLine_ID) "
+ "WHERE l.M_InOut_ID=?";
ArrayList list = new ArrayList();
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, trxName);
pstmt.setInt (1, M_InOut_ID);
rs = pstmt.executeQuery ();
while (rs.next ())
list.add (new MMatchPO (ctx, rs, trxName));
}
catch (Exception e)
{
s_log.log(Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
MMatchPO[] retValue = new MMatchPO[list.size()];
list.toArray (retValue);
return retValue;
} // getInOut
/**
* Get PO Matches of Invoice
* @param ctx context
* @param C_Invoice_ID invoice
* @param trxName transaction
* @return array of matches
*/
public static MMatchPO[] getInvoice (Properties ctx,
int C_Invoice_ID, String trxName)
{
if (C_Invoice_ID == 0)
return new MMatchPO[]{};
//
String sql = "SELECT * FROM M_MatchPO mi"
+ " INNER JOIN C_InvoiceLine il ON (mi.C_InvoiceLine_ID=il.C_InvoiceLine_ID) "
+ "WHERE il.C_Invoice_ID=?";
ArrayList list = new ArrayList();
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, trxName);
pstmt.setInt (1, C_Invoice_ID);
rs = pstmt.executeQuery ();
while (rs.next ())
list.add (new MMatchPO (ctx, rs, trxName));
}
catch (Exception e)
{
s_log.log(Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
MMatchPO[] retValue = new MMatchPO[list.size()];
list.toArray (retValue);
return retValue;
} // getInvoice
/**
* Get PO Matches for OrderLine
* @param ctx context
* @param C_OrderLine_ID order
* @param trxName transaction
* @return array of matches
*/
public static MMatchPO[] getOrderLine (Properties ctx, int C_OrderLine_ID, String trxName)
{
if (C_OrderLine_ID == 0)
return new MMatchPO[]{};
//
String sql = "SELECT * FROM M_MatchPO WHERE C_OrderLine_ID=?";
ArrayList list = new ArrayList();
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, trxName);
pstmt.setInt (1, C_OrderLine_ID);
rs = pstmt.executeQuery ();
while (rs.next ())
list.add (new MMatchPO (ctx, rs, trxName));
}
catch (Exception e)
{
s_log.log(Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
MMatchPO[] retValue = new MMatchPO[list.size()];
list.toArray (retValue);
return retValue;
} // getOrderLine
/**
* Update or Create Match PO record
* @param iLine invoice line
* @param sLine receipt line
* @param dateTrx transaction date
* @param qty qty to match
* @return Match PO Record
*/
public static MMatchPO create (MInvoiceLine iLine, MInOutLine sLine,
Timestamp dateTrx, BigDecimal qty)
{
String trxName = null;
Properties ctx = null;
int C_OrderLine_ID = 0;
if (iLine != null)
{
trxName = iLine.get_TrxName();
ctx = iLine.getCtx();
C_OrderLine_ID = iLine.getC_OrderLine_ID();
}
if (sLine != null)
{
trxName = sLine.get_TrxName();
ctx = sLine.getCtx();
C_OrderLine_ID = sLine.getC_OrderLine_ID();
}
if (C_OrderLine_ID > 0)
{
return create(ctx, iLine, sLine, C_OrderLine_ID, dateTrx, qty, trxName);
}
else
{
if (sLine != null && iLine != null)
{
MMatchPO[] matchpos = MMatchPO.get(ctx, sLine.getM_InOutLine_ID(), trxName);
for (MMatchPO matchpo : matchpos)
{
C_OrderLine_ID = matchpo.getC_OrderLine_ID();
MOrderLine orderLine = new MOrderLine(ctx, C_OrderLine_ID, trxName);
BigDecimal toInvoice = orderLine.getQtyOrdered().subtract(orderLine.getQtyInvoiced());
if (toInvoice.signum() <= 0)
continue;
BigDecimal matchQty = qty;
if (matchQty.compareTo(toInvoice) > 0)
matchQty = toInvoice;
if (matchQty.signum() <= 0)
continue;
MMatchPO newMatchPO = create(ctx, iLine, sLine, C_OrderLine_ID, dateTrx, matchQty, trxName);
if (!newMatchPO.save())
{
String msg = "Failed to update match po.";
ValueNamePair error = CLogger.retrieveError();
if (error != null)
{
msg = msg + " " + error.getName();
}
throw new RuntimeException(msg);
}
qty = qty.subtract(matchQty);
if (qty.signum() <= 0)
return newMatchPO;
}
}
return null;
}
}
/**
* Update or create MatchPO record (if needed, create MatchInv too).
* @param ctx
* @param iLine
* @param sLine
* @param C_OrderLine_ID
* @param dateTrx
* @param qty
* @param trxName
* @return Match PO record
*/
protected static MMatchPO create(Properties ctx, MInvoiceLine iLine,
MInOutLine sLine, int C_OrderLine_ID, Timestamp dateTrx,
BigDecimal qty, String trxName) {
MMatchPO retValue = null;
List matchPOList = MatchPOAutoMatch.getNotMatchedMatchPOList(ctx, C_OrderLine_ID, trxName);
if (!matchPOList.isEmpty())
{
for (MMatchPO mpo : matchPOList)
{
if (qty.compareTo(mpo.getQty()) >= 0)
{
BigDecimal toMatch = qty;
BigDecimal matchQty = mpo.getQty();
if (toMatch.compareTo(matchQty) > 0)
toMatch = matchQty;
if (iLine != null)
{
if ((mpo.getC_InvoiceLine_ID() == 0)
|| (mpo.getC_InvoiceLine_ID() == iLine.getC_InvoiceLine_ID()))
{
if (iLine.getM_AttributeSetInstance_ID() != 0)
{
if (mpo.getM_AttributeSetInstance_ID() == 0)
mpo.setM_AttributeSetInstance_ID(iLine.getM_AttributeSetInstance_ID());
else if (mpo.getM_AttributeSetInstance_ID() != iLine.getM_AttributeSetInstance_ID())
continue;
}
}
else
continue;
}
if (sLine != null)
{
if ((mpo.getM_InOutLine_ID() == 0)
|| (mpo.getM_InOutLine_ID() == sLine.getM_InOutLine_ID()))
{
if (sLine.getM_AttributeSetInstance_ID() != 0)
{
if (mpo.getM_AttributeSetInstance_ID() == 0)
mpo.setM_AttributeSetInstance_ID(sLine.getM_AttributeSetInstance_ID());
else if (mpo.getM_AttributeSetInstance_ID() != sLine.getM_AttributeSetInstance_ID())
continue;
}
}
else
continue;
if (iLine == null && mpo.isPosted())
continue;
}
if (iLine != null && sLine == null && mpo.getC_InvoiceLine_ID() == 0)
{
//verify m_matchinv not created for other invoice
int cnt = DB.getSQLValue(iLine.get_TrxName(), "SELECT Count(*) FROM M_MatchInv WHERE M_InOutLine_ID="+mpo.getM_InOutLine_ID()
+" AND C_InvoiceLine_ID != "+iLine.getC_InvoiceLine_ID() + " AND Reversal_ID=0");
if (cnt > 0)
continue;
}
if ((iLine != null || mpo.getC_InvoiceLine_ID() > 0) && (sLine != null || mpo.getM_InOutLine_ID() > 0))
{
int M_InOutLine_ID = sLine != null ? sLine.getM_InOutLine_ID() : mpo.getM_InOutLine_ID();
int C_InvoiceLine_ID = iLine != null ? iLine.getC_InvoiceLine_ID() : mpo.getC_InvoiceLine_ID();
//verify invoiceline not already linked to another inoutline
int tmpInOutLineId = DB.getSQLValue(mpo.get_TrxName(), "SELECT M_InOutLine_ID FROM C_InvoiceLine WHERE C_InvoiceLine_ID="+C_InvoiceLine_ID);
if (tmpInOutLineId > 0 && tmpInOutLineId != M_InOutLine_ID)
{
continue;
}
//verify m_matchinv not created yet
int cnt = DB.getSQLValue(mpo.get_TrxName(), "SELECT Count(*) FROM M_MatchInv WHERE M_InOutLine_ID="+M_InOutLine_ID
+" AND C_InvoiceLine_ID="+C_InvoiceLine_ID);
if (cnt <= 0)
{
MMatchInv matchInv = createMatchInv(mpo, C_InvoiceLine_ID, M_InOutLine_ID, mpo.getQty(), dateTrx, trxName);
if (matchInv == null)
continue;
mpo.setMatchInvCreated(matchInv);
}
}
if (iLine != null)
mpo.setC_InvoiceLine_ID(iLine);
if (sLine != null){
mpo.setM_InOutLine_ID(sLine.getM_InOutLine_ID());
if (!mpo.isPosted())
mpo.setDateAcct(sLine.getParent().getDateAcct());
}
if (!mpo.save())
{
String msg = "Failed to update match po.";
ValueNamePair error = CLogger.retrieveError();
if (error != null)
{
msg = msg + " " + error.getName();
}
throw new RuntimeException(msg);
}
qty = qty.subtract(toMatch);
if (qty.signum() <= 0)
{
retValue = mpo;
break;
}
}
}
}
// Create New
if (retValue == null)
{
BigDecimal sLineMatchedQty = null;
if (sLine != null && iLine != null)
{
sLineMatchedQty = DB.getSQLValueBD(sLine.get_TrxName(), "SELECT Sum(Qty) FROM M_MatchPO WHERE C_OrderLine_ID="+C_OrderLine_ID+" AND M_InOutLine_ID=?", sLine.getM_InOutLine_ID());
}
if (sLine != null && (sLine.getC_OrderLine_ID() == C_OrderLine_ID || iLine == null)
&& (sLineMatchedQty == null || sLineMatchedQty.signum() <= 0))
{
if (qty.signum() != 0)
{
retValue = new MMatchPO (sLine, dateTrx, qty);
retValue.setC_OrderLine_ID(C_OrderLine_ID);
MMatchPO otherMatchPO = null;
if (iLine == null) {
MMatchPO[] matchPOs = MMatchPO.getOrderLine(retValue.getCtx(), sLine.getC_OrderLine_ID(), retValue.get_TrxName());
for (MMatchPO matchPO : matchPOs)
{
if (matchPO.getC_InvoiceLine_ID() > 0 && matchPO.getM_InOutLine_ID() == 0 && matchPO.getReversal_ID() == 0 && matchPO.getQty().compareTo(retValue.getQty()) >=0 )
{
//check m_matchinv not created with different qty
int cnt = DB.getSQLValueEx(sLine.get_TrxName(), "SELECT Count(*) FROM M_MatchInv WHERE M_InOutLine_ID="+sLine.getM_InOutLine_ID()
+" AND C_InvoiceLine_ID="+ matchPO.getC_InvoiceLine_ID() + " AND Qty != ?", retValue.getQty());
if (cnt <= 0) {
if (!matchPO.isPosted() && matchPO.getQty().compareTo(retValue.getQty()) >=0 ) // greater than or equal quantity
{
otherMatchPO = matchPO;
iLine = new MInvoiceLine(retValue.getCtx(), matchPO.getC_InvoiceLine_ID(), retValue.get_TrxName());
matchPO.setQty(matchPO.getQty().subtract(retValue.getQty()));
matchPO.saveEx();
break;
}
}
}
}
}
if (iLine != null) {
if (otherMatchPO == null)
retValue.setC_InvoiceLine_ID(iLine);
//auto create matchinv
if (otherMatchPO != null)
{
//verify m_matchinv not created yet
int cnt = DB.getSQLValue(retValue.get_TrxName(), "SELECT Count(*) FROM M_MatchInv WHERE M_InOutLine_ID="+retValue.getM_InOutLine_ID()
+" AND C_InvoiceLine_ID="+otherMatchPO.getC_InvoiceLine_ID());
if (cnt <= 0)
{
MMatchInv matchInv = createMatchInv(retValue, otherMatchPO.getC_InvoiceLine_ID(), retValue.getM_InOutLine_ID(), retValue.getQty(), dateTrx, trxName);
if (matchInv == null)
{
String msg = "Failed to create match inv.";
ValueNamePair error = CLogger.retrieveError();
if (error != null)
{
msg = msg + " " + error.getName();
}
throw new RuntimeException(msg);
}
retValue.setMatchInvCreated(matchInv);
}
if (otherMatchPO.getQty().signum() == 0 )
otherMatchPO.deleteEx(true);
}
}
if (!retValue.save())
{
String msg = "Failed to update match po.";
ValueNamePair error = CLogger.retrieveError();
if (error != null)
{
msg = msg + " " + error.getName();
}
throw new RuntimeException(msg);
}
}
}
else if (iLine != null)
{
if (qty.signum() != 0)
{
retValue = new MMatchPO (iLine, dateTrx, qty);
retValue.setC_OrderLine_ID(C_OrderLine_ID);
if (!retValue.save())
{
String msg = "Failed to update match po.";
ValueNamePair error = CLogger.retrieveError();
if (error != null)
{
msg = msg + " " + error.getName();
}
throw new RuntimeException(msg);
}
//auto create m_matchinv
Map noInvoiceLines = new HashMap<>();
Map> invoiceMatched = new HashMap>();
List noInvoiceList = new ArrayList();
//get all matchpo with inoutline for C_OrderLine_ID
MMatchPO[] matchPOs = MMatchPO.getOrderLine(iLine.getCtx(), C_OrderLine_ID, iLine.get_TrxName());
for (MMatchPO matchPO : matchPOs)
{
if (matchPO.getM_MatchPO_ID() == retValue.getM_MatchPO_ID())
continue;
if (matchPO.getM_InOutLine_ID() > 0 && matchPO.getReversal_ID() == 0 && matchPO.getRef_MatchPO_ID() == 0)
{
if (matchPO.getC_InvoiceLine_ID() == 0)
{
String docStatus = matchPO.getM_InOutLine().getM_InOut().getDocStatus();
if (docStatus.equals(DocAction.STATUS_Completed) || docStatus.equals(DocAction.STATUS_Closed))
{
noInvoiceLines.put(matchPO.getM_MatchPO_ID(), new BigDecimal[]{matchPO.getQty()});
noInvoiceList.add(matchPO);
}
}
else
{
List invoices = invoiceMatched.get(matchPO.getM_InOutLine_ID());
if (invoices == null)
{
invoices = new ArrayList();
invoiceMatched.put(matchPO.getM_InOutLine_ID(), invoices);
}
invoices.add(matchPO);
}
}
}
//sort in created sequence
Collections.sort(noInvoiceList, new Comparator() {
@Override
public int compare(MMatchPO arg0, MMatchPO arg1) {
return arg0.getM_MatchPO_ID() > arg1.getM_MatchPO_ID()
? 1
: (arg0.getM_MatchPO_ID()==arg1.getM_MatchPO_ID() ? 0 : -1);
}
});
//goes through all matchpo that potentially have not been matched to any invoice yet
//calculate balance that have not been matched to invoice line
for (MMatchPO matchPO : noInvoiceList)
{
BigDecimal[] qtyHolder = noInvoiceLines.get(matchPO.getM_MatchPO_ID());
List matchedInvoices = invoiceMatched.get(matchPO.getM_InOutLine_ID());
MMatchInv[] matchInvoices = MMatchInv.getInOutLine(iLine.getCtx(), matchPO.getM_InOutLine_ID(), iLine.get_TrxName());
for (MMatchInv matchInv : matchInvoices)
{
if (matchInv.getReversal_ID() > 0)
continue;
BigDecimal alreadyMatch = BigDecimal.ZERO;
if (matchedInvoices != null)
{
for(MMatchPO matchedInvoice : matchedInvoices)
{
if (matchedInvoice.getC_InvoiceLine_ID()==matchInv.getC_InvoiceLine_ID())
alreadyMatch = alreadyMatch.add(matchedInvoice.getQty());
}
}
BigDecimal balance = matchInv.getQty().subtract(alreadyMatch);
if (balance.signum() > 0)
{
String docStatus = matchInv.getC_InvoiceLine().getC_Invoice().getDocStatus();
if (docStatus.equals(DocAction.STATUS_Completed) || docStatus.equals(DocAction.STATUS_Closed))
{
qtyHolder[0] = qtyHolder[0].subtract(balance);
}
}
}
}
//do matching
BigDecimal toMatch = retValue.getQty();
for (MMatchPO matchPO : noInvoiceList)
{
BigDecimal[] qtyHolder = noInvoiceLines.get(matchPO.getM_MatchPO_ID());
if (qtyHolder[0].signum() > 0)
{
BigDecimal autoMatchQty = null;
if (qtyHolder[0].compareTo(toMatch) >= 0)
{
autoMatchQty = toMatch;
toMatch = BigDecimal.ZERO;
}
else
{
autoMatchQty = qtyHolder[0];
toMatch = toMatch.subtract(autoMatchQty);
}
if (autoMatchQty != null && autoMatchQty.signum() > 0)
{
MMatchInv[] matchInvoices = MMatchInv.get(Env.getCtx(), matchPO.getM_InOutLine_ID(), retValue.getC_InvoiceLine_ID(), trxName);
if (matchInvoices == null || matchInvoices.length == 0)
{
MMatchInv matchInv = createMatchInv(retValue, retValue.getC_InvoiceLine_ID(), matchPO.getM_InOutLine_ID(), autoMatchQty, dateTrx, trxName);
retValue.setMatchInvCreated(matchInv);
if (matchInv == null)
break;
}
}
}
if (toMatch.signum() <= 0)
break;
}
}
}
}
if (C_OrderLine_ID > 0 && retValue != null)
MatchPOAutoMatch.match(ctx, C_OrderLine_ID, retValue, trxName);
return retValue;
} // create
/**
* Create MatchInv record
* @param mpo
* @param C_InvoiceLine_ID
* @param M_InOutLine_ID
* @param qty
* @param dateTrx
* @param trxName
* @return Match Inv record
*/
protected static MMatchInv createMatchInv(MMatchPO mpo, int C_InvoiceLine_ID, int M_InOutLine_ID, BigDecimal qty, Timestamp dateTrx, String trxName)
{
Savepoint savepoint = null;
Trx trx = null;
MMatchInv matchInv = null;
try
{
trx = trxName != null ? Trx.get(trxName, false) : null;
savepoint = trx != null ? trx.getConnection().setSavepoint() : null;
matchInv = new MMatchInv(mpo.getCtx(), 0, mpo.get_TrxName());
matchInv.setC_InvoiceLine_ID(C_InvoiceLine_ID);
matchInv.setM_Product_ID(mpo.getM_Product_ID());
matchInv.setM_InOutLine_ID(M_InOutLine_ID);
matchInv.setAD_Client_ID(mpo.getAD_Client_ID());
matchInv.setAD_Org_ID(mpo.getAD_Org_ID());
matchInv.setM_AttributeSetInstance_ID(mpo.getM_AttributeSetInstance_ID());
matchInv.setQty(qty);
matchInv.setDateTrx(dateTrx);
matchInv.setProcessed(true);
if (!matchInv.save())
{
if (savepoint != null)
{
trx.getConnection().rollback(savepoint);
savepoint = null;
}
else
{
matchInv.delete(true);
}
String msg = "Failed to auto match invoice.";
ValueNamePair error = CLogger.retrieveError();
if (error != null)
{
msg = msg + " " + error.getName();
}
s_log.severe(msg);
matchInv = null;
}
} catch (Exception e) {
s_log.log(Level.SEVERE, "Failed to auto match Invoice.", e);
matchInv = null;
} finally {
if (savepoint != null)
{
try {
trx.getConnection().releaseSavepoint(savepoint);
} catch (Exception e) {}
}
}
return matchInv;
}
protected MMatchInv m_matchInv;
/**
* Register the match inv created for immediate accounting posting
* @param matchInv
*/
protected void setMatchInvCreated(MMatchInv matchInv) {
m_matchInv = matchInv;
}
/**
* Get the match inv created for immediate accounting posting.
* The Match Inv record reference is set to null after call, so if you call this method twice, the second call will returns null.
* @return Match Inv record or null
*/
public MMatchInv getMatchInvCreated() {
MMatchInv tmp = m_matchInv;
m_matchInv = null;
return tmp;
}
/** Static Logger */
private static CLogger s_log = CLogger.getCLogger (MMatchPO.class);
/**
* UUID based Constructor
* @param ctx Context
* @param M_MatchPO_UU UUID key
* @param trxName Transaction
*/
public MMatchPO(Properties ctx, String M_MatchPO_UU, String trxName) {
super(ctx, M_MatchPO_UU, trxName);
if (Util.isEmpty(M_MatchPO_UU))
setInitialDefaults();
}
/**
* Standard Constructor
* @param ctx context
* @param M_MatchPO_ID id
* @param trxName transaction
*/
public MMatchPO (Properties ctx, int M_MatchPO_ID, String trxName)
{
super (ctx, M_MatchPO_ID, trxName);
if (M_MatchPO_ID == 0)
setInitialDefaults();
} // MMatchPO
/**
* Set the initial defaults for a new record
*/
private void setInitialDefaults() {
setM_AttributeSetInstance_ID(0);
setPosted (false);
setProcessed (false);
setProcessing (false);
}
/**
* Load Construor
* @param ctx context
* @param rs result set
* @param trxName transaction
*/
public MMatchPO (Properties ctx, ResultSet rs, String trxName)
{
super(ctx, rs, trxName);
} // MMatchPO
/**
* Shipment Line Constructor
* @param sLine shipment line
* @param dateTrx optional date
* @param qty matched quantity
*/
public MMatchPO (MInOutLine sLine, Timestamp dateTrx, BigDecimal qty)
{
this (sLine.getCtx(), 0, sLine.get_TrxName());
setClientOrg(sLine);
setM_InOutLine_ID (sLine.getM_InOutLine_ID());
setC_OrderLine_ID (sLine.getC_OrderLine_ID());
if (dateTrx != null)
setDateTrx (dateTrx);
setM_Product_ID (sLine.getM_Product_ID());
setM_AttributeSetInstance_ID(sLine.getM_AttributeSetInstance_ID());
setQty (qty);
setProcessed(true); // auto
} // MMatchPO
/**
* Invoice Line Constructor
* @param iLine invoice line
* @param dateTrx optional date
* @param qty matched quantity
*/
public MMatchPO (MInvoiceLine iLine, Timestamp dateTrx, BigDecimal qty)
{
this (iLine.getCtx(), 0, iLine.get_TrxName());
setClientOrg(iLine);
setC_InvoiceLine_ID(iLine);
if (iLine.getC_OrderLine_ID() != 0)
setC_OrderLine_ID (iLine.getC_OrderLine_ID());
if (dateTrx != null)
setDateTrx (dateTrx);
setM_Product_ID (iLine.getM_Product_ID());
setM_AttributeSetInstance_ID(iLine.getM_AttributeSetInstance_ID());
setQty (qty);
setProcessed(true); // auto
} // MMatchPO
/** Invoice Line Changed */
protected boolean m_isInvoiceLineChange = false;
/** InOut Line Changed */
protected boolean m_isInOutLineChange = false;
/** Order Line */
protected MOrderLine m_oLine = null;
/** Invoice Line */
protected MInvoiceLine m_iLine = null;
/**
* Set C_InvoiceLine_ID
* @param line line
*/
public void setC_InvoiceLine_ID (MInvoiceLine line)
{
m_iLine = line;
if (line == null)
setC_InvoiceLine_ID(0);
else
setC_InvoiceLine_ID(line.getC_InvoiceLine_ID());
} // setC_InvoiceLine_ID
/**
* Set C_InvoiceLine_ID
* @param C_InvoiceLine_ID id
*/
public void setC_InvoiceLine_ID (int C_InvoiceLine_ID)
{
int old = getC_InvoiceLine_ID();
if (old != C_InvoiceLine_ID)
{
super.setC_InvoiceLine_ID (C_InvoiceLine_ID);
m_isInvoiceLineChange = true;
}
} // setC_InvoiceLine_ID
/**
* Get Invoice Line
* @return invoice line or null
*/
public MInvoiceLine getInvoiceLine()
{
if (m_iLine == null && getC_InvoiceLine_ID() != 0)
m_iLine = new MInvoiceLine(getCtx(), getC_InvoiceLine_ID(), get_TrxName());
return m_iLine;
} // getInvoiceLine
/**
* Set M_InOutLine_ID
* @param M_InOutLine_ID id
*/
public void setM_InOutLine_ID (int M_InOutLine_ID)
{
int old = getM_InOutLine_ID();
if (old != M_InOutLine_ID)
{
super.setM_InOutLine_ID (M_InOutLine_ID);
m_isInOutLineChange = true;
}
} // setM_InOutLine_ID
/**
* Set C_OrderLine_ID
* @param line line
*/
public void setC_OrderLine_ID (MOrderLine line)
{
m_oLine = line;
if (line == null)
setC_OrderLine_ID(0);
else
setC_OrderLine_ID(line.getC_OrderLine_ID());
} // setC_InvoiceLine_ID
/**
* Get Order Line
* @return order line or null
*/
public MOrderLine getOrderLine()
{
if ((m_oLine == null && getC_OrderLine_ID() != 0)
|| getC_OrderLine_ID() != m_oLine.getC_OrderLine_ID())
m_oLine = new MOrderLine(getCtx(), getC_OrderLine_ID(), get_TrxName());
return m_oLine;
} // getOrderLine
/**
* Get PriceActual from Invoice and convert it to Order Currency.
* @return Price Actual in Order Currency
*/
public BigDecimal getInvoicePriceActual()
{
MInvoiceLine iLine = getInvoiceLine();
MInvoice invoice = iLine.getParent();
MOrder order = getOrderLine().getParent();
BigDecimal priceActual = iLine.getPriceActual();
int invoiceCurrency_ID = invoice.getC_Currency_ID();
int orderCurrency_ID = order.getC_Currency_ID();
if (invoiceCurrency_ID != orderCurrency_ID)
{
priceActual = MConversionRate.convert(getCtx(), priceActual, invoiceCurrency_ID, orderCurrency_ID,
invoice.getDateInvoiced(), invoice.getC_ConversionType_ID(),
getAD_Client_ID(), getAD_Org_ID());
if (priceActual == null)
throw new AdempiereException(MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingCurrencyToBaseCurrency",
invoiceCurrency_ID, orderCurrency_ID, invoice.getC_ConversionType_ID(), invoice.getDateInvoiced(), get_TrxName()));
}
return priceActual;
}
@Override
protected boolean beforeSave (boolean newRecord)
{
// Set Trx Date
if (getDateTrx() == null)
setDateTrx (new Timestamp(System.currentTimeMillis()));
// Set Acct Date
if (getDateAcct() == null)
{
Timestamp ts = getNewerDateAcct();
if (ts == null)
ts = getDateTrx();
setDateAcct (ts);
}
// Set ASI from Receipt
if (getM_AttributeSetInstance_ID() == 0 && getM_InOutLine_ID() != 0)
{
MInOutLine iol = new MInOutLine (getCtx(), getM_InOutLine_ID(), get_TrxName());
setM_AttributeSetInstance_ID(iol.getM_AttributeSetInstance_ID());
}
// BF [ 2240484 ] Re MatchingPO, MMatchPO doesn't contains Invoice info
// If newRecord, set c_invoiceline_id while null
if (newRecord && getC_InvoiceLine_ID() == 0 && getReversal_ID()==0)
{
MMatchInv[] mpi = MMatchInv.getInOutLine(getCtx(), getM_InOutLine_ID(), get_TrxName());
for (int i = 0; i < mpi.length; i++)
{
if (mpi[i].getC_InvoiceLine_ID() != 0 &&
mpi[i].getM_AttributeSetInstance_ID() == getM_AttributeSetInstance_ID())
{
//verify m_matchpo not created yet
int cnt = DB.getSQLValue(get_TrxName(), "SELECT Count(*) FROM M_MatchPO WHERE M_InOutLine_ID="+getM_InOutLine_ID()
+" AND C_InvoiceLine_ID="+mpi[i].getC_InvoiceLine_ID());
if (cnt > 0)
continue;
if (mpi[i].getQty().compareTo(getQty()) == 0) // same quantity
{
setC_InvoiceLine_ID(mpi[i].getC_InvoiceLine_ID());
break;
}
else // create MatchPO record for PO-Invoice if different quantity
{
MInvoiceLine il = new MInvoiceLine(getCtx(), mpi[i].getC_InvoiceLine_ID(), get_TrxName());
MMatchPO match = new MMatchPO(il, getDateTrx(), mpi[i].getQty());
match.setC_OrderLine_ID(getC_OrderLine_ID());
if (!match.save())
{
String msg = "Failed to create match po";
ValueNamePair error = CLogger.retrieveError();
if (error != null)
msg = msg + " " + error.getName();
throw new RuntimeException(msg);
}
}
}
}
}
// Find OrderLine
if (getC_OrderLine_ID() == 0)
{
MInvoiceLine il = null;
if (getC_InvoiceLine_ID() != 0)
{
il = getInvoiceLine();
if (il.getC_OrderLine_ID() != 0)
setC_OrderLine_ID(il.getC_OrderLine_ID());
} // get from invoice
if (getC_OrderLine_ID() == 0 && getM_InOutLine_ID() != 0)
{
MInOutLine iol = new MInOutLine (getCtx(), getM_InOutLine_ID(), get_TrxName());
if (iol.getC_OrderLine_ID() != 0)
{
setC_OrderLine_ID(iol.getC_OrderLine_ID());
if (il != null)
{
il.setC_OrderLine_ID(iol.getC_OrderLine_ID());
il.saveEx();
}
}
} // get from shipment
} // find order line
// Price Match Approval
if (getC_OrderLine_ID() != 0
&& getC_InvoiceLine_ID() != 0
&& (newRecord ||
is_ValueChanged("C_OrderLine_ID") || is_ValueChanged("C_InvoiceLine_ID")))
{
BigDecimal poPrice = getOrderLine().getPriceActual();
BigDecimal invPrice = getInvoicePriceActual();
BigDecimal difference = poPrice.subtract(invPrice);
if (difference.signum() != 0)
{
difference = difference.multiply(getQty());
setPriceMatchDifference(difference);
// Approval
MBPGroup group = MBPGroup.getOfBPartner(getCtx(), getOrderLine().getC_BPartner_ID());
BigDecimal mt = group.getPriceMatchTolerance();
if (mt != null && mt.signum() != 0)
{
BigDecimal poAmt = poPrice.multiply(getQty());
BigDecimal maxTolerance = poAmt.multiply(mt);
maxTolerance = maxTolerance.abs()
.divide(Env.ONEHUNDRED, 2, RoundingMode.HALF_UP);
difference = difference.abs();
boolean ok = difference.compareTo(maxTolerance) <= 0;
if (log.isLoggable(Level.CONFIG)) log.config("Difference=" + getPriceMatchDifference()
+ ", Max=" + maxTolerance + " => " + ok);
setIsApproved(ok);
}
}
else
{
setPriceMatchDifference(difference);
setIsApproved(true);
}
//validate against M_MatchInv
if (getM_InOutLine_ID() > 0 && getC_InvoiceLine_ID() > 0)
{
int cnt = DB.getSQLValue(get_TrxName(), "SELECT Count(*) FROM M_MatchInv WHERE M_InOutLine_ID="+getM_InOutLine_ID()
+" AND C_InvoiceLine_ID="+getC_InvoiceLine_ID());
if (cnt <= 0)
{
MInvoiceLine invoiceLine = new MInvoiceLine(getCtx(), getC_InvoiceLine_ID(), get_TrxName());
MInOutLine inoutLine = new MInOutLine(getCtx(), getM_InOutLine_ID(), get_TrxName());
throw new IllegalStateException("[MatchPO] Missing corresponding invoice matching record for invoice line "
+ invoiceLine + " and receipt line " + inoutLine);
}
}
}
return true;
} // beforeSave
/**
* After Save.
* Set Order Qty Delivered/Invoiced
* @param newRecord new
* @param success success
* @return success
*/
@Override
protected boolean afterSave (boolean newRecord, boolean success)
{
//perform matched qty validation
if (success)
{
if (getM_InOutLine_ID() > 0)
{
MInOutLine line = new MInOutLine(getCtx(), getM_InOutLine_ID(), get_TrxName());
BigDecimal matchedQty = DB.getSQLValueBD(get_TrxName(), "SELECT Coalesce(SUM(Qty),0) FROM M_MatchPO WHERE M_InOutLine_ID=?" , getM_InOutLine_ID());
if (line.getMovementQty().signum() > 0 && matchedQty != null && matchedQty.compareTo(line.getMovementQty()) > 0)
{
throw new IllegalStateException("Total matched qty > movement qty. MatchedQty="+matchedQty+", MovementQty="+line.getMovementQty()+", Line="+line);
}
}
if (getC_InvoiceLine_ID() > 0)
{
MInvoiceLine line = new MInvoiceLine(getCtx(), getC_InvoiceLine_ID(), get_TrxName());
BigDecimal matchedQty = DB.getSQLValueBD(get_TrxName(), "SELECT Coalesce(SUM(Qty),0) FROM M_MatchPO WHERE C_InvoiceLine_ID=? AND Reversal_ID IS NULL " , getC_InvoiceLine_ID() );
if (matchedQty != null && matchedQty.compareTo(line.getQtyInvoiced()) > 0)
{
throw new IllegalStateException("Total matched qty > invoiced qty. MatchedQty="+matchedQty+", InvoicedQty="+line.getQtyInvoiced()+", Line="+line);
}
}
if (getC_OrderLine_ID() > 0)
{
boolean validateOrderedQty = MSysConfig.getBooleanValue(MSysConfig.VALIDATE_MATCHING_TO_ORDERED_QTY, true, Env.getAD_Client_ID(Env.getCtx()));
if (validateOrderedQty)
{
MOrderLine line = new MOrderLine(getCtx(), getC_OrderLine_ID(), get_TrxName());
BigDecimal qtyOrdered = line.getQtyOrdered();
BigDecimal invoicedQty = DB.getSQLValueBD(get_TrxName(), "SELECT Coalesce(SUM(Qty),0) FROM M_MatchPO WHERE C_InvoiceLine_ID > 0 and C_OrderLine_ID=? AND Reversal_ID IS NULL" , getC_OrderLine_ID());
if ( invoicedQty != null
&& ( (qtyOrdered.signum() > 0 && invoicedQty.compareTo(qtyOrdered) > 0)
|| (qtyOrdered.signum() < 0 && invoicedQty.compareTo(qtyOrdered) < 0)
)
)
{
throw new IllegalStateException("Total matched invoiced qty > ordered qty. MatchedInvoicedQty="+invoicedQty+", OrderedQty="+qtyOrdered+", Line="+line);
}
BigDecimal deliveredQty = DB.getSQLValueBD(get_TrxName(), "SELECT Coalesce(SUM(Qty),0) FROM M_MatchPO WHERE M_InOutLine_ID > 0 and C_OrderLine_ID=? AND Reversal_ID IS NULL" , getC_OrderLine_ID());
if ( deliveredQty != null
&& ( (qtyOrdered.signum() > 0 && deliveredQty.compareTo(qtyOrdered) > 0)
|| (qtyOrdered.signum() < 0 && deliveredQty.compareTo(qtyOrdered) < 0)
)
)
{
throw new IllegalStateException("Total matched delivered qty > ordered qty. MatchedDeliveredQty="+deliveredQty+", OrderedQty="+qtyOrdered+", Line="+line);
}
}
}
}
// Purchase Order Delivered/Invoiced
// (Reserved in VMatch and MInOut.completeIt)
if (success && getC_OrderLine_ID() != 0)
{
MOrderLine orderLine = getOrderLine();
//
if (m_isInOutLineChange && (newRecord || getM_InOutLine_ID() != get_ValueOldAsInt("M_InOutLine_ID")))
{
if (getM_InOutLine_ID() != 0) // new delivery
orderLine.setQtyDelivered(orderLine.getQtyDelivered().add(getQty()));
else if (!newRecord) // if (getM_InOutLine_ID() == 0) // reset to 0
orderLine.setQtyDelivered(orderLine.getQtyDelivered().subtract(getQty()));
orderLine.setDateDelivered(getDateTrx()); // overwrite=last
}
else if (!newRecord && getM_InOutLine_ID() > 0 && is_ValueChanged(COLUMNNAME_Qty))
{
BigDecimal oldQty = (BigDecimal)(get_ValueOld(COLUMNNAME_Qty));
orderLine.setQtyDelivered(orderLine.getQtyDelivered().subtract(oldQty.subtract(getQty())));
}
if (m_isInvoiceLineChange && (newRecord || getC_InvoiceLine_ID() != get_ValueOldAsInt("C_InvoiceLine_ID")))
{
if (getC_InvoiceLine_ID() != 0) // first time
orderLine.setQtyInvoiced(orderLine.getQtyInvoiced().add(getQty()));
else if (!newRecord) // if (getC_InvoiceLine_ID() == 0) // set to 0
orderLine.setQtyInvoiced(orderLine.getQtyInvoiced().subtract(getQty()));
orderLine.setDateInvoiced(getDateTrx()); // overwrite=last
}
else if (!newRecord && getC_InvoiceLine_ID() > 0 && is_ValueChanged(COLUMNNAME_Qty))
{
BigDecimal oldQty = (BigDecimal)(get_ValueOld(COLUMNNAME_Qty));
orderLine.setQtyInvoiced(orderLine.getQtyInvoiced().subtract(oldQty.subtract(getQty())));
}
// Update Order ASI if full match
if (orderLine.getM_AttributeSetInstance_ID() == 0
&& getM_InOutLine_ID() != 0)
{
MInOutLine iol = new MInOutLine (getCtx(), getM_InOutLine_ID(), get_TrxName());
if (iol.getMovementQty().compareTo(orderLine.getQtyOrdered()) == 0)
orderLine.setM_AttributeSetInstance_ID(iol.getM_AttributeSetInstance_ID());
}
return orderLine.save();
}
//
return success;
} // afterSave
/**
* Get the newer Date Acct between invoice and shipment
* @return date or null
*/
public Timestamp getNewerDateAcct()
{
Timestamp invoiceDate = null;
Timestamp shipDate = null;
if (getC_InvoiceLine_ID() != 0)
{
String sql = "SELECT i.DateAcct "
+ "FROM C_InvoiceLine il"
+ " INNER JOIN C_Invoice i ON (i.C_Invoice_ID=il.C_Invoice_ID) "
+ "WHERE C_InvoiceLine_ID=?";
invoiceDate = DB.getSQLValueTS(null, sql, getC_InvoiceLine_ID());
}
//
if (getM_InOutLine_ID() != 0)
{
String sql = "SELECT io.DateAcct "
+ "FROM M_InOutLine iol"
+ " INNER JOIN M_InOut io ON (io.M_InOut_ID=iol.M_InOut_ID) "
+ "WHERE iol.M_InOutLine_ID=?";
shipDate = DB.getSQLValueTS(null, sql, getM_InOutLine_ID());
}
//
// Assuming that order date is always earlier
if (invoiceDate == null)
return shipDate;
if (shipDate == null)
return invoiceDate;
if (invoiceDate.after(shipDate))
return invoiceDate;
return shipDate;
} // getNewerDateAcct
/**
* Before Delete
* @return true if acct was deleted
*/
@Override
protected boolean beforeDelete ()
{
if (isPosted())
{
MPeriod.testPeriodOpen(getCtx(), getDateTrx(), MDocType.DOCBASETYPE_MatchPO, getAD_Org_ID());
setPosted(false);
MFactAcct.deleteEx (Table_ID, get_ID(), get_TrxName());
}
return true;
} // beforeDelete
/**
* After Delete.
* Update Order Line Qty Delivered/Invoiced .
* @param success success
* @return success
*/
@Override
protected boolean afterDelete (boolean success)
{
// Order Delivered/Invoiced
// (Reserved in VMatch and MInOut.completeIt)
if (success && getC_OrderLine_ID() != 0)
{
MOrderLine orderLine = new MOrderLine (getCtx(), getC_OrderLine_ID(), get_TrxName());
if (getM_InOutLine_ID() != 0)
orderLine.setQtyDelivered(orderLine.getQtyDelivered().subtract(getQty()));
if (getC_InvoiceLine_ID() != 0)
orderLine.setQtyInvoiced(orderLine.getQtyInvoiced().subtract(getQty()));
return orderLine.save(get_TrxName());
}
return success;
} // afterDelete
/**
* String Representation
* @return info
*/
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder ("MMatchPO[");
sb.append (get_ID())
.append (",Qty=").append (getQty())
.append (",C_OrderLine_ID=").append (getC_OrderLine_ID())
.append (",M_InOutLine_ID=").append (getM_InOutLine_ID())
.append (",C_InvoiceLine_ID=").append (getC_InvoiceLine_ID())
.append (",Processed=").append(isProcessed())
.append (",Posted=").append(isPosted())
.append ("]");
return sb.toString ();
} // toString
/**
* Reverse this MatchPO document.
* @param reversalDate
* @return true if reversed
* @throws Exception
*/
public boolean reverse(Timestamp reversalDate)
{
return reverse(reversalDate, false);
}
/**
* Reverse this MatchPO document.
* @param reversalDate
* @param reverseMatchingOnly true if MR is not reverse
* @return true if reversed
* @throws Exception
*/
public boolean reverse(Timestamp reversalDate, boolean reverseMatchingOnly)
{
if (this.isProcessed() && this.getReversal_ID() == 0)
{
MMatchPO reversal = new MMatchPO (getCtx(), 0, get_TrxName());
reversal.setC_InvoiceLine_ID(getC_InvoiceLine_ID());
reversal.setM_InOutLine_ID(getM_InOutLine_ID());
if (getC_OrderLine_ID() != 0)
reversal.setC_OrderLine_ID(getC_OrderLine_ID());
else{
reversal.setC_OrderLine_ID(getM_InOutLine().getC_OrderLine_ID());
}
reversal.setM_Product_ID(getM_Product_ID());
reversal.setM_AttributeSetInstance_ID(getM_AttributeSetInstance_ID());
reversal.setAD_Org_ID(this.getAD_Org_ID());
reversal.setDescription("(->" + this.getDocumentNo() + ")");
reversal.setQty(this.getQty().negate());
reversal.setDateAcct(reversalDate);
reversal.setDateTrx(reversalDate);
reversal.set_ValueNoCheck ("DocumentNo", null);
reversal.setPosted (false);
reversal.setProcessed(true);
reversal.setRef_MatchPO_ID(getRef_MatchPO_ID());
reversal.setReversal_ID(getM_MatchPO_ID());
reversal.saveEx();
this.setDescription("(" + reversal.getDocumentNo() + "<-)");
this.setReversal_ID(reversal.getM_MatchPO_ID());
this.saveEx();
//update qtyOrdered
if (reverseMatchingOnly && reversal.getM_InOutLine_ID() > 0 && reversal.getC_OrderLine_ID() > 0)
{
MInOutLine sLine = new MInOutLine(Env.getCtx(), reversal.getM_InOutLine_ID(), get_TrxName());
if (sLine.getMovementQty().compareTo(this.getQty()) == 0 && sLine.getC_OrderLine_ID() == reversal.getC_OrderLine_ID())
{
//clear c_orderline from shipment so we can match the shipment again (to the same or different order line)
sLine.setC_OrderLine_ID(0);
sLine.saveEx();
}
//add back qtyOrdered
MOrderLine oLine = new MOrderLine(Env.getCtx(), reversal.getC_OrderLine_ID(), get_TrxName());
BigDecimal storageReservationToUpdate = oLine.getQtyReserved();
oLine.setQtyReserved(oLine.getQtyReserved().add(getQty()));
BigDecimal reservedAndDelivered = oLine.getQtyDelivered().add(oLine.getQtyReserved());
if (reservedAndDelivered.compareTo(oLine.getQtyOrdered()) > 0)
{
oLine.setQtyReserved(oLine.getQtyReserved().subtract(reservedAndDelivered.subtract(oLine.getQtyOrdered())));
if (oLine.getQtyReserved().signum()==-1)
oLine.setQtyReserved(Env.ZERO);
}
oLine.saveEx();
storageReservationToUpdate = storageReservationToUpdate.subtract(oLine.getQtyReserved());
if (storageReservationToUpdate.signum() != 0)
{
IReservationTracer tracer = null;
IReservationTracerFactory factory = Core.getReservationTracerFactory();
if (factory != null)
{
int docTypeId = DB.getSQLValue((String)null, Doc.DOC_TYPE_BY_DOC_BASE_TYPE_SQL, getAD_Client_ID(), Doc.DOCTYPE_MatMatchPO);
tracer = factory.newTracer(docTypeId, reversal.getDocumentNo(), 10,
reversal.get_Table_ID(), reversal.get_ID(), oLine.getM_Warehouse_ID(),
oLine.getM_Product_ID(), oLine.getM_AttributeSetInstance_ID(), oLine.getParent().isSOTrx(),
get_TrxName());
}
boolean success = MStorageReservation.add (Env.getCtx(), oLine.getM_Warehouse_ID(),
oLine.getM_Product_ID(),
oLine.getM_AttributeSetInstance_ID(),
storageReservationToUpdate.negate(), oLine.getParent().isSOTrx(), get_TrxName(), tracer);
if (!success)
return false;
}
}
// auto create new matchpo if have invoice line
if ( reversal.getC_InvoiceLine_ID() > 0 && reversal.getM_InOutLine_ID() > 0 )
{
MMatchPO[] matchPOs = MMatchPO.getOrderLine(reversal.getCtx(), reversal.getC_OrderLine_ID(), reversal.get_TrxName());
BigDecimal matchQty = getQty();
for (MMatchPO matchPO : matchPOs)
{
if (matchPO.getReversal_ID() == 0 && !matchPO.isPosted()
&& matchPO.getC_InvoiceLine_ID() == reversal.getC_InvoiceLine_ID()
&& matchPO.getM_InOutLine_ID() == 0 )
{
matchPO.setQty(matchPO.getQty().add(matchQty));
matchPO.saveEx();
matchQty = BigDecimal.ZERO;
break;
}
}
if (matchQty.signum() != 0)
{
MMatchPO matchPO = new MMatchPO (getCtx(), 0, get_TrxName());
matchPO.setC_OrderLine_ID(getC_OrderLine_ID());
matchPO.setC_InvoiceLine_ID(getC_InvoiceLine_ID());
matchPO.setM_InOutLine_ID(0);
matchPO.setAD_Org_ID(getAD_Org_ID());
matchPO.setQty(getQty());
matchPO.setDateAcct(getDateAcct());
matchPO.setDateTrx(getDateTrx());
matchPO.setM_AttributeSetInstance_ID(getM_AttributeSetInstance_ID());
matchPO.setM_Product_ID(getM_Product_ID());
matchPO.setDescription(null);
matchPO.setProcessed(true);
matchPO.setPosted (false);
matchPO.saveEx();
}
}
return true;
}
return false;
}
/**
* @return true if this is created to reverse another match po document
*/
public boolean isReversal() {
if (getReversal_ID() > 0) {
MMatchPO reversal = new MMatchPO (getCtx(), getReversal_ID(), get_TrxName());
if (reversal.getM_MatchPO_ID() < getM_MatchPO_ID())
return true;
}
return false;
}
/**
* Get or create Match PO record for order line.
* @param C_OrderLine_ID
* @param qty
* @param sLine
* @param trxName
* @return new or existing MMatchPO record
*/
public static MMatchPO getOrCreate(int C_OrderLine_ID, BigDecimal qty, MInOutLine sLine, String trxName) {
Query query = new Query(Env.getCtx(), MMatchPO.Table_Name, "C_OrderLine_ID=? AND Qty=? AND Posted IN (?,?) AND M_InOutLine_ID IS NULL", trxName);
MMatchPO matchPO = query.setParameters(C_OrderLine_ID, qty, Doc.STATUS_NotPosted, Doc.STATUS_Deferred).first();
if (matchPO != null) {
matchPO.setM_InOutLine_ID(sLine.getM_InOutLine_ID());
return matchPO;
} else {
return new MMatchPO (sLine, null, qty);
}
}
} // MMatchPO