IDEMPIERE-4416 invalidating a purchase order doesn't remove connectio… (#1942)
* IDEMPIERE-4416 invalidating a purchase order doesn't remove connection to receipt * IDEMPIERE-4416 invalidating a purchase order doesn't remove connection to receipt - refactoring, make MiniTableImpl usable outside of test * IDEMPIERE-4416 invalidating a purchase order doesn't remove connection to receipt - refactoring, move business and sql logic from Match to model class. - add more unit test * IDEMPIERE-4416 invalidating a purchase order doesn't remove connection to receipt - set M_InOut.C_Order_ID to null * IDEMPIERE-4416 invalidating a purchase order doesn't remove connection to receipt - update unit test for setting of M_InOut.C_Order_ID to null.
This commit is contained in:
parent
0da6a2ab44
commit
890862de47
|
@ -77,10 +77,246 @@ import org.compiere.wf.MWorkflow;
|
|||
public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
|
||||
{
|
||||
/**
|
||||
*
|
||||
* generated serial id
|
||||
*/
|
||||
private static final long serialVersionUID = -8699990804131725782L;
|
||||
|
||||
private static final String BASE_MATCHING_SQL =
|
||||
"""
|
||||
SELECT hdr.M_InOut_ID, hdr.DocumentNo, hdr.MovementDate, bp.Name, hdr.C_BPartner_ID,
|
||||
lin.Line, lin.M_InOutLine_ID, p.Name, lin.M_Product_ID,
|
||||
CASE WHEN (dt.DocBaseType='MMS' AND hdr.issotrx='N') THEN lin.MovementQty * -1 ELSE lin.MovementQty END,
|
||||
%s, org.Name, hdr.AD_Org_ID
|
||||
FROM M_InOut hdr
|
||||
INNER JOIN AD_Org org ON (hdr.AD_Org_ID=org.AD_Org_ID)
|
||||
INNER JOIN C_BPartner bp ON (hdr.C_BPartner_ID=bp.C_BPartner_ID)
|
||||
INNER JOIN M_InOutLine lin ON (hdr.M_InOut_ID=lin.M_InOut_ID)
|
||||
INNER JOIN M_Product p ON (lin.M_Product_ID=p.M_Product_ID)
|
||||
INNER JOIN C_DocType dt ON (hdr.C_DocType_ID = dt.C_DocType_ID AND (dt.DocBaseType='MMR' OR (dt.DocBaseType='MMS' AND hdr.isSOTrx ='N')))
|
||||
FULL JOIN %s m ON (lin.M_InOutLine_ID=m.M_InOutLine_ID)
|
||||
WHERE hdr.DocStatus IN ('CO','CL')
|
||||
""";
|
||||
|
||||
private static final String BASE_MATCHING_GROUP_BY_SQL =
|
||||
"""
|
||||
GROUP BY hdr.M_InOut_ID,hdr.DocumentNo,hdr.MovementDate,bp.Name,hdr.C_BPartner_ID,
|
||||
lin.Line,lin.M_InOutLine_ID,p.Name,lin.M_Product_ID,lin.MovementQty, org.Name, hdr.AD_Org_ID, dt.DocBaseType, hdr.IsSOTrx
|
||||
HAVING %s <> %s
|
||||
""";
|
||||
|
||||
public static final String NOT_FULLY_MATCHED_TO_ORDER = BASE_MATCHING_SQL.formatted(
|
||||
"SUM(CASE WHEN m.M_InOutLine_ID IS NOT NULL THEN COALESCE(m.Qty,0) ELSE 0 END)",
|
||||
"M_MatchPO");
|
||||
|
||||
public static final String NOT_FULLY_MATCHED_TO_ORDER_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL.formatted(
|
||||
"CASE WHEN (dt.DocBaseType='MMS' AND hdr.issotrx='N') THEN lin.MovementQty * -1 ELSE lin.MovementQty END",
|
||||
"SUM(CASE WHEN m.M_InOutLine_ID IS NOT NULL THEN COALESCE(m.Qty,0) ELSE 0 END)");
|
||||
|
||||
public static final String FULL_OR_PARTIALLY_MATCHED_TO_ORDER = BASE_MATCHING_SQL.formatted(
|
||||
"SUM(CASE WHEN m.M_InOutLine_ID IS NOT NULL THEN COALESCE(m.Qty,0) ELSE 0 END)",
|
||||
"M_MatchPO");
|
||||
|
||||
public static final String FULL_OR_PARTIALLY_MATCHED_TO_ORDER_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL.formatted(
|
||||
"0",
|
||||
"SUM(CASE WHEN m.M_InOutLine_ID IS NOT NULL THEN COALESCE(m.Qty,0) ELSE 0 END)");
|
||||
|
||||
public static final String NOT_FULLY_MATCHED_TO_INVOICE = BASE_MATCHING_SQL.formatted("SUM(COALESCE(m.Qty,0))",
|
||||
"M_MatchInv");
|
||||
|
||||
public static final String NOT_FULLY_MATCHED_TO_INVOICE_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL.formatted(
|
||||
"CASE WHEN (dt.DocBaseType='MMS' AND hdr.issotrx='N') THEN lin.MovementQty * -1 ELSE lin.MovementQty END",
|
||||
"SUM(COALESCE(m.Qty,0))");
|
||||
|
||||
public static final String FULL_OR_PARTIALLY_MATCHED_TO_INVOICE = BASE_MATCHING_GROUP_BY_SQL.formatted(
|
||||
"SUM(COALESCE(m.Qty,0))",
|
||||
"M_MatchInv");
|
||||
|
||||
public static final String FULL_OR_PARTIALLY_MATCHED_TO_INVOICE_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL.formatted(
|
||||
"0",
|
||||
"SUM(COALESCE(m.Qty,0))");
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param C_OrderLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of material receipts not fully matched to order
|
||||
*/
|
||||
public static List<MatchingRecord> getNotFullyMatchedToOrder(int C_BPartner_ID, int M_Product_ID, int C_OrderLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(NOT_FULLY_MATCHED_TO_ORDER);
|
||||
if (C_OrderLine_ID > 0) {
|
||||
builder.append(" AND m.C_OrderLine_ID=").append(C_OrderLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.MovementDate").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.MovementDate").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ NOT_FULLY_MATCHED_TO_ORDER_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param C_OrderLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of material receipts full or partially match to order
|
||||
*/
|
||||
public static List<MatchingRecord> getFullOrPartiallyMatchedToOrder(int C_BPartner_ID, int M_Product_ID, int C_OrderLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(FULL_OR_PARTIALLY_MATCHED_TO_ORDER);
|
||||
if (C_OrderLine_ID > 0) {
|
||||
builder.append(" AND m.C_OrderLine_ID=").append(C_OrderLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.MovementDate").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.MovementDate").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ FULL_OR_PARTIALLY_MATCHED_TO_ORDER_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param C_InvoiceLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of material receipts not fully match to invoice
|
||||
*/
|
||||
public static List<MatchingRecord> getNotFullyMatchedToInvoice(int C_BPartner_ID, int M_Product_ID, int C_InvoiceLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(NOT_FULLY_MATCHED_TO_INVOICE);
|
||||
if (C_InvoiceLine_ID > 0) {
|
||||
builder.append(" AND m.C_InvoiceLine_ID=").append(C_InvoiceLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.MovementDate").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.MovementDate").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ NOT_FULLY_MATCHED_TO_INVOICE_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param C_InvoiceLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of material receipts full or partially match to invoice
|
||||
*/
|
||||
public static List<MatchingRecord> getFullOrPartiallyMatchedToInvoice(int C_BPartner_ID, int M_Product_ID, int C_InvoiceLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(FULL_OR_PARTIALLY_MATCHED_TO_INVOICE);
|
||||
if (C_InvoiceLine_ID > 0) {
|
||||
builder.append(" AND m.C_InvoiceLine_ID=").append(C_InvoiceLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.MovementDate").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.MovementDate").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ FULL_OR_PARTIALLY_MATCHED_TO_INVOICE_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* record for matchings
|
||||
*/
|
||||
public static record MatchingRecord(int M_InOut_ID, String documentNo, Timestamp documentDate, String businessPartnerName, int C_BPartner_ID, int line, int M_InOutLine_ID,
|
||||
String productName, int M_Product_ID, BigDecimal movementQty, BigDecimal matchedQty, String organizationName, int AD_Org_ID) {}
|
||||
|
||||
/**
|
||||
* Create Shipment From Order
|
||||
* @param order order
|
||||
|
|
|
@ -22,12 +22,19 @@ import java.sql.ResultSet;
|
|||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.adempiere.base.Core;
|
||||
import org.adempiere.exceptions.AdempiereException;
|
||||
import org.adempiere.exceptions.FillMandatoryException;
|
||||
import org.adempiere.exceptions.WarehouseLocatorConflictException;
|
||||
import org.adempiere.util.IReservationTracer;
|
||||
import org.adempiere.util.IReservationTracerFactory;
|
||||
import org.compiere.process.DocumentEngine;
|
||||
import org.compiere.util.CLogger;
|
||||
import org.compiere.util.DB;
|
||||
import org.compiere.util.Env;
|
||||
import org.compiere.util.Msg;
|
||||
import org.compiere.util.Util;
|
||||
import org.compiere.util.ValueNamePair;
|
||||
|
||||
/**
|
||||
* InOut Line
|
||||
|
@ -805,4 +812,139 @@ public class MInOutLine extends X_M_InOutLine
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match this material receipt line with invoice line
|
||||
* @param C_InvoiceLine_ID
|
||||
* @param qty
|
||||
* @return true if matching is ok
|
||||
*/
|
||||
public boolean matchToInvoiceLine(int C_InvoiceLine_ID, BigDecimal qty) {
|
||||
boolean success = false;
|
||||
if (C_InvoiceLine_ID <= 0)
|
||||
throw new IllegalArgumentException("Invalid C_InvoiceLine_ID argument: " + C_InvoiceLine_ID);
|
||||
|
||||
// Update Invoice Line
|
||||
MInvoiceLine iLine = new MInvoiceLine (Env.getCtx(), C_InvoiceLine_ID, get_TrxName());
|
||||
if (iLine.get_ID() != C_InvoiceLine_ID)
|
||||
throw new IllegalArgumentException("Invalid C_InvoiceLine_ID argument: " + C_InvoiceLine_ID);
|
||||
|
||||
iLine.setM_InOutLine_ID(get_ID());
|
||||
if (getC_OrderLine_ID() != 0)
|
||||
iLine.setC_OrderLine_ID(getC_OrderLine_ID());
|
||||
iLine.saveEx();
|
||||
// Create Shipment - Invoice Link
|
||||
if (iLine.getM_Product_ID() != 0)
|
||||
{
|
||||
MMatchInv match = new MMatchInv (iLine, null, qty);
|
||||
match.setM_InOutLine_ID(get_ID());
|
||||
match.saveEx();
|
||||
success = true;
|
||||
if (MClient.isClientAccountingImmediate()) {
|
||||
String ignoreError = DocumentEngine.postImmediate(match.getCtx(), match.getAD_Client_ID(), match.get_Table_ID(), match.get_ID(), true, match.get_TrxName());
|
||||
if (ignoreError != null) {
|
||||
log.warning(ignoreError);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
success = true;
|
||||
// Create PO - Invoice Link = corrects PO
|
||||
if (iLine.getM_Product_ID() != 0)
|
||||
{
|
||||
BigDecimal matchedQty = DB.getSQLValueBD(iLine.get_TrxName(), "SELECT Coalesce(SUM(Qty),0) FROM M_MatchPO WHERE C_InvoiceLine_ID=?" , iLine.getC_InvoiceLine_ID());
|
||||
if (matchedQty.add(qty).compareTo(iLine.getQtyInvoiced()) <= 0)
|
||||
{
|
||||
MMatchPO matchPO = MMatchPO.create(iLine, this, null, qty);
|
||||
if (matchPO != null)
|
||||
{
|
||||
matchPO.saveEx();
|
||||
if (MClient.isClientAccountingImmediate()) {
|
||||
String ignoreError = DocumentEngine.postImmediate(matchPO.getCtx(), matchPO.getAD_Client_ID(), matchPO.get_Table_ID(), matchPO.get_ID(), true, matchPO.get_TrxName());
|
||||
if (ignoreError != null)
|
||||
log.warning(ignoreError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match this material receipt line with order line
|
||||
* @param C_OrderLine_ID
|
||||
* @param qty
|
||||
* @return true if matching is ok
|
||||
*/
|
||||
public boolean matchToOrderLine(int C_OrderLine_ID, BigDecimal qty) {
|
||||
boolean success = false;
|
||||
// Update Order Line
|
||||
MOrderLine oLine = new MOrderLine(Env.getCtx(), C_OrderLine_ID, get_TrxName());
|
||||
BigDecimal storageReservationToUpdate = null;
|
||||
if (oLine.get_ID() != 0) // other in MInOut.completeIt
|
||||
{
|
||||
storageReservationToUpdate = oLine.getQtyReserved();
|
||||
oLine.setQtyReserved(oLine.getQtyReserved().subtract(qty));
|
||||
if (oLine.getQtyReserved().signum() == -1)
|
||||
oLine.setQtyReserved(Env.ZERO);
|
||||
else if (oLine.getQtyDelivered().compareTo(oLine.getQtyOrdered()) > 0)
|
||||
oLine.setQtyReserved(Env.ZERO);
|
||||
oLine.saveEx();
|
||||
storageReservationToUpdate = storageReservationToUpdate.subtract(oLine.getQtyReserved());
|
||||
}
|
||||
|
||||
// Update Shipment Line
|
||||
BigDecimal toDeliver = oLine.getQtyOrdered().subtract(oLine.getQtyDelivered());
|
||||
if (toDeliver.signum() < 0)
|
||||
toDeliver = Env.ZERO;
|
||||
if (getMovementQty().compareTo(toDeliver) <= 0)
|
||||
{
|
||||
setC_OrderLine_ID(C_OrderLine_ID);
|
||||
saveEx();
|
||||
}
|
||||
else if (getC_OrderLine_ID() != 0)
|
||||
{
|
||||
setC_OrderLine_ID(0);
|
||||
saveEx();
|
||||
}
|
||||
|
||||
// Create PO - Shipment Link
|
||||
if (getM_Product_ID() != 0)
|
||||
{
|
||||
MMatchPO match = MMatchPO.getOrCreate(C_OrderLine_ID, qty, this, get_TrxName());
|
||||
match.setC_OrderLine_ID(C_OrderLine_ID);
|
||||
if (!match.save())
|
||||
{
|
||||
String msg = "PO Match not created: " + match;
|
||||
ValueNamePair error = CLogger.retrieveError();
|
||||
if (error != null)
|
||||
{
|
||||
msg = msg + ". " + error.getName();
|
||||
}
|
||||
throw new AdempiereException(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = true;
|
||||
// Correct Ordered Qty for Stocked Products (see MOrder.reserveStock / MInOut.processIt)
|
||||
if (oLine.get_ID() > 0 && oLine.getM_Product_ID() > 0 && oLine.getProduct().isStocked() && storageReservationToUpdate != null) {
|
||||
IReservationTracer tracer = null;
|
||||
IReservationTracerFactory factory = Core.getReservationTracerFactory();
|
||||
if (factory != null) {
|
||||
tracer = factory.newTracer(getParent().getC_DocType_ID(), getParent().getDocumentNo(), getLine(),
|
||||
get_Table_ID(), get_ID(), oLine.getM_Warehouse_ID(),
|
||||
oLine.getM_Product_ID(), oLine.getM_AttributeSetInstance_ID(), oLine.getParent().isSOTrx(),
|
||||
get_TrxName());
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
success = true;
|
||||
|
||||
return success;
|
||||
}
|
||||
} // MInOutLine
|
||||
|
|
|
@ -73,10 +73,130 @@ import org.eevolution.model.MPPProductBOMLine;
|
|||
public class MInvoice extends X_C_Invoice implements DocAction, IDocsPostProcess
|
||||
{
|
||||
/**
|
||||
*
|
||||
* generated serial id
|
||||
*/
|
||||
private static final long serialVersionUID = 9166700544471146864L;
|
||||
|
||||
public static final String MATCH_TO_RECEIPT_SQL =
|
||||
"""
|
||||
SELECT hdr.C_Invoice_ID, hdr.DocumentNo, hdr.DateInvoiced, bp.Name, hdr.C_BPartner_ID,
|
||||
lin.Line, lin.C_InvoiceLine_ID, p.Name, lin.M_Product_ID,
|
||||
CASE WHEN dt.DocBaseType='APC' THEN lin.QtyInvoiced * -1 ELSE lin.QtyInvoiced END,SUM(NVL(mi.Qty,0)), org.Name, hdr.AD_Org_ID
|
||||
FROM C_Invoice hdr
|
||||
INNER JOIN AD_Org org ON (hdr.AD_Org_ID=org.AD_Org_ID)
|
||||
INNER JOIN C_BPartner bp ON (hdr.C_BPartner_ID=bp.C_BPartner_ID)
|
||||
INNER JOIN C_InvoiceLine lin ON (hdr.C_Invoice_ID=lin.C_Invoice_ID)
|
||||
INNER JOIN M_Product p ON (lin.M_Product_ID=p.M_Product_ID)
|
||||
INNER JOIN C_DocType dt ON (hdr.C_DocType_ID=dt.C_DocType_ID AND dt.DocBaseType IN ('API','APC'))
|
||||
FULL JOIN M_MatchInv mi ON (lin.C_InvoiceLine_ID=mi.C_InvoiceLine_ID)
|
||||
WHERE hdr.DocStatus IN ('CO','CL')
|
||||
""";
|
||||
|
||||
private static final String BASE_MATCHING_GROUP_BY_SQL =
|
||||
"""
|
||||
GROUP BY hdr.C_Invoice_ID,hdr.DocumentNo,hdr.DateInvoiced,bp.Name,hdr.C_BPartner_ID,
|
||||
lin.Line,lin.C_InvoiceLine_ID,p.Name,lin.M_Product_ID,dt.DocBaseType,lin.QtyInvoiced, org.Name, hdr.AD_Org_ID, dt.DocBaseType
|
||||
HAVING %s <> SUM(NVL(mi.Qty,0))
|
||||
""";
|
||||
public static final String NOT_FULLY_MATCHED_TO_RECEIPT_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL
|
||||
.formatted("CASE WHEN dt.DocBaseType='APC' THEN lin.QtyInvoiced * -1 ELSE lin.QtyInvoiced END");
|
||||
|
||||
public static final String FULL_OR_PARTIALLY_MATCHED_TO_RECEIPT_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL.formatted("0");
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param M_InOutLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of invoices not fully matched to receipt
|
||||
*/
|
||||
public static List<MatchingRecord> getNotFullyMatchedToReceipt(int C_BPartner_ID, int M_Product_ID, int M_InOutLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(MATCH_TO_RECEIPT_SQL);
|
||||
if (M_InOutLine_ID > 0) {
|
||||
builder.append(" AND mi.M_InOutLine_ID = ").append(M_InOutLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.DateInvoiced").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.DateInvoiced").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ NOT_FULLY_MATCHED_TO_RECEIPT_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param M_InOutLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of invoices full or partially match to receipt
|
||||
*/
|
||||
public static List<MatchingRecord> getFullOrPartiallyMatchedToReceipt(int C_BPartner_ID, int M_Product_ID, int M_InOutLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(MATCH_TO_RECEIPT_SQL);
|
||||
if (M_InOutLine_ID > 0) {
|
||||
builder.append(" AND mi.M_InOutLine_ID = ").append(M_InOutLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.DateInvoiced").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.DateInvoiced").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ FULL_OR_PARTIALLY_MATCHED_TO_RECEIPT_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* record for matchings
|
||||
*/
|
||||
public static record MatchingRecord(int C_Invoice_ID, String documentNo, Timestamp documentDate, String businessPartnerName, int C_BPartner_ID, int line, int C_InvoiceLine_ID,
|
||||
String productName, int M_Product_ID, BigDecimal qtyInvoiced, BigDecimal matchedQty, String organizationName, int AD_Org_ID) {}
|
||||
|
||||
/**
|
||||
* Get Payments Of BPartner
|
||||
* @param ctx context
|
||||
|
|
|
@ -1452,4 +1452,22 @@ public class MMatchPO extends X_M_MatchPO
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.sql.PreparedStatement;
|
|||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
@ -77,10 +78,255 @@ import org.eevolution.model.MPPProductBOMLine;
|
|||
public class MOrder extends X_C_Order implements DocAction
|
||||
{
|
||||
/**
|
||||
*
|
||||
* generated serial id
|
||||
*/
|
||||
private static final long serialVersionUID = 1298245367836653594L;
|
||||
|
||||
private static final String BASE_MATCHING_SQL =
|
||||
"""
|
||||
SELECT hdr.C_Order_ID, hdr.DocumentNo, hdr.DateOrdered, bp.Name, hdr.C_BPartner_ID,
|
||||
lin.Line, lin.C_OrderLine_ID, p.Name, lin.M_Product_ID,
|
||||
lin.QtyOrdered,
|
||||
%s,
|
||||
org.Name, hdr.AD_Org_ID
|
||||
FROM C_Order hdr
|
||||
INNER JOIN AD_Org org ON (hdr.AD_Org_ID=org.AD_Org_ID)
|
||||
INNER JOIN C_BPartner bp ON (hdr.C_BPartner_ID=bp.C_BPartner_ID)
|
||||
INNER JOIN C_OrderLine lin ON (hdr.C_Order_ID=lin.C_Order_ID)
|
||||
INNER JOIN M_Product p ON (lin.M_Product_ID=p.M_Product_ID)
|
||||
INNER JOIN C_DocType dt ON (hdr.C_DocType_ID=dt.C_DocType_ID AND dt.DocBaseType='POO')
|
||||
FULL JOIN M_MatchPO mo ON (lin.C_OrderLine_ID=mo.C_OrderLine_ID)
|
||||
WHERE %s
|
||||
AND hdr.DocStatus IN ('CO','CL')
|
||||
""";
|
||||
|
||||
private static final String BASE_MATCHING_GROUP_BY_SQL =
|
||||
"""
|
||||
GROUP BY hdr.C_Order_ID,hdr.DocumentNo,hdr.DateOrdered,bp.Name,hdr.C_BPartner_ID,
|
||||
lin.Line,lin.C_OrderLine_ID,p.Name,lin.M_Product_ID,lin.QtyOrdered, org.Name, hdr.AD_Org_ID
|
||||
HAVING %s <> %s
|
||||
""";
|
||||
|
||||
public static final String NOT_FULLY_MATCHED_TO_RECEIPT = BASE_MATCHING_SQL
|
||||
.formatted("SUM(CASE WHEN (mo.M_InOutLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END)",
|
||||
" ( mo.M_InOutLine_ID IS NULL OR "
|
||||
+ " (lin.QtyOrdered <> (SELECT sum(mo1.Qty) AS Qty"
|
||||
+ " FROM m_matchpo mo1 WHERE "
|
||||
+ " mo1.C_ORDERLINE_ID=lin.C_ORDERLINE_ID AND "
|
||||
+ " hdr.C_ORDER_ID=lin.C_ORDER_ID AND "
|
||||
+ " mo1.M_InOutLine_ID"
|
||||
+ " IS NOT NULL group by mo1.C_ORDERLINE_ID))) ");
|
||||
|
||||
public static final String NOT_FULLY_MATCHED_TO_RECEIPT_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL
|
||||
.formatted("lin.QtyOrdered", "SUM(CASE WHEN (mo.M_InOutLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END) ");
|
||||
|
||||
public static final String FULL_OR_PARTIALLY_MATCHED_TO_RECEIPT = BASE_MATCHING_SQL
|
||||
.formatted("SUM(CASE WHEN (mo.M_InOutLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END)", " mo.M_InOutLine_ID IS NOT NULL ");
|
||||
|
||||
public static final String FULL_OR_PARTIALLY_MATCHED_TO_RECEIPT_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL
|
||||
.formatted("0", "SUM(CASE WHEN (mo.M_InOutLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END) ");
|
||||
|
||||
public static final String NOT_FULLY_MATCHED_TO_INVOICE = BASE_MATCHING_SQL
|
||||
.formatted("SUM(CASE WHEN (mo.C_InvoiceLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END)",
|
||||
" ( mo.C_InvoiceLine_ID IS NULL OR "
|
||||
+ " (lin.QtyOrdered <> (SELECT sum(mo1.Qty) AS Qty"
|
||||
+ " FROM m_matchpo mo1 WHERE "
|
||||
+ " mo1.C_ORDERLINE_ID=lin.C_ORDERLINE_ID AND "
|
||||
+ " hdr.C_ORDER_ID=lin.C_ORDER_ID AND "
|
||||
+ " mo1.C_InvoiceLine_ID"
|
||||
+ " IS NOT NULL group by mo1.C_ORDERLINE_ID))) ");
|
||||
|
||||
public static final String NOT_FULLY_MATCHED_TO_INVOICE_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL
|
||||
.formatted("lin.QtyOrdered", "SUM(CASE WHEN (mo.C_InvoiceLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END) ");
|
||||
|
||||
public static final String FULL_OR_PARTIALLY_MATCHED_TO_INVOICE = BASE_MATCHING_SQL
|
||||
.formatted("SUM(CASE WHEN (mo.C_InvoiceLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END)", " mo.C_InvoiceLine_ID IS NOT NULL ");
|
||||
|
||||
public static final String FULL_OR_PARTIALLY_MATCHED_TO_INVOICE_GROUP_BY = BASE_MATCHING_GROUP_BY_SQL
|
||||
.formatted("0", "SUM(CASE WHEN (mo.C_InvoiceLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END) ");
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param M_InOutLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of orders not fully matched to receipt
|
||||
*/
|
||||
public static List<MatchingRecord> getNotFullyMatchedToReceipt(int C_BPartner_ID, int M_Product_ID, int M_InOutLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(NOT_FULLY_MATCHED_TO_RECEIPT);
|
||||
if (M_InOutLine_ID > 0) {
|
||||
builder.append(" AND mo.M_InOutLine_ID = ").append(M_InOutLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.DateOrdered").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.DateOrdered").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ NOT_FULLY_MATCHED_TO_RECEIPT_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param M_InOutLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of orders full or partially match to receipt
|
||||
*/
|
||||
public static List<MatchingRecord> getFullOrPartiallyMatchedToReceipt(int C_BPartner_ID, int M_Product_ID, int M_InOutLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(FULL_OR_PARTIALLY_MATCHED_TO_RECEIPT);
|
||||
if (M_InOutLine_ID > 0) {
|
||||
builder.append(" AND mo.M_InOutLine_ID = ").append(M_InOutLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.DateOrdered").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.DateOrdered").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ FULL_OR_PARTIALLY_MATCHED_TO_RECEIPT_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param C_InvoiceLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of orders not fully matched to invoice
|
||||
*/
|
||||
public static List<MatchingRecord> getNotFullyMatchedToInvoice(int C_BPartner_ID, int M_Product_ID, int C_InvoiceLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(NOT_FULLY_MATCHED_TO_RECEIPT);
|
||||
if (C_InvoiceLine_ID > 0) {
|
||||
builder.append(" AND mo.C_InvoiceLine_ID = ").append(C_InvoiceLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.DateOrdered").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.DateOrdered").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ NOT_FULLY_MATCHED_TO_INVOICE_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param C_BPartner_ID
|
||||
* @param M_Product_ID
|
||||
* @param C_InvoiceLine_ID
|
||||
* @param from
|
||||
* @param to
|
||||
* @param trxName
|
||||
* @return list of orders full or partially match to invoice
|
||||
*/
|
||||
public static List<MatchingRecord> getFullOrPartiallyMatchedToInvoice(int C_BPartner_ID, int M_Product_ID, int C_InvoiceLine_ID, Timestamp from, Timestamp to, String trxName) {
|
||||
StringBuilder builder = new StringBuilder(FULL_OR_PARTIALLY_MATCHED_TO_INVOICE);
|
||||
if (C_InvoiceLine_ID > 0) {
|
||||
builder.append(" AND mo.C_InvoiceLine_ID = ").append(C_InvoiceLine_ID);
|
||||
}
|
||||
if (M_Product_ID > 0) {
|
||||
builder.append(" AND lin.M_Product_ID=").append(M_Product_ID);
|
||||
}
|
||||
if (C_BPartner_ID > 0) {
|
||||
builder.append(" AND hdr.C_BPartner_ID=").append(C_BPartner_ID);
|
||||
}
|
||||
if (from != null) {
|
||||
builder.append(" AND ").append("hdr.DateOrdered").append(" >= ").append(DB.TO_DATE(from));
|
||||
}
|
||||
if (to != null) {
|
||||
builder.append(" AND ").append("hdr.DateOrdered").append(" <= ").append(DB.TO_DATE(to));
|
||||
}
|
||||
String sql = MRole.getDefault().addAccessSQL(
|
||||
builder.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO)
|
||||
+ FULL_OR_PARTIALLY_MATCHED_TO_INVOICE_GROUP_BY;
|
||||
|
||||
List<MatchingRecord> records = new ArrayList<>();
|
||||
try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
MatchingRecord matchingRecord = new MatchingRecord(rs.getInt(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getInt(5), rs.getInt(6), rs.getInt(7),
|
||||
rs.getString(8), rs.getInt(9), rs.getBigDecimal(10), rs.getBigDecimal(11), rs.getString(12), rs.getInt(13));
|
||||
records.add(matchingRecord);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* record for matchings
|
||||
*/
|
||||
public static record MatchingRecord(int C_Order_ID, String documentNo, Timestamp documentDate, String businessPartnerName, int C_BPartner_ID, int line, int C_OrderLine_ID,
|
||||
String productName, int M_Product_ID, BigDecimal qtyOrdered, BigDecimal matchedQty, String organizationName, int AD_Org_ID) {}
|
||||
|
||||
/**
|
||||
* Create new Order by copying
|
||||
* @param from order
|
||||
|
@ -2451,8 +2697,13 @@ public class MOrder extends X_C_Order implements DocAction
|
|||
so.saveEx();
|
||||
}
|
||||
|
||||
if (isSOTrx()) {
|
||||
if (!createReversals())
|
||||
return false;
|
||||
} else {
|
||||
if (!createPOReversals())
|
||||
return false;
|
||||
}
|
||||
|
||||
MOrderLine[] lines = getLines(true, MOrderLine.COLUMNNAME_M_Product_ID);
|
||||
for (int i = 0; i < lines.length; i++)
|
||||
|
@ -2523,6 +2774,7 @@ public class MOrder extends X_C_Order implements DocAction
|
|||
if (!isSOTrx())
|
||||
return true;
|
||||
|
||||
if (log.isLoggable(Level.INFO))
|
||||
log.info("createReversals");
|
||||
StringBuilder info = new StringBuilder();
|
||||
|
||||
|
@ -2596,6 +2848,37 @@ public class MOrder extends X_C_Order implements DocAction
|
|||
return true;
|
||||
} // createReversals
|
||||
|
||||
/**
|
||||
* Create match po reversals
|
||||
* @return true if success
|
||||
*/
|
||||
protected boolean createPOReversals() {
|
||||
if (isSOTrx())
|
||||
return true;
|
||||
|
||||
Timestamp loginDate = TimeUtil.getDay(Env.getContextAsDate(Env.getCtx(), Env.DATE));
|
||||
for(MOrderLine line : getLines()) {
|
||||
MMatchPO[] matchPOs = MMatchPO.getOrderLine(Env.getCtx(), line.get_ID(), get_TrxName());
|
||||
for(MMatchPO matchPO : matchPOs) {
|
||||
if (matchPO.getReversal_ID() > 0)
|
||||
continue;
|
||||
if (!matchPO.reverse(loginDate, true)) {
|
||||
m_processMsg = "Could not Reverse " + matchPO;
|
||||
return false;
|
||||
}
|
||||
if (matchPO.getM_InOutLine_ID() > 0) {
|
||||
MInOutLine iol = new MInOutLine(Env.getCtx(), matchPO.getM_InOutLine_ID(), get_TrxName());
|
||||
MInOut io = new MInOut(Env.getCtx(), iol.getM_InOut_ID(), get_TrxName());
|
||||
if (io.getC_Order_ID() == this.getC_Order_ID()) {
|
||||
io.setC_Order_ID(0);
|
||||
io.saveEx();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close Document.
|
||||
|
|
|
@ -258,7 +258,7 @@
|
|||
<setEntry value="org.eclipse.equinox.servletbridge@default:default"/>
|
||||
<setEntry value="org.eclipse.equinox.simpleconfigurator.manipulator@default:default"/>
|
||||
<setEntry value="org.eclipse.equinox.simpleconfigurator@1:true"/>
|
||||
<setEntry value="org.eclipse.jdt.core.compiler.batch*3.33.0.v20230218-1114@default:default"/>
|
||||
<setEntry value="org.eclipse.jdt.core.compiler.batch@default:default"/>
|
||||
<setEntry value="org.eclipse.jdt.core@default:default"/>
|
||||
<setEntry value="org.eclipse.jetty.alpn.client@default:default"/>
|
||||
<setEntry value="org.eclipse.jetty.annotations@default:true"/>
|
||||
|
|
|
@ -21,31 +21,21 @@ import java.sql.Timestamp;
|
|||
import java.util.Vector;
|
||||
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.minigrid.ColumnInfo;
|
||||
import org.compiere.minigrid.IDColumn;
|
||||
import org.compiere.minigrid.IMiniTable;
|
||||
import org.compiere.model.MClient;
|
||||
import org.compiere.model.MInOut;
|
||||
import org.compiere.model.MInOutLine;
|
||||
import org.compiere.model.MInvoiceLine;
|
||||
import org.compiere.model.MMatchInv;
|
||||
import org.compiere.model.MMatchPO;
|
||||
import org.compiere.model.MOrderLine;
|
||||
import org.compiere.model.MInvoice;
|
||||
import org.compiere.model.MOrder;
|
||||
import org.compiere.model.MRole;
|
||||
import org.compiere.model.MStorageReservation;
|
||||
import org.compiere.model.Query;
|
||||
import org.compiere.process.DocumentEngine;
|
||||
import org.compiere.util.CLogger;
|
||||
import org.compiere.util.DB;
|
||||
import org.compiere.util.Env;
|
||||
import org.compiere.util.KeyNamePair;
|
||||
import org.compiere.util.Msg;
|
||||
import org.compiere.util.Trx;
|
||||
import org.compiere.util.ValueNamePair;
|
||||
|
||||
public class Match
|
||||
{
|
||||
|
@ -64,7 +54,7 @@ public class Match
|
|||
public static final int MATCH_ORDER = 2;
|
||||
|
||||
public static final int MODE_NOTMATCHED = 0;
|
||||
//private static final int MODE_MATCHED = 1;
|
||||
public static final int MODE_MATCHED = 1;
|
||||
|
||||
/** Indexes in Table */
|
||||
public static final int I_ID = 0;
|
||||
|
@ -80,11 +70,12 @@ public class Match
|
|||
private String m_dateColumn = "";
|
||||
private String m_qtyColumn = "";
|
||||
private String m_groupBy = "";
|
||||
private StringBuffer m_linetype = null;
|
||||
private String m_trxName = null;
|
||||
|
||||
/**
|
||||
* Match From Changed - Fill Match To
|
||||
* @param selection match from
|
||||
* @param list of match to option
|
||||
*/
|
||||
protected Vector<String> cmd_matchFrom(String selection)
|
||||
{
|
||||
|
@ -103,7 +94,15 @@ public class Match
|
|||
|
||||
|
||||
/**
|
||||
* Search Button Pressed - Fill match from
|
||||
* Search Button Pressed - Fill match from.
|
||||
* @param xMatchedTable
|
||||
* @param display match from (MATCH_* constant), to popular xMatchedTable.
|
||||
* @param matchToString match to (invoice, material receipt or purchase order)
|
||||
* @param Product optional M_Product_ID
|
||||
* @param Vendor optional C_BPartner_ID
|
||||
* @param from optional from date
|
||||
* @param to optional to date
|
||||
* @param matched true for partial or fully match, false for not matched
|
||||
*/
|
||||
public IMiniTable cmd_search(IMiniTable xMatchedTable, int display, String matchToString, Integer Product, Integer Vendor, Timestamp from, Timestamp to, boolean matched)
|
||||
{
|
||||
|
@ -144,9 +143,14 @@ public class Match
|
|||
|
||||
} // cmd_search
|
||||
|
||||
|
||||
/**
|
||||
* Process Button Pressed - Process Matching
|
||||
* @param xMatchedTable Match from table
|
||||
* @param xMatchedToTable Match to table
|
||||
* @param matchMode {@link #MODE_NOTMATCHED} or {@link #MODE_MATCHED}
|
||||
* @param matchFrom Match from document type
|
||||
* @param matchTo Match to document type
|
||||
* @param m_xMatched Difference to match
|
||||
*/
|
||||
public void cmd_process(IMiniTable xMatchedTable, IMiniTable xMatchedToTable, int matchMode, int matchFrom, String matchTo, BigDecimal m_xMatched)
|
||||
{
|
||||
|
@ -233,6 +237,14 @@ public class Match
|
|||
|
||||
/**
|
||||
* Fill match to
|
||||
* @param xMatchedTable Match from table, to get line id from selected row
|
||||
* @param xMatchedToTable
|
||||
* @param displayString Match from, to populate xMatchedToTable
|
||||
* @param matchToType Document to match with displayString (MATCH_* constant)
|
||||
* @param sameBPartner
|
||||
* @param sameProduct
|
||||
* @param sameQty
|
||||
* @param matched true for partial or fully match, false for not matched
|
||||
*/
|
||||
public IMiniTable cmd_searchTo(IMiniTable xMatchedTable, IMiniTable xMatchedToTable, String displayString, int matchToType, boolean sameBPartner, boolean sameProduct, boolean sameQty, boolean matched)
|
||||
{
|
||||
|
@ -272,15 +284,17 @@ public class Match
|
|||
/**************************************************************************
|
||||
* Initialize Table access - create SQL, dateColumn.
|
||||
* <br>
|
||||
* The driving table is "hdr", e.g. for hdr.C_BPartner_ID=..
|
||||
* The line table is "lin", e.g. for lin.M_Product_ID=..
|
||||
* The driving table is "hdr", e.g. for hdr.C_BPartner_ID=..<br/>
|
||||
* The line table is "lin", e.g. for lin.M_Product_ID=..<br/>
|
||||
* You use the dateColumn/qtyColumn variable directly as it is table specific.
|
||||
* <br>
|
||||
* The sql is dependent on MatchMode:
|
||||
* - If Matched - all (fully or partially) matched records are listed
|
||||
* The sql is dependent on MatchMode:<br/>
|
||||
* - If Matched - all (fully or partially) matched records are listed<br/>
|
||||
* - If Not Matched - all not fully matched records are listed
|
||||
* @param display (Invoice, Shipment, Order) see MATCH_*
|
||||
* @param matchToType (Invoice, Shipment, Order) see MATCH_*
|
||||
* @param display (Match from - Invoice, Material Receipt, Order) see MATCH_*
|
||||
* @param matchToType (Match to - Invoice, Material Receipt, Order) see MATCH_*
|
||||
* @param matched false for not matched, true for full/partially matched
|
||||
* @param lineMatched
|
||||
*/
|
||||
protected void tableInit (int display, int matchToType, boolean matched, KeyNamePair lineMatched)
|
||||
{
|
||||
|
@ -297,113 +311,54 @@ public class Match
|
|||
}
|
||||
if (display == MATCH_INVOICE)
|
||||
{
|
||||
//invoice matched with material receipt (m_matchinv)
|
||||
m_dateColumn = "hdr.DateInvoiced";
|
||||
m_qtyColumn = "lin.QtyInvoiced";
|
||||
m_sql.append("SELECT hdr.C_Invoice_ID,hdr.DocumentNo, hdr.DateInvoiced, bp.Name,hdr.C_BPartner_ID,"
|
||||
+ " lin.Line,lin.C_InvoiceLine_ID, p.Name,lin.M_Product_ID,"
|
||||
+ " CASE WHEN dt.DocBaseType='APC' THEN lin.QtyInvoiced * -1 ELSE lin.QtyInvoiced END,SUM(NVL(mi.Qty,0)), org.Name, hdr.AD_Org_ID " //JAVIER
|
||||
+ "FROM C_Invoice hdr"
|
||||
+ " INNER JOIN AD_Org org ON (hdr.AD_Org_ID=org.AD_Org_ID)" //JAVIER
|
||||
+ " INNER JOIN C_BPartner bp ON (hdr.C_BPartner_ID=bp.C_BPartner_ID)"
|
||||
+ " INNER JOIN C_InvoiceLine lin ON (hdr.C_Invoice_ID=lin.C_Invoice_ID)"
|
||||
+ " INNER JOIN M_Product p ON (lin.M_Product_ID=p.M_Product_ID)"
|
||||
+ " INNER JOIN C_DocType dt ON (hdr.C_DocType_ID=dt.C_DocType_ID AND dt.DocBaseType IN ('API','APC'))"
|
||||
+ " FULL JOIN M_MatchInv mi ON (lin.C_InvoiceLine_ID=mi.C_InvoiceLine_ID) "
|
||||
+ "WHERE hdr.DocStatus IN ('CO','CL')");
|
||||
m_sql.append(MInvoice.MATCH_TO_RECEIPT_SQL);
|
||||
if (lineMatched!= null && Line_ID > 0 )
|
||||
m_sql.append(" AND mi.M_InOutLine_ID = ").append(Line_ID);
|
||||
|
||||
m_groupBy = " GROUP BY hdr.C_Invoice_ID,hdr.DocumentNo,hdr.DateInvoiced,bp.Name,hdr.C_BPartner_ID,"
|
||||
+ " lin.Line,lin.C_InvoiceLine_ID,p.Name,lin.M_Product_ID,dt.DocBaseType,lin.QtyInvoiced, org.Name, hdr.AD_Org_ID, dt.DocBaseType " //JAVIER
|
||||
+ "HAVING "
|
||||
+ (matched ? "0" : "CASE WHEN dt.DocBaseType='APC' THEN lin.QtyInvoiced * -1 ELSE lin.QtyInvoiced END")
|
||||
+ "<>SUM(NVL(mi.Qty,0))";
|
||||
m_groupBy = matched ? MInvoice.FULL_OR_PARTIALLY_MATCHED_TO_RECEIPT_GROUP_BY : MInvoice.NOT_FULLY_MATCHED_TO_RECEIPT_GROUP_BY;
|
||||
}
|
||||
else if (display == MATCH_ORDER)
|
||||
{
|
||||
//order match with receipt or invoice (m_matchpo)
|
||||
//note that only matchToType == MATCH_SHIPMENT is implemented in UI
|
||||
m_dateColumn = "hdr.DateOrdered";
|
||||
m_qtyColumn = "lin.QtyOrdered";
|
||||
m_sql.append("SELECT hdr.C_Order_ID,hdr.DocumentNo, hdr.DateOrdered, bp.Name,hdr.C_BPartner_ID,"
|
||||
+ " lin.Line,lin.C_OrderLine_ID, p.Name,lin.M_Product_ID,"
|
||||
+ " lin.QtyOrdered,");
|
||||
if (matchToType == MATCH_SHIPMENT)
|
||||
m_sql.append("SUM(CASE WHEN (mo.M_InOutLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END), ");
|
||||
else if (matchToType == MATCH_INVOICE)
|
||||
m_sql.append("SUM(CASE WHEN (mo.C_InvoiceLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END), ");
|
||||
else
|
||||
m_sql.append("SUM(COALESCE(mo.Qty,0)), ");
|
||||
m_sql.append("org.Name, hdr.AD_Org_ID " //JAVIER
|
||||
+ "FROM C_Order hdr"
|
||||
+ " INNER JOIN AD_Org org ON (hdr.AD_Org_ID=org.AD_Org_ID)" //JAVIER
|
||||
+ " INNER JOIN C_BPartner bp ON (hdr.C_BPartner_ID=bp.C_BPartner_ID)"
|
||||
+ " INNER JOIN C_OrderLine lin ON (hdr.C_Order_ID=lin.C_Order_ID)"
|
||||
+ " INNER JOIN M_Product p ON (lin.M_Product_ID=p.M_Product_ID)"
|
||||
+ " INNER JOIN C_DocType dt ON (hdr.C_DocType_ID=dt.C_DocType_ID AND dt.DocBaseType='POO')"
|
||||
+ " FULL JOIN M_MatchPO mo ON (lin.C_OrderLine_ID=mo.C_OrderLine_ID) "
|
||||
+ " WHERE " ) ; //[ 1876972 ] Can't match partially matched PO with an unmatched receipt SOLVED BY BOJANA, AGENDA_GM
|
||||
m_linetype = new StringBuffer();
|
||||
m_linetype.append( matchToType == MATCH_SHIPMENT ? "M_InOutLine_ID" : "C_InvoiceLine_ID") ;
|
||||
if ( matched ) {
|
||||
m_sql.append( " mo." + m_linetype + " IS NOT NULL " ) ;
|
||||
{
|
||||
m_sql.append(matched ? MOrder.FULL_OR_PARTIALLY_MATCHED_TO_RECEIPT : MOrder.NOT_FULLY_MATCHED_TO_RECEIPT);
|
||||
if (lineMatched!= null && Line_ID > 0 )
|
||||
m_sql.append( " AND mo.M_InOutLine_ID = " + Line_ID) ;
|
||||
} else {
|
||||
m_sql.append( " ( mo." + m_linetype + " IS NULL OR "
|
||||
+ " (lin.QtyOrdered <> (SELECT sum(mo1.Qty) AS Qty"
|
||||
+ " FROM m_matchpo mo1 WHERE "
|
||||
+ " mo1.C_ORDERLINE_ID=lin.C_ORDERLINE_ID AND "
|
||||
+ " hdr.C_ORDER_ID=lin.C_ORDER_ID AND "
|
||||
+ " mo1." + m_linetype
|
||||
+ " IS NOT NULL group by mo1.C_ORDERLINE_ID))) " );
|
||||
m_groupBy = matched ? MOrder.FULL_OR_PARTIALLY_MATCHED_TO_RECEIPT_GROUP_BY : MOrder.NOT_FULLY_MATCHED_TO_RECEIPT_GROUP_BY;
|
||||
}
|
||||
m_sql.append( " AND hdr.DocStatus IN ('CO','CL')" );
|
||||
m_groupBy = " GROUP BY hdr.C_Order_ID,hdr.DocumentNo,hdr.DateOrdered,bp.Name,hdr.C_BPartner_ID,"
|
||||
+ " lin.Line,lin.C_OrderLine_ID,p.Name,lin.M_Product_ID,lin.QtyOrdered, org.Name, hdr.AD_Org_ID " //JAVIER
|
||||
+ "HAVING "
|
||||
+ (matched ? "0" : "lin.QtyOrdered");
|
||||
|
||||
if (matchToType == MATCH_SHIPMENT)
|
||||
m_groupBy = m_groupBy + "<>SUM(CASE WHEN (mo.M_InOutLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END) ";
|
||||
else if (matchToType == MATCH_INVOICE)
|
||||
m_groupBy = m_groupBy + "<>SUM(CASE WHEN (mo.C_InvoiceLine_ID IS NOT NULL) THEN COALESCE(mo.Qty,0) ELSE 0 END) ";
|
||||
else
|
||||
m_groupBy = m_groupBy + "<>SUM(COALESCE(mo.Qty,0)) ";
|
||||
{
|
||||
m_sql.append(matched ? MOrder.FULL_OR_PARTIALLY_MATCHED_TO_INVOICE : MOrder.NOT_FULLY_MATCHED_TO_INVOICE);
|
||||
if (lineMatched!= null && Line_ID > 0 )
|
||||
m_sql.append( " AND mo.C_InvoiceLine_ID = " + Line_ID) ;
|
||||
m_groupBy = matched ? MOrder.FULL_OR_PARTIALLY_MATCHED_TO_INVOICE_GROUP_BY : MOrder.NOT_FULLY_MATCHED_TO_INVOICE_GROUP_BY;
|
||||
}
|
||||
}
|
||||
else // Shipment
|
||||
{
|
||||
//receipt match with order (m_matchpo) or invoice (m_matchinv)
|
||||
m_dateColumn = "hdr.MovementDate";
|
||||
m_qtyColumn = "lin.MovementQty";
|
||||
m_sql.append("SELECT hdr.M_InOut_ID,hdr.DocumentNo, hdr.MovementDate, bp.Name,hdr.C_BPartner_ID,"
|
||||
+ " lin.Line,lin.M_InOutLine_ID, p.Name,lin.M_Product_ID,"
|
||||
+ " CASE WHEN (dt.DocBaseType='MMS' AND hdr.issotrx='N') THEN lin.MovementQty * -1 ELSE lin.MovementQty END,");
|
||||
if (matchToType == MATCH_ORDER)
|
||||
m_sql.append("SUM(CASE WHEN m.M_InOutLine_ID IS NOT NULL THEN COALESCE(m.Qty,0) ELSE 0 END),");
|
||||
m_sql.append(matched ? MInOut.FULL_OR_PARTIALLY_MATCHED_TO_ORDER : MInOut.NOT_FULLY_MATCHED_TO_ORDER);
|
||||
else
|
||||
m_sql.append("SUM(COALESCE(m.Qty,0)),");
|
||||
m_sql.append("org.Name, hdr.AD_Org_ID " //JAVIER
|
||||
+ "FROM M_InOut hdr"
|
||||
+ " INNER JOIN AD_Org org ON (hdr.AD_Org_ID=org.AD_Org_ID)" //JAVIER
|
||||
+ " INNER JOIN C_BPartner bp ON (hdr.C_BPartner_ID=bp.C_BPartner_ID)"
|
||||
+ " INNER JOIN M_InOutLine lin ON (hdr.M_InOut_ID=lin.M_InOut_ID)"
|
||||
+ " INNER JOIN M_Product p ON (lin.M_Product_ID=p.M_Product_ID)"
|
||||
+ " INNER JOIN C_DocType dt ON (hdr.C_DocType_ID = dt.C_DocType_ID AND (dt.DocBaseType='MMR' OR (dt.DocBaseType='MMS' AND hdr.isSOTrx ='N')))"
|
||||
+ " FULL JOIN ")
|
||||
.append(matchToType == MATCH_ORDER ? "M_MatchPO" : "M_MatchInv")
|
||||
.append(" m ON (lin.M_InOutLine_ID=m.M_InOutLine_ID) "
|
||||
+ "WHERE hdr.DocStatus IN ('CO','CL')");
|
||||
m_sql.append(matched ? MInOut.FULL_OR_PARTIALLY_MATCHED_TO_INVOICE : MInOut.NOT_FULLY_MATCHED_TO_INVOICE);
|
||||
if ( matchToType == MATCH_INVOICE && lineMatched != null && Line_ID > 0 )
|
||||
m_sql.append(" AND m.C_InvoiceLine_ID = ").append(Line_ID);
|
||||
if ( matchToType == MATCH_ORDER && lineMatched != null && Line_ID > 0 )
|
||||
m_sql.append(" AND m.C_OrderLine_ID = ").append(Line_ID);
|
||||
|
||||
m_groupBy = " GROUP BY hdr.M_InOut_ID,hdr.DocumentNo,hdr.MovementDate,bp.Name,hdr.C_BPartner_ID,"
|
||||
+ " lin.Line,lin.M_InOutLine_ID,p.Name,lin.M_Product_ID,lin.MovementQty, org.Name, hdr.AD_Org_ID, dt.DocBaseType, hdr.IsSOTrx " //JAVIER
|
||||
+ "HAVING "
|
||||
+ (matched ? "0" : "CASE WHEN (dt.DocBaseType='MMS' AND hdr.issotrx='N') THEN lin.MovementQty * -1 ELSE lin.MovementQty END");
|
||||
if (matchToType == MATCH_ORDER)
|
||||
m_groupBy = m_groupBy + "<>SUM(CASE WHEN m.M_InOutLine_ID IS NOT NULL THEN COALESCE(m.Qty,0) ELSE 0 END)";
|
||||
m_groupBy = matched ? MInOut.FULL_OR_PARTIALLY_MATCHED_TO_ORDER_GROUP_BY : MInOut.NOT_FULLY_MATCHED_TO_ORDER_GROUP_BY;
|
||||
else
|
||||
m_groupBy = m_groupBy + "<>SUM(COALESCE(m.Qty,0))";
|
||||
m_groupBy = matched ? MInOut.FULL_OR_PARTIALLY_MATCHED_TO_INVOICE_GROUP_BY : MInOut.NOT_FULLY_MATCHED_TO_INVOICE_GROUP_BY;
|
||||
}
|
||||
} // tableInit
|
||||
|
||||
|
@ -459,132 +414,15 @@ public class Match
|
|||
MInOutLine sLine = new MInOutLine (Env.getCtx(), M_InOutLine_ID, trxName);
|
||||
if (invoice) // Shipment - Invoice
|
||||
{
|
||||
// Update Invoice Line
|
||||
MInvoiceLine iLine = new MInvoiceLine (Env.getCtx(), Line_ID, trxName);
|
||||
iLine.setM_InOutLine_ID(M_InOutLine_ID);
|
||||
if (sLine.getC_OrderLine_ID() != 0)
|
||||
iLine.setC_OrderLine_ID(sLine.getC_OrderLine_ID());
|
||||
iLine.saveEx();
|
||||
// Create Shipment - Invoice Link
|
||||
if (iLine.getM_Product_ID() != 0)
|
||||
{
|
||||
MMatchInv match = new MMatchInv (iLine, null, qty);
|
||||
match.setM_InOutLine_ID(M_InOutLine_ID);
|
||||
match.saveEx();
|
||||
success = true;
|
||||
if (MClient.isClientAccountingImmediate()) {
|
||||
String ignoreError = DocumentEngine.postImmediate(match.getCtx(), match.getAD_Client_ID(), match.get_Table_ID(), match.get_ID(), true, match.get_TrxName());
|
||||
if (ignoreError != null) {
|
||||
log.info(ignoreError);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
success = true;
|
||||
// Create PO - Invoice Link = corrects PO
|
||||
if (iLine.getM_Product_ID() != 0)
|
||||
{
|
||||
BigDecimal matchedQty = DB.getSQLValueBD(iLine.get_TrxName(), "SELECT Coalesce(SUM(Qty),0) FROM M_MatchPO WHERE C_InvoiceLine_ID=?" , iLine.getC_InvoiceLine_ID());
|
||||
if (matchedQty.add(qty).compareTo(iLine.getQtyInvoiced()) <= 0)
|
||||
{
|
||||
MMatchPO matchPO = MMatchPO.create(iLine, sLine, null, qty);
|
||||
if (matchPO != null)
|
||||
{
|
||||
matchPO.saveEx();
|
||||
if (MClient.isClientAccountingImmediate()) {
|
||||
String ignoreError = DocumentEngine.postImmediate(matchPO.getCtx(), matchPO.getAD_Client_ID(), matchPO.get_Table_ID(), matchPO.get_ID(), true, matchPO.get_TrxName());
|
||||
if (ignoreError != null)
|
||||
log.info(ignoreError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
success = sLine.matchToInvoiceLine(Line_ID, qty);
|
||||
}
|
||||
else // Shipment - Order
|
||||
{
|
||||
// Update Order Line
|
||||
MOrderLine oLine = new MOrderLine(Env.getCtx(), Line_ID, trxName);
|
||||
BigDecimal storageReservationToUpdate = null;
|
||||
if (oLine.get_ID() != 0) // other in MInOut.completeIt
|
||||
{
|
||||
storageReservationToUpdate = oLine.getQtyReserved();
|
||||
oLine.setQtyReserved(oLine.getQtyReserved().subtract(qty));
|
||||
if (oLine.getQtyReserved().signum() == -1)
|
||||
oLine.setQtyReserved(Env.ZERO);
|
||||
else if (oLine.getQtyDelivered().compareTo(oLine.getQtyOrdered()) > 0)
|
||||
oLine.setQtyReserved(Env.ZERO);
|
||||
oLine.saveEx();
|
||||
storageReservationToUpdate = storageReservationToUpdate.subtract(oLine.getQtyReserved());
|
||||
}
|
||||
|
||||
// Update Shipment Line
|
||||
BigDecimal toDeliver = oLine.getQtyOrdered().subtract(oLine.getQtyDelivered());
|
||||
if (toDeliver.signum() < 0)
|
||||
toDeliver = Env.ZERO;
|
||||
if (sLine.getMovementQty().compareTo(toDeliver) <= 0)
|
||||
{
|
||||
sLine.setC_OrderLine_ID(Line_ID);
|
||||
sLine.saveEx();
|
||||
}
|
||||
else if (sLine.getC_OrderLine_ID() != 0)
|
||||
{
|
||||
sLine.setC_OrderLine_ID(0);
|
||||
sLine.saveEx();
|
||||
}
|
||||
|
||||
// Create PO - Shipment Link
|
||||
if (sLine.getM_Product_ID() != 0)
|
||||
{
|
||||
MMatchPO match = getOrCreate(Line_ID, qty, sLine, trxName);
|
||||
match.setC_OrderLine_ID(Line_ID);
|
||||
if (!match.save())
|
||||
{
|
||||
String msg = "PO Match not created: " + match;
|
||||
ValueNamePair error = CLogger.retrieveError();
|
||||
if (error != null)
|
||||
{
|
||||
msg = msg + ". " + error.getName();
|
||||
}
|
||||
throw new AdempiereException(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = true;
|
||||
// Correct Ordered Qty for Stocked Products (see MOrder.reserveStock / MInOut.processIt)
|
||||
if (oLine.get_ID() > 0 && oLine.getM_Product_ID() > 0 && oLine.getProduct().isStocked() && storageReservationToUpdate != null) {
|
||||
IReservationTracer tracer = null;
|
||||
IReservationTracerFactory factory = Core.getReservationTracerFactory();
|
||||
if (factory != null) {
|
||||
tracer = factory.newTracer(sLine.getParent().getC_DocType_ID(), sLine.getParent().getDocumentNo(), sLine.getLine(),
|
||||
sLine.get_Table_ID(), sLine.get_ID(), oLine.getM_Warehouse_ID(),
|
||||
oLine.getM_Product_ID(), oLine.getM_AttributeSetInstance_ID(), oLine.getParent().isSOTrx(),
|
||||
trxName);
|
||||
}
|
||||
success = MStorageReservation.add (Env.getCtx(), oLine.getM_Warehouse_ID(),
|
||||
oLine.getM_Product_ID(),
|
||||
oLine.getM_AttributeSetInstance_ID(),
|
||||
storageReservationToUpdate.negate(), oLine.getParent().isSOTrx(), trxName, tracer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
success = true;
|
||||
success = sLine.matchToOrderLine(Line_ID, qty);
|
||||
}
|
||||
return success;
|
||||
} // createMatchRecord
|
||||
|
||||
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param trxName
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
* Contributors: *
|
||||
* - hengsin *
|
||||
**********************************************************************/
|
||||
package org.idempiere.test.ui;
|
||||
package org.compiere.minigrid;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
@ -34,15 +34,15 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.compiere.minigrid.ColumnInfo;
|
||||
import org.compiere.minigrid.IDColumn;
|
||||
import org.compiere.minigrid.IMiniTable;
|
||||
import org.compiere.apps.form.Match;
|
||||
import org.compiere.model.MRole;
|
||||
import org.compiere.model.PO;
|
||||
import org.compiere.util.KeyNamePair;
|
||||
|
||||
/**
|
||||
*
|
||||
* Headless implmentation of {@link IMiniTable}.<br/>
|
||||
* This support the use/test of some UI API (for e.g {@link Match} in headless environment (for e.g unit test).
|
||||
* @author hengsin
|
||||
*
|
||||
*/
|
||||
|
@ -62,6 +62,13 @@ public class MiniTableImpl implements IMiniTable {
|
|||
public MiniTableImpl() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param layout
|
||||
*/
|
||||
public MiniTableImpl(ColumnInfo[] layout) {
|
||||
prepareTable(layout, null, null, false, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return false;
|
|
@ -23,7 +23,7 @@
|
|||
* - hengsin *
|
||||
**********************************************************************/
|
||||
|
||||
package org.idempiere.test.ui;
|
||||
package org.compiere.minigrid;
|
||||
|
||||
/**
|
||||
*
|
|
@ -255,7 +255,7 @@
|
|||
<setEntry value="org.eclipse.equinox.servletbridge@default:default"/>
|
||||
<setEntry value="org.eclipse.equinox.simpleconfigurator.manipulator@default:default"/>
|
||||
<setEntry value="org.eclipse.equinox.simpleconfigurator@1:true"/>
|
||||
<setEntry value="org.eclipse.jdt.core.compiler.batch*3.33.0.v20230218-1114@default:default"/>
|
||||
<setEntry value="org.eclipse.jdt.core.compiler.batch@default:default"/>
|
||||
<setEntry value="org.eclipse.jdt.core@default:default"/>
|
||||
<setEntry value="org.eclipse.jdt.junit.runtime@default:default"/>
|
||||
<setEntry value="org.eclipse.jdt.junit5.runtime@default:default"/>
|
||||
|
|
|
@ -33,6 +33,7 @@ import java.math.BigDecimal;
|
|||
import org.compiere.apps.form.Match;
|
||||
import org.compiere.minigrid.ColumnInfo;
|
||||
import org.compiere.minigrid.IDColumn;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.MBPartner;
|
||||
import org.compiere.model.MDocType;
|
||||
import org.compiere.model.MInOut;
|
||||
|
@ -55,7 +56,6 @@ import org.compiere.util.Env;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
@ -571,11 +571,9 @@ public class MatchPOTest extends AbstractTestCase {
|
|||
|
||||
Match match = new Match();
|
||||
match.setTrxName(getTrxName());
|
||||
MiniTableImpl fromTable = new MiniTableImpl();
|
||||
MiniTableImpl toTable = new MiniTableImpl();
|
||||
ColumnInfo[] layout = match.getColumnLayout();
|
||||
fromTable.prepareTable(layout, null, null, false, null);
|
||||
toTable.prepareTable(layout, null, null, false, null);
|
||||
MiniTableImpl fromTable = new MiniTableImpl(layout);
|
||||
MiniTableImpl toTable = new MiniTableImpl(layout);
|
||||
match.cmd_search(fromTable, Match.MATCH_SHIPMENT, match.getMatchTypeText(Match.MATCH_ORDER), product.get_ID(), bpartner.get_ID(), null, null, false);
|
||||
assertTrue(fromTable.getRowCount()>0, "Unexpected number of records for not matched Material Receipt: " + fromTable.getRowCount());
|
||||
int selectedRow = -1;
|
||||
|
@ -759,11 +757,9 @@ public class MatchPOTest extends AbstractTestCase {
|
|||
|
||||
Match match = new Match();
|
||||
match.setTrxName(getTrxName());
|
||||
MiniTableImpl fromTable = new MiniTableImpl();
|
||||
MiniTableImpl toTable = new MiniTableImpl();
|
||||
ColumnInfo[] layout = match.getColumnLayout();
|
||||
fromTable.prepareTable(layout, null, null, false, null);
|
||||
toTable.prepareTable(layout, null, null, false, null);
|
||||
MiniTableImpl fromTable = new MiniTableImpl(layout);
|
||||
MiniTableImpl toTable = new MiniTableImpl(layout);
|
||||
match.cmd_search(fromTable, Match.MATCH_SHIPMENT, match.getMatchTypeText(Match.MATCH_ORDER), product.get_ID(), bpartner.get_ID(), null, null, false);
|
||||
assertTrue(fromTable.getRowCount()>0, "Unexpected number of records for not matched Material Receipt: " + fromTable.getRowCount());
|
||||
int selectedRow = -1;
|
||||
|
@ -866,4 +862,102 @@ public class MatchPOTest extends AbstractTestCase {
|
|||
newOnHand = MStorageOnHand.getQtyOnHand(product.get_ID(), getM_Warehouse_ID(), 0, getTrxName()).intValue();
|
||||
assertEquals(initialOnHand+1, newOnHand, "Unexpected qty on hand value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoidMatchOrder() {
|
||||
MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id); // Tree Farm Inc.
|
||||
MProduct product = MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.ELM.id); // Elm Tree
|
||||
|
||||
int initialOnHand = MStorageOnHand.getQtyOnHand(product.get_ID(), getM_Warehouse_ID(), 0, getTrxName()).intValue();
|
||||
int initialOnOrdered = MStorageReservation.getQty(product.get_ID(), getM_Warehouse_ID(), 0, false, getTrxName()).intValue();
|
||||
|
||||
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
|
||||
order.setBPartner(bpartner);
|
||||
order.setIsSOTrx(false);
|
||||
order.setC_DocTypeTarget_ID();
|
||||
order.setDocStatus(DocAction.STATUS_Drafted);
|
||||
order.setDocAction(DocAction.ACTION_Complete);
|
||||
order.saveEx();
|
||||
|
||||
BigDecimal orderQty = new BigDecimal("1");
|
||||
MOrderLine orderLine = new MOrderLine(order);
|
||||
orderLine.setLine(10);
|
||||
orderLine.setProduct(product);
|
||||
orderLine.setQty(orderQty);
|
||||
orderLine.saveEx();
|
||||
|
||||
//complete order
|
||||
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
order.load(getTrxName());
|
||||
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
int newOnOrdered = MStorageReservation.getQty(product.get_ID(), getM_Warehouse_ID(), 0, false, getTrxName()).intValue();
|
||||
assertEquals(initialOnOrdered+1, newOnOrdered, "Unexpected qty on ordered value");
|
||||
|
||||
//create and complete material receipt
|
||||
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered()); // MM Receipt
|
||||
receipt.saveEx();
|
||||
|
||||
MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID());
|
||||
int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID();
|
||||
|
||||
BigDecimal receiptQty = new BigDecimal("1");
|
||||
MInOutLine receiptLine = new MInOutLine(receipt);
|
||||
receiptLine.setOrderLine(orderLine, M_Locator_ID, receiptQty);
|
||||
receiptLine.setLine(10);
|
||||
receiptLine.setQty(receiptQty);
|
||||
receiptLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
receipt.load(getTrxName());
|
||||
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(0, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(1, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
newOnOrdered = MStorageReservation.getQty(product.get_ID(), getM_Warehouse_ID(), 0, false, getTrxName()).intValue();
|
||||
assertEquals(initialOnOrdered, newOnOrdered, "Unexpected qty on ordered value");
|
||||
int newOnHand = MStorageOnHand.getQtyOnHand(product.get_ID(), getM_Warehouse_ID(), 0, getTrxName()).intValue();
|
||||
assertEquals(initialOnHand+1, newOnHand, "Unexpected qty on hand value");
|
||||
|
||||
MMatchPO[] matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName());
|
||||
assertEquals(1, matchPOs.length, "Unexpected number of MatchPO for order line");
|
||||
|
||||
//void order
|
||||
order.setDocAction(DocAction.ACTION_Void);
|
||||
order.saveEx();
|
||||
info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Void);
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
order.load(getTrxName());
|
||||
assertEquals(DocAction.STATUS_Voided, order.getDocStatus());
|
||||
|
||||
//check material receipt line is available for matching in Matching form
|
||||
Match match = new Match();
|
||||
match.setTrxName(getTrxName());
|
||||
ColumnInfo[] layout = match.getColumnLayout();
|
||||
MiniTableImpl fromTable = new MiniTableImpl(layout);
|
||||
match.cmd_search(fromTable, Match.MATCH_SHIPMENT, match.getMatchTypeText(Match.MATCH_ORDER), product.get_ID(), bpartner.get_ID(), null, null, false);
|
||||
assertTrue(fromTable.getRowCount()>0, "Unexpected number of records for not matched Material Receipt: " + fromTable.getRowCount());
|
||||
int selectedRow = -1;
|
||||
for(int i = 0; i < fromTable.getRowCount(); i++) {
|
||||
String docNo = (String)fromTable.getValueAt(i, Match.I_DocumentNo);
|
||||
if (receipt.getDocumentNo().equals(docNo)) {
|
||||
int matched = ((Number)fromTable.getValueAt(i, Match.I_MATCHED)).intValue();
|
||||
assertEquals(0, matched, "Unexpected matched qty for Material Receipt line");
|
||||
int qty = ((Number)fromTable.getValueAt(i, Match.I_QTY)).intValue();
|
||||
assertEquals(receiptLine.getMovementQty().intValue(), qty, "Unexpected qty for Material Receipt line");
|
||||
selectedRow = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(selectedRow >= 0, "Can't find not matched Material Receipt line");
|
||||
|
||||
receiptLine.load(getTrxName());
|
||||
assertEquals(0, receiptLine.getC_OrderLine_ID(), "Material receipt line: order line not clear after void of purchase order");
|
||||
|
||||
receipt.load(getTrxName());
|
||||
assertEquals(0, receipt.getC_Order_ID(), "Material receipt: order not clear after void of purchase order");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.Vector;
|
|||
|
||||
import org.compiere.apps.form.Allocation;
|
||||
import org.compiere.minigrid.IMiniTable;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.MAllocationHdr;
|
||||
import org.compiere.model.MBPartner;
|
||||
import org.compiere.model.MBankAccount;
|
||||
|
@ -53,7 +54,6 @@ import org.compiere.util.TimeUtil;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,8 +31,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
import java.util.Vector;
|
||||
|
||||
import org.compiere.apps.form.Charge;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,6 +34,7 @@ import java.sql.Timestamp;
|
|||
import java.util.Vector;
|
||||
|
||||
import org.compiere.grid.CreateFromDepositBatch;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.GridTab;
|
||||
import org.compiere.model.GridWindow;
|
||||
import org.compiere.model.MDepositBatch;
|
||||
|
@ -50,7 +51,6 @@ import org.compiere.util.TimeUtil;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.ArrayList;
|
|||
import java.util.Vector;
|
||||
|
||||
import org.compiere.grid.CreateFromInvoice;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.GridTab;
|
||||
import org.compiere.model.GridWindow;
|
||||
import org.compiere.model.MBPartner;
|
||||
|
@ -56,7 +57,6 @@ import org.compiere.util.TimeUtil;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.List;
|
|||
import java.util.Vector;
|
||||
|
||||
import org.compiere.grid.CreateFromPackageShipment;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.GridTab;
|
||||
import org.compiere.model.GridWindow;
|
||||
import org.compiere.model.MBPartner;
|
||||
|
@ -59,7 +60,6 @@ import org.compiere.util.TimeUtil;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,6 +34,7 @@ import java.sql.Timestamp;
|
|||
import java.util.Vector;
|
||||
|
||||
import org.compiere.grid.CreateFromRMA;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.GridTab;
|
||||
import org.compiere.model.GridWindow;
|
||||
import org.compiere.model.MBPartner;
|
||||
|
@ -54,7 +55,6 @@ import org.compiere.util.TimeUtil;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -35,6 +35,7 @@ import java.util.ArrayList;
|
|||
import java.util.Vector;
|
||||
|
||||
import org.compiere.grid.CreateFromShipment;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.GridTab;
|
||||
import org.compiere.model.GridWindow;
|
||||
import org.compiere.model.MBPartner;
|
||||
|
@ -59,7 +60,6 @@ import org.compiere.util.TimeUtil;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,6 +34,7 @@ import java.sql.Timestamp;
|
|||
import java.util.Vector;
|
||||
|
||||
import org.compiere.grid.CreateFromStatement;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.GridTab;
|
||||
import org.compiere.model.GridWindow;
|
||||
import org.compiere.model.MBankStatement;
|
||||
|
@ -50,7 +51,6 @@ import org.compiere.util.TimeUtil;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.List;
|
|||
import java.util.Vector;
|
||||
|
||||
import org.compiere.apps.form.FactReconcile;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.MBankStatement;
|
||||
import org.compiere.model.MBankStatementLine;
|
||||
import org.compiere.model.MClientInfo;
|
||||
|
@ -52,7 +53,6 @@ import org.compiere.util.Util;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,21 +33,25 @@ import java.math.BigDecimal;
|
|||
import org.compiere.apps.form.Match;
|
||||
import org.compiere.minigrid.ColumnInfo;
|
||||
import org.compiere.minigrid.IDColumn;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.MBPartner;
|
||||
import org.compiere.model.MInOut;
|
||||
import org.compiere.model.MInOutLine;
|
||||
import org.compiere.model.MInvoice;
|
||||
import org.compiere.model.MInvoiceLine;
|
||||
import org.compiere.model.MMatchInv;
|
||||
import org.compiere.model.MMatchPO;
|
||||
import org.compiere.model.MOrder;
|
||||
import org.compiere.model.MOrderLine;
|
||||
import org.compiere.model.MProduct;
|
||||
import org.compiere.model.MWarehouse;
|
||||
import org.compiere.process.DocAction;
|
||||
import org.compiere.process.DocumentEngine;
|
||||
import org.compiere.process.ProcessInfo;
|
||||
import org.compiere.util.Env;
|
||||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class MatchFormTest extends AbstractTestCase {
|
||||
|
@ -163,4 +167,277 @@ public class MatchFormTest extends AbstractTestCase {
|
|||
MMatchPO[] matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName());
|
||||
assertEquals(1, matchPOs.length, "Unexpected number of MatchPO for order line");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchInvoiceToReceipt() {
|
||||
MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id);
|
||||
MProduct product = MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id);
|
||||
|
||||
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
|
||||
order.setBPartner(bpartner);
|
||||
order.setIsSOTrx(false);
|
||||
order.setC_DocTypeTarget_ID();
|
||||
order.setDocStatus(DocAction.STATUS_Drafted);
|
||||
order.setDocAction(DocAction.ACTION_Complete);
|
||||
order.saveEx();
|
||||
|
||||
BigDecimal orderQty = new BigDecimal("1");
|
||||
MOrderLine orderLine = new MOrderLine(order);
|
||||
orderLine.setLine(10);
|
||||
orderLine.setProduct(product);
|
||||
orderLine.setQty(orderQty);
|
||||
orderLine.saveEx();
|
||||
|
||||
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
order.load(getTrxName());
|
||||
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(0, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
|
||||
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered()); // MM Receipt
|
||||
receipt.saveEx();
|
||||
|
||||
MInOutLine receiptLine = new MInOutLine(receipt);
|
||||
receiptLine.setC_OrderLine_ID(orderLine.get_ID());
|
||||
receiptLine.setLine(10);
|
||||
receiptLine.setProduct(product);
|
||||
receiptLine.setQty(BigDecimal.ONE);
|
||||
MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID());
|
||||
int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID();
|
||||
receiptLine.setM_Locator_ID(M_Locator_ID);
|
||||
receiptLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||
receipt.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||
|
||||
if (!receipt.isPosted()) {
|
||||
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName());
|
||||
assertTrue(error == null);
|
||||
}
|
||||
receipt.load(getTrxName());
|
||||
assertTrue(receipt.isPosted());
|
||||
|
||||
MInvoice invoice = new MInvoice(Env.getCtx(), 0, getTrxName());
|
||||
invoice.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.AP_INVOICE.id);
|
||||
invoice.setBPartner(bpartner);
|
||||
invoice.setDateAcct(order.getDateAcct());
|
||||
invoice.setDateInvoiced(order.getDateOrdered());
|
||||
invoice.setDocStatus(DocAction.STATUS_Drafted);
|
||||
invoice.setDocAction(DocAction.ACTION_Complete);
|
||||
invoice.saveEx();
|
||||
|
||||
BigDecimal qtyInvoiced = new BigDecimal(1);
|
||||
MInvoiceLine invoiceLine = new MInvoiceLine(invoice);
|
||||
invoiceLine.setProduct(product);
|
||||
invoiceLine.setLine(10);
|
||||
invoiceLine.setQty(qtyInvoiced);
|
||||
invoiceLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete);
|
||||
invoice.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus());
|
||||
|
||||
if (!invoice.isPosted()) {
|
||||
String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName());
|
||||
assertTrue(error == null);
|
||||
}
|
||||
invoice.load(getTrxName());
|
||||
assertTrue(invoice.isPosted());
|
||||
|
||||
Match match = new Match();
|
||||
match.setTrxName(getTrxName());
|
||||
ColumnInfo[] columnLayout = match.getColumnLayout();
|
||||
MiniTableImpl fromTable = new MiniTableImpl(columnLayout);
|
||||
MiniTableImpl toTable = new MiniTableImpl(columnLayout);
|
||||
|
||||
//load not match invoice
|
||||
match.cmd_search(fromTable, Match.MATCH_INVOICE, match.getMatchTypeText(Match.MATCH_SHIPMENT), product.get_ID(), bpartner.get_ID(), null, null, false);
|
||||
assertTrue(fromTable.getRowCount()>0, "Unexpected number of records for not matched vendor invoice: " + fromTable.getRowCount());
|
||||
int selectedRow = -1;
|
||||
for(int i = 0; i < fromTable.getRowCount(); i++) {
|
||||
String docNo = (String)fromTable.getValueAt(i, Match.I_DocumentNo);
|
||||
if (invoice.getDocumentNo().equals(docNo)) {
|
||||
int matched = ((Number)fromTable.getValueAt(i, Match.I_MATCHED)).intValue();
|
||||
assertEquals(0, matched, "Unexpected matched qty for purchase order line");
|
||||
int qty = ((Number)fromTable.getValueAt(i, Match.I_QTY)).intValue() ;
|
||||
assertEquals(orderLine.getQtyOrdered().intValue(), qty, "Unexpected qty for vendor invoice line");
|
||||
selectedRow = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(selectedRow >= 0, "Can't find not matched vendor invoice line");
|
||||
fromTable.setSelectedRow(selectedRow);
|
||||
|
||||
//load not matched receipt
|
||||
match.cmd_searchTo(fromTable, toTable, match.getMatchTypeText(Match.MATCH_SHIPMENT), Match.MATCH_INVOICE, true, true, true, false);
|
||||
assertTrue(toTable.getRowCount()>0, "Unexpected number of records for not matched material receipt Line: " + fromTable.getRowCount());
|
||||
int selectedReceiptRow = -1;
|
||||
for(int i = 0; i < toTable.getRowCount(); i++) {
|
||||
String docNo = (String)toTable.getValueAt(i, Match.I_DocumentNo);
|
||||
if (receipt.getDocumentNo().equals(docNo)) {
|
||||
int matched = ((Number)toTable.getValueAt(i, Match.I_MATCHED)).intValue();
|
||||
assertEquals(0, matched, "Unexpected matched qty for material receipt line");
|
||||
int qty = ((Number)toTable.getValueAt(i, Match.I_QTY)).intValue();
|
||||
assertEquals(orderLine.getQtyOrdered().intValue(), qty, "Unexpected qty for material receipt line");
|
||||
selectedReceiptRow = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(selectedReceiptRow >= 0, "Can't find not matched material receipt line");
|
||||
|
||||
//select and process matching
|
||||
IDColumn idColumn = (IDColumn)toTable.getValueAt(selectedReceiptRow, Match.I_ID);
|
||||
idColumn.setSelected(true);
|
||||
match.cmd_process(fromTable, toTable, Match.MODE_NOTMATCHED, Match.MATCH_INVOICE, match.getMatchTypeText(Match.MATCH_SHIPMENT), new BigDecimal(1));
|
||||
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyInvoiced().intValue(), "Unexpected order line qty invoiced value");
|
||||
receiptLine.load(getTrxName());
|
||||
MMatchInv[] matchInvs = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName());
|
||||
assertEquals(1, matchInvs.length, "Unexpected number of MatchInv for invoice line");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchReceiptToInvoice() {
|
||||
MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id);
|
||||
MProduct product = MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id);
|
||||
|
||||
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
|
||||
order.setBPartner(bpartner);
|
||||
order.setIsSOTrx(false);
|
||||
order.setC_DocTypeTarget_ID();
|
||||
order.setDocStatus(DocAction.STATUS_Drafted);
|
||||
order.setDocAction(DocAction.ACTION_Complete);
|
||||
order.saveEx();
|
||||
|
||||
BigDecimal orderQty = new BigDecimal("1");
|
||||
MOrderLine orderLine = new MOrderLine(order);
|
||||
orderLine.setLine(10);
|
||||
orderLine.setProduct(product);
|
||||
orderLine.setQty(orderQty);
|
||||
orderLine.saveEx();
|
||||
|
||||
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
order.load(getTrxName());
|
||||
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(0, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
|
||||
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered()); // MM Receipt
|
||||
receipt.saveEx();
|
||||
|
||||
MInOutLine receiptLine = new MInOutLine(receipt);
|
||||
receiptLine.setC_OrderLine_ID(orderLine.get_ID());
|
||||
receiptLine.setLine(10);
|
||||
receiptLine.setProduct(product);
|
||||
receiptLine.setQty(BigDecimal.ONE);
|
||||
MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID());
|
||||
int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID();
|
||||
receiptLine.setM_Locator_ID(M_Locator_ID);
|
||||
receiptLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||
receipt.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||
|
||||
if (!receipt.isPosted()) {
|
||||
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName());
|
||||
assertTrue(error == null);
|
||||
}
|
||||
receipt.load(getTrxName());
|
||||
assertTrue(receipt.isPosted());
|
||||
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(0, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(1, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
assertEquals(0, orderLine.getQtyInvoiced().intValue(), "Unexpected order line qty invoiced value");
|
||||
|
||||
MInvoice invoice = new MInvoice(Env.getCtx(), 0, getTrxName());
|
||||
invoice.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.AP_INVOICE.id);
|
||||
invoice.setBPartner(bpartner);
|
||||
invoice.setDateAcct(order.getDateAcct());
|
||||
invoice.setDateInvoiced(order.getDateOrdered());
|
||||
invoice.setDocStatus(DocAction.STATUS_Drafted);
|
||||
invoice.setDocAction(DocAction.ACTION_Complete);
|
||||
invoice.saveEx();
|
||||
|
||||
BigDecimal qtyInvoiced = new BigDecimal(1);
|
||||
MInvoiceLine invoiceLine = new MInvoiceLine(invoice);
|
||||
invoiceLine.setProduct(product);
|
||||
invoiceLine.setLine(10);
|
||||
invoiceLine.setQty(qtyInvoiced);
|
||||
invoiceLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete);
|
||||
invoice.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus());
|
||||
|
||||
if (!invoice.isPosted()) {
|
||||
String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName());
|
||||
assertTrue(error == null);
|
||||
}
|
||||
invoice.load(getTrxName());
|
||||
assertTrue(invoice.isPosted());
|
||||
|
||||
Match match = new Match();
|
||||
match.setTrxName(getTrxName());
|
||||
ColumnInfo[] columnLayout = match.getColumnLayout();
|
||||
MiniTableImpl fromTable = new MiniTableImpl(columnLayout);
|
||||
MiniTableImpl toTable = new MiniTableImpl(columnLayout);
|
||||
|
||||
//load not match receipt
|
||||
match.cmd_search(fromTable, Match.MATCH_SHIPMENT, match.getMatchTypeText(Match.MATCH_INVOICE), product.get_ID(), bpartner.get_ID(), null, null, false);
|
||||
assertTrue(fromTable.getRowCount()>0, "Unexpected number of records for not matched material receipt: " + fromTable.getRowCount());
|
||||
int selectedRow = -1;
|
||||
for(int i = 0; i < fromTable.getRowCount(); i++) {
|
||||
String docNo = (String)fromTable.getValueAt(i, Match.I_DocumentNo);
|
||||
if (receipt.getDocumentNo().equals(docNo)) {
|
||||
int matched = ((Number)fromTable.getValueAt(i, Match.I_MATCHED)).intValue();
|
||||
assertEquals(0, matched, "Unexpected matched qty for material receipt line");
|
||||
int qty = ((Number)fromTable.getValueAt(i, Match.I_QTY)).intValue() ;
|
||||
assertEquals(orderLine.getQtyOrdered().intValue(), qty, "Unexpected qty for material receipt line");
|
||||
selectedRow = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(selectedRow >= 0, "Can't find not matched material receipt line");
|
||||
fromTable.setSelectedRow(selectedRow);
|
||||
|
||||
//load not matched vendor invoice
|
||||
match.cmd_searchTo(fromTable, toTable, match.getMatchTypeText(Match.MATCH_INVOICE), Match.MATCH_SHIPMENT, true, true, true, false);
|
||||
assertTrue(toTable.getRowCount()>0, "Unexpected number of records for not matched vendor invoice Line: " + fromTable.getRowCount());
|
||||
int selectedInvoiceRow = -1;
|
||||
for(int i = 0; i < toTable.getRowCount(); i++) {
|
||||
String docNo = (String)toTable.getValueAt(i, Match.I_DocumentNo);
|
||||
if (invoice.getDocumentNo().equals(docNo)) {
|
||||
int matched = ((Number)toTable.getValueAt(i, Match.I_MATCHED)).intValue();
|
||||
assertEquals(0, matched, "Unexpected matched qty for vendor invoice line");
|
||||
int qty = ((Number)toTable.getValueAt(i, Match.I_QTY)).intValue();
|
||||
assertEquals(orderLine.getQtyOrdered().intValue(), qty, "Unexpected qty for vendor invoice line");
|
||||
selectedInvoiceRow = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(selectedInvoiceRow >= 0, "Can't find not matched vendor invoice line");
|
||||
|
||||
//select and process matching
|
||||
IDColumn idColumn = (IDColumn)toTable.getValueAt(selectedInvoiceRow, Match.I_ID);
|
||||
idColumn.setSelected(true);
|
||||
match.cmd_process(fromTable, toTable, Match.MODE_NOTMATCHED, Match.MATCH_SHIPMENT, match.getMatchTypeText(Match.MATCH_INVOICE), new BigDecimal(1));
|
||||
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyInvoiced().intValue(), "Unexpected order line qty invoiced value");
|
||||
receiptLine.load(getTrxName());
|
||||
MMatchInv[] matchInvs = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName());
|
||||
assertEquals(1, matchInvs.length, "Unexpected number of MatchInv for invoice line");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.compiere.apps.form.PayPrint;
|
|||
import org.compiere.apps.form.PaySelect;
|
||||
import org.compiere.apps.form.PaySelect.BankInfo;
|
||||
import org.compiere.minigrid.IDColumn;
|
||||
import org.compiere.minigrid.MiniTableImpl;
|
||||
import org.compiere.model.MAllocationHdr;
|
||||
import org.compiere.model.MAllocationLine;
|
||||
import org.compiere.model.MBPartner;
|
||||
|
@ -69,7 +70,6 @@ import org.compiere.util.ValueNamePair;
|
|||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.idempiere.test.ui.MiniTableImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class PaySelectFormTest extends AbstractTestCase {
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
/***********************************************************************
|
||||
* This file is part of iDempiere ERP Open Source *
|
||||
* http://www.idempiere.org *
|
||||
* *
|
||||
* Copyright (C) Contributors *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License *
|
||||
* as published by the Free Software Foundation; either version 2 *
|
||||
* of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* 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., 51 Franklin Street, Fifth Floor, Boston, *
|
||||
* MA 02110-1301, USA. *
|
||||
* *
|
||||
* Contributors: *
|
||||
* - hengsin *
|
||||
**********************************************************************/
|
||||
package org.idempiere.test.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.compiere.model.MBPartner;
|
||||
import org.compiere.model.MInOut;
|
||||
import org.compiere.model.MInOut.MatchingRecord;
|
||||
import org.compiere.model.MInOutLine;
|
||||
import org.compiere.model.MInvoice;
|
||||
import org.compiere.model.MInvoiceLine;
|
||||
import org.compiere.model.MMatchInv;
|
||||
import org.compiere.model.MMatchPO;
|
||||
import org.compiere.model.MOrder;
|
||||
import org.compiere.model.MOrderLine;
|
||||
import org.compiere.model.MProduct;
|
||||
import org.compiere.model.MWarehouse;
|
||||
import org.compiere.process.DocAction;
|
||||
import org.compiere.process.DocumentEngine;
|
||||
import org.compiere.process.ProcessInfo;
|
||||
import org.compiere.util.Env;
|
||||
import org.compiere.wf.MWorkflow;
|
||||
import org.idempiere.test.AbstractTestCase;
|
||||
import org.idempiere.test.DictionaryIDs;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test matching api
|
||||
* @author hengsin
|
||||
*/
|
||||
public class MatchingTest extends AbstractTestCase {
|
||||
|
||||
/**
|
||||
* default constructor
|
||||
*/
|
||||
public MatchingTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchReceiptToPO() {
|
||||
MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id);
|
||||
MProduct product = MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id);
|
||||
|
||||
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
|
||||
order.setBPartner(bpartner);
|
||||
order.setIsSOTrx(false);
|
||||
order.setC_DocTypeTarget_ID();
|
||||
order.setDocStatus(DocAction.STATUS_Drafted);
|
||||
order.setDocAction(DocAction.ACTION_Complete);
|
||||
order.saveEx();
|
||||
|
||||
BigDecimal orderQty = new BigDecimal("1");
|
||||
MOrderLine orderLine = new MOrderLine(order);
|
||||
orderLine.setLine(10);
|
||||
orderLine.setProduct(product);
|
||||
orderLine.setQty(orderQty);
|
||||
orderLine.saveEx();
|
||||
|
||||
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
order.load(getTrxName());
|
||||
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(0, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
|
||||
MInOut receipt = new MInOut(Env.getCtx(), 0, getTrxName());
|
||||
receipt.setBPartner(bpartner);
|
||||
receipt.setIsSOTrx(false);
|
||||
receipt.setC_DocType_ID(DictionaryIDs.C_DocType.MM_RECEIPT.id);
|
||||
receipt.setM_Warehouse_ID(getM_Warehouse_ID());
|
||||
receipt.saveEx();
|
||||
MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID());
|
||||
int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID();
|
||||
BigDecimal receiptQty = new BigDecimal("1");
|
||||
MInOutLine receiptLine = new MInOutLine(receipt);
|
||||
receiptLine.setProduct(product);
|
||||
receiptLine.setM_Locator_ID(M_Locator_ID);
|
||||
receiptLine.setLine(10);
|
||||
receiptLine.setQty(receiptQty);
|
||||
receiptLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
receipt.load(getTrxName());
|
||||
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(0, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
|
||||
List<MatchingRecord> notMatchList = MInOut.getNotFullyMatchedToOrder(bpartner.get_ID(), product.get_ID(), 0, null, null, getTrxName());
|
||||
assertTrue(notMatchList.size() > 0, "Fail to retrieve receipts not fully matched to order");
|
||||
Optional<MInOut.MatchingRecord> optionalReceipt = notMatchList.stream().filter(m -> receipt.getDocumentNo().equals(m.documentNo())).findFirst();
|
||||
assertTrue(optionalReceipt.isPresent(), "Can't find not matched Material Receipt line");
|
||||
|
||||
List<MOrder.MatchingRecord> notMatchOrders = MOrder.getNotFullyMatchedToReceipt(bpartner.get_ID(), product.get_ID(), 0, null, null, getTrxName());
|
||||
assertTrue(notMatchOrders.size() > 0, "Fail to retrieve orders not fully matched to material receipt");
|
||||
Optional<MOrder.MatchingRecord> optionalOrder = notMatchOrders.stream().filter(m -> order.getDocumentNo().equals(m.documentNo())).findFirst();
|
||||
assertTrue(optionalOrder.isPresent(), "Can't find not matched PO line");
|
||||
|
||||
//process matching
|
||||
boolean ok = receiptLine.matchToOrderLine(orderLine.get_ID(), orderLine.getQtyOrdered());
|
||||
assertTrue(ok, "Failed to match receipt line to order line");
|
||||
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(0, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(1, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
receiptLine.load(getTrxName());
|
||||
assertEquals(orderLine.getC_OrderLine_ID(), receiptLine.getC_OrderLine_ID(), "Unexpected order line ID value for receipt line");
|
||||
MMatchPO[] matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName());
|
||||
assertEquals(1, matchPOs.length, "Unexpected number of MatchPO for order line");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchInvoiceToReceipt() {
|
||||
MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id);
|
||||
MProduct product = MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id);
|
||||
|
||||
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
|
||||
order.setBPartner(bpartner);
|
||||
order.setIsSOTrx(false);
|
||||
order.setC_DocTypeTarget_ID();
|
||||
order.setDocStatus(DocAction.STATUS_Drafted);
|
||||
order.setDocAction(DocAction.ACTION_Complete);
|
||||
order.saveEx();
|
||||
|
||||
BigDecimal orderQty = new BigDecimal("1");
|
||||
MOrderLine orderLine = new MOrderLine(order);
|
||||
orderLine.setLine(10);
|
||||
orderLine.setProduct(product);
|
||||
orderLine.setQty(orderQty);
|
||||
orderLine.saveEx();
|
||||
|
||||
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
order.load(getTrxName());
|
||||
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(0, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
|
||||
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered()); // MM Receipt
|
||||
receipt.saveEx();
|
||||
|
||||
MInOutLine receiptLine = new MInOutLine(receipt);
|
||||
receiptLine.setC_OrderLine_ID(orderLine.get_ID());
|
||||
receiptLine.setLine(10);
|
||||
receiptLine.setProduct(product);
|
||||
receiptLine.setQty(BigDecimal.ONE);
|
||||
MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID());
|
||||
int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID();
|
||||
receiptLine.setM_Locator_ID(M_Locator_ID);
|
||||
receiptLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||
receipt.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||
|
||||
if (!receipt.isPosted()) {
|
||||
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName());
|
||||
assertTrue(error == null);
|
||||
}
|
||||
receipt.load(getTrxName());
|
||||
assertTrue(receipt.isPosted());
|
||||
|
||||
MInvoice invoice = new MInvoice(Env.getCtx(), 0, getTrxName());
|
||||
invoice.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.AP_INVOICE.id);
|
||||
invoice.setBPartner(bpartner);
|
||||
invoice.setDateAcct(order.getDateAcct());
|
||||
invoice.setDateInvoiced(order.getDateOrdered());
|
||||
invoice.setDocStatus(DocAction.STATUS_Drafted);
|
||||
invoice.setDocAction(DocAction.ACTION_Complete);
|
||||
invoice.saveEx();
|
||||
|
||||
BigDecimal qtyInvoiced = new BigDecimal(1);
|
||||
MInvoiceLine invoiceLine = new MInvoiceLine(invoice);
|
||||
invoiceLine.setProduct(product);
|
||||
invoiceLine.setLine(10);
|
||||
invoiceLine.setQty(qtyInvoiced);
|
||||
invoiceLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete);
|
||||
invoice.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus());
|
||||
|
||||
if (!invoice.isPosted()) {
|
||||
String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName());
|
||||
assertTrue(error == null);
|
||||
}
|
||||
invoice.load(getTrxName());
|
||||
assertTrue(invoice.isPosted());
|
||||
|
||||
//load not match invoice
|
||||
List<MInvoice.MatchingRecord> notMatchInvoices = MInvoice.getNotFullyMatchedToReceipt(bpartner.get_ID(), product.getM_Product_ID(), 0, null, null, getTrxName());
|
||||
assertTrue(notMatchInvoices.size() > 0, "Unexpected number of records for not matched vendor invoice");
|
||||
Optional<MInvoice.MatchingRecord> optionalInvoice = notMatchInvoices.stream().filter(nmi -> invoice.getDocumentNo().equals(nmi.documentNo())).findFirst();
|
||||
assertTrue(optionalInvoice.isPresent(), "Can't find not matched vendor invoice line");
|
||||
|
||||
//load not matched receipt
|
||||
List<MInOut.MatchingRecord> notMatchReceipts = MInOut.getNotFullyMatchedToInvoice(bpartner.getC_BPartner_ID(), product.getM_Product_ID(), 0, null, null, getTrxName());
|
||||
assertTrue(notMatchReceipts.size()>0, "Unexpected number of records for not matched material receipt Line");
|
||||
Optional<MInOut.MatchingRecord> optionalReceipt = notMatchReceipts.stream().filter(nmr -> receipt.getDocumentNo().equals(nmr.documentNo())).findFirst();
|
||||
assertTrue(optionalReceipt.isPresent(), "Can't find not matched material receipt line");
|
||||
|
||||
//select and process matching
|
||||
boolean ok = receiptLine.matchToInvoiceLine(invoiceLine.getC_InvoiceLine_ID(), qtyInvoiced);
|
||||
assertTrue(ok, "Failed to match receipt line to invoice line");
|
||||
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyInvoiced().intValue(), "Unexpected order line qty invoiced value");
|
||||
receiptLine.load(getTrxName());
|
||||
MMatchInv[] matchInvs = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName());
|
||||
assertEquals(1, matchInvs.length, "Unexpected number of MatchInv for invoice line");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchReceiptToInvoice() {
|
||||
MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id);
|
||||
MProduct product = MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id);
|
||||
|
||||
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
|
||||
order.setBPartner(bpartner);
|
||||
order.setIsSOTrx(false);
|
||||
order.setC_DocTypeTarget_ID();
|
||||
order.setDocStatus(DocAction.STATUS_Drafted);
|
||||
order.setDocAction(DocAction.ACTION_Complete);
|
||||
order.saveEx();
|
||||
|
||||
BigDecimal orderQty = new BigDecimal("1");
|
||||
MOrderLine orderLine = new MOrderLine(order);
|
||||
orderLine.setLine(10);
|
||||
orderLine.setProduct(product);
|
||||
orderLine.setQty(orderQty);
|
||||
orderLine.saveEx();
|
||||
|
||||
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
order.load(getTrxName());
|
||||
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(0, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
|
||||
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered()); // MM Receipt
|
||||
receipt.saveEx();
|
||||
|
||||
MInOutLine receiptLine = new MInOutLine(receipt);
|
||||
receiptLine.setC_OrderLine_ID(orderLine.get_ID());
|
||||
receiptLine.setLine(10);
|
||||
receiptLine.setProduct(product);
|
||||
receiptLine.setQty(BigDecimal.ONE);
|
||||
MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID());
|
||||
int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID();
|
||||
receiptLine.setM_Locator_ID(M_Locator_ID);
|
||||
receiptLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||
receipt.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||
|
||||
if (!receipt.isPosted()) {
|
||||
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName());
|
||||
assertTrue(error == null);
|
||||
}
|
||||
receipt.load(getTrxName());
|
||||
assertTrue(receipt.isPosted());
|
||||
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(0, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value");
|
||||
assertEquals(1, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value");
|
||||
assertEquals(0, orderLine.getQtyInvoiced().intValue(), "Unexpected order line qty invoiced value");
|
||||
|
||||
MInvoice invoice = new MInvoice(Env.getCtx(), 0, getTrxName());
|
||||
invoice.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.AP_INVOICE.id);
|
||||
invoice.setBPartner(bpartner);
|
||||
invoice.setDateAcct(order.getDateAcct());
|
||||
invoice.setDateInvoiced(order.getDateOrdered());
|
||||
invoice.setDocStatus(DocAction.STATUS_Drafted);
|
||||
invoice.setDocAction(DocAction.ACTION_Complete);
|
||||
invoice.saveEx();
|
||||
|
||||
BigDecimal qtyInvoiced = new BigDecimal(1);
|
||||
MInvoiceLine invoiceLine = new MInvoiceLine(invoice);
|
||||
invoiceLine.setProduct(product);
|
||||
invoiceLine.setLine(10);
|
||||
invoiceLine.setQty(qtyInvoiced);
|
||||
invoiceLine.saveEx();
|
||||
|
||||
info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete);
|
||||
invoice.load(getTrxName());
|
||||
assertFalse(info.isError(), info.getSummary());
|
||||
assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus());
|
||||
|
||||
if (!invoice.isPosted()) {
|
||||
String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName());
|
||||
assertTrue(error == null);
|
||||
}
|
||||
invoice.load(getTrxName());
|
||||
assertTrue(invoice.isPosted());
|
||||
|
||||
//load not match receipt
|
||||
List<MInOut.MatchingRecord> notMatchReceipts = MInOut.getNotFullyMatchedToInvoice(bpartner.getC_BPartner_ID(), product.getM_Product_ID(), 0, null, null, getTrxName());
|
||||
assertTrue(notMatchReceipts.size()>0, "Unexpected number of records for not matched material receipt");
|
||||
Optional<MInOut.MatchingRecord> optionalReceipt = notMatchReceipts.stream().filter(nmr -> receipt.getDocumentNo().equals(nmr.documentNo())).findFirst();
|
||||
assertTrue(optionalReceipt.isPresent(), "Can't find not matched material receipt line");
|
||||
|
||||
//load not matched vendor invoice
|
||||
List<MInvoice.MatchingRecord> notMatchInvoices = MInvoice.getNotFullyMatchedToReceipt(bpartner.getC_BPartner_ID(), product.getM_Product_ID(), 0, null, null, getTrxName());
|
||||
assertTrue(notMatchInvoices.size()>0, "Unexpected number of records for not matched vendor invoice Line");
|
||||
Optional<MInvoice.MatchingRecord> optionalInvoice = notMatchInvoices.stream().filter(nmi -> invoice.getDocumentNo().equals(nmi.documentNo())).findFirst();
|
||||
assertTrue(optionalInvoice.isPresent(), "Can't find not matched vendor invoice line");
|
||||
|
||||
//select and process matching
|
||||
boolean ok = receiptLine.matchToInvoiceLine(invoiceLine.getC_InvoiceLine_ID(), qtyInvoiced);
|
||||
assertTrue(ok, "Failed to match receipt line to invoice line");
|
||||
|
||||
orderLine.load(getTrxName());
|
||||
assertEquals(1, orderLine.getQtyInvoiced().intValue(), "Unexpected order line qty invoiced value");
|
||||
receiptLine.load(getTrxName());
|
||||
MMatchInv[] matchInvs = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName());
|
||||
assertEquals(1, matchInvs.length, "Unexpected number of MatchInv for invoice line");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue