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:
hengsin 2023-07-31 21:20:26 +08:00 committed by GitHub
parent 0da6a2ab44
commit 890862de47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1629 additions and 259 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 (!createReversals())
return false;
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,7 +2774,8 @@ public class MOrder extends X_C_Order implements DocAction
if (!isSOTrx())
return true;
log.info("createReversals");
if (log.isLoggable(Level.INFO))
log.info("createReversals");
StringBuilder info = new StringBuilder();
// Reverse All *Shipments*
@ -2596,7 +2848,38 @@ 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.
* Cancel not delivered Quantities

View File

@ -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"/>

View File

@ -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
* 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
* 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)
{
@ -232,7 +236,15 @@ public class Match
/**
* Fill match to
* 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')");
if ( matchToType == MATCH_INVOICE && lineMatched!= null && Line_ID > 0 )
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 )
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

View File

@ -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;

View File

@ -23,7 +23,7 @@
* - hengsin *
**********************************************************************/
package org.idempiere.test.ui;
package org.compiere.minigrid;
/**
*

View File

@ -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"/>

View File

@ -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");
}
}

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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");
}
}

View File

@ -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 {

View File

@ -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");
}
}