From 74b6604d578f8c28a95e050ba7a3fe0cc89279e3 Mon Sep 17 00:00:00 2001 From: hengsin Date: Tue, 14 Dec 2021 01:48:48 +0800 Subject: [PATCH] =?UTF-8?q?IDEMPIERE-5065=20Material=20Receipt=20Line=20no?= =?UTF-8?q?t=20available=20for=20matching=20after=E2=80=A6=20(#1019)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * IDEMPIERE-5065 Material Receipt Line not available for matching after reversal of Match PO * IDEMPIERE-5065 Material Receipt Line not available for matching after reversal of Match PO Fix unit test * IDEMPIERE-5065 Material Receipt Line not available for matching after reversal of Match PO Remove unreliable and potentially dangerous match po consolidation --- .../org/adempiere/process/MatchPOReverse.java | 2 +- .../src/org/compiere/acct/Doc.java | 2 +- .../src/org/compiere/model/MMatchPO.java | 139 +++--- .../org/adempiere/webui/apps/form/WMatch.java | 2 - .../src/org/compiere/apps/form/Match.java | 161 ++++--- org.idempiere.test/META-INF/MANIFEST.MF | 3 +- .../org/idempiere/test/base/MatchPOTest.java | 401 ++++++++++++++++++ .../org/idempiere/test/ui/MiniTableImpl.java | 343 +++++++++++++++ .../org/idempiere/test/ui/TableColumn.java | 122 ++++++ 9 files changed, 1041 insertions(+), 134 deletions(-) create mode 100644 org.idempiere.test/src/org/idempiere/test/ui/MiniTableImpl.java create mode 100644 org.idempiere.test/src/org/idempiere/test/ui/TableColumn.java diff --git a/org.adempiere.base/src/org/adempiere/process/MatchPOReverse.java b/org.adempiere.base/src/org/adempiere/process/MatchPOReverse.java index 3927babeee..495ba52b5a 100644 --- a/org.adempiere.base/src/org/adempiere/process/MatchPOReverse.java +++ b/org.adempiere.base/src/org/adempiere/process/MatchPOReverse.java @@ -53,7 +53,7 @@ public class MatchPOReverse extends SvrProcess { if (reversalDate == null) { reversalDate = new Timestamp(System.currentTimeMillis()); } - if (!po.reverse(reversalDate)) + if (!po.reverse(reversalDate, true)) throw new AdempiereException("Failed to reverse matching"); } return "@OK@"; diff --git a/org.adempiere.base/src/org/compiere/acct/Doc.java b/org.adempiere.base/src/org/compiere/acct/Doc.java index 27105a82c4..0e46105b0c 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc.java @@ -138,7 +138,7 @@ public abstract class Doc * M_Requisition POR **************************************************************************/ - private static final String DOC_TYPE_BY_DOC_BASE_TYPE_SQL = "SELECT C_DocType_ID FROM C_DocType WHERE AD_Client_ID=? AND DocBaseType=? AND IsActive='Y' ORDER BY IsDefault DESC, C_DocType_ID"; + public static final String DOC_TYPE_BY_DOC_BASE_TYPE_SQL = "SELECT C_DocType_ID FROM C_DocType WHERE AD_Client_ID=? AND DocBaseType=? AND IsActive='Y' ORDER BY IsDefault DESC, C_DocType_ID"; /** AR Invoices - ARI */ public static final String DOCTYPE_ARInvoice = MDocType.DOCBASETYPE_ARInvoice; diff --git a/org.adempiere.base/src/org/compiere/model/MMatchPO.java b/org.adempiere.base/src/org/compiere/model/MMatchPO.java index e8f479e8be..ddbbd47061 100644 --- a/org.adempiere.base/src/org/compiere/model/MMatchPO.java +++ b/org.adempiere.base/src/org/compiere/model/MMatchPO.java @@ -31,6 +31,10 @@ import java.util.Map; import java.util.Properties; import java.util.logging.Level; +import org.adempiere.base.Core; +import org.adempiere.util.IReservationTracer; +import org.adempiere.util.IReservationTracerFactory; +import org.compiere.acct.Doc; import org.compiere.process.DocAction; import org.compiere.util.CLogger; import org.compiere.util.DB; @@ -1274,84 +1278,12 @@ public class MMatchPO extends X_M_MatchPO .append (",C_OrderLine_ID=").append (getC_OrderLine_ID()) .append (",M_InOutLine_ID=").append (getM_InOutLine_ID()) .append (",C_InvoiceLine_ID=").append (getC_InvoiceLine_ID()) + .append (",Processed=").append(isProcessed()) + .append (",Posted=").append(isPosted()) .append ("]"); return sb.toString (); } // toString - /** - * Consolidate MPO entries. - * (data conversion issue) - * @param ctx context - */ - public static void consolidate(Properties ctx) - { - String sql = "SELECT * FROM M_MatchPO po " - + "WHERE EXISTS (SELECT 1 FROM M_MatchPO x " - + "WHERE po.C_OrderLine_ID=x.C_OrderLine_ID AND po.Qty=x.Qty " - + "GROUP BY C_OrderLine_ID, Qty " - + "HAVING COUNT(*) = 2) " - + " AND AD_Client_ID=?" - + "ORDER BY C_OrderLine_ID, M_InOutLine_ID"; - PreparedStatement pstmt = null; - ResultSet rs = null; - int success = 0; - int errors = 0; - try - { - pstmt = DB.prepareStatement (sql, null); - pstmt.setInt(1, Env.getAD_Client_ID(ctx)); - rs = pstmt.executeQuery (); - while (rs.next ()) - { - MMatchPO po1 = new MMatchPO (ctx, rs, null); - if (rs.next()) - { - MMatchPO po2 = new MMatchPO (ctx, rs, null); - if (po1.getM_InOutLine_ID() != 0 && po1.getC_InvoiceLine_ID() == 0 - && po2.getM_InOutLine_ID() == 0 && po2.getC_InvoiceLine_ID() != 0) - { - StringBuilder s1 = new StringBuilder("UPDATE M_MatchPO SET C_InvoiceLine_ID=") - .append(po2.getC_InvoiceLine_ID()) - .append(" WHERE M_MatchPO_ID=").append(po1.getM_MatchPO_ID()); - int no1 = DB.executeUpdate(s1.toString(), null); - if (no1 != 1) - { - errors++; - s_log.warning("Not updated M_MatchPO_ID=" + po1.getM_MatchPO_ID()); - continue; - } - // - String s2 = "DELETE FROM Fact_Acct WHERE AD_Table_ID=473 AND Record_ID=?"; - int no2 = DB.executeUpdate(s2, po2.getM_MatchPO_ID(), null); - String s3 = "DELETE FROM M_MatchPO WHERE M_MatchPO_ID=?"; - int no3 = DB.executeUpdate(s3, po2.getM_MatchPO_ID(), null); - if (no2 == 0 && no3 == 1) - success++; - else - { - s_log.warning("M_MatchPO_ID=" + po2.getM_MatchPO_ID() - + " - Deleted=" + no2 + ", Acct=" + no3); - errors++; - } - } - } - } - } - catch (Exception e) - { - s_log.log (Level.SEVERE, sql, e); - } - finally - { - DB.close(rs, pstmt); - rs = null; pstmt = null; - } - if (errors == 0 && success == 0) - ; - else - if (s_log.isLoggable(Level.INFO)) s_log.info("Success #" + success + " - Error #" + errors); - } // consolidate - /** * Reverse MatchPO. * @param reversalDate @@ -1359,7 +1291,20 @@ public class MMatchPO extends X_M_MatchPO * @throws Exception */ - public boolean reverse(Timestamp reversalDate) + public boolean reverse(Timestamp reversalDate) + { + return reverse(reversalDate, false); + } + + /** + * Reverse MatchPO. + * @param reversalDate + * @param reverseMatchingOnly true if MR is not reverse + * @return boolean + * @throws Exception + */ + + public boolean reverse(Timestamp reversalDate, boolean reverseMatchingOnly) { if (this.isProcessed() && this.getReversal_ID() == 0) { @@ -1389,6 +1334,50 @@ public class MMatchPO extends X_M_MatchPO this.setReversal_ID(reversal.getM_MatchPO_ID()); this.saveEx(); + //update qtyOrdered + if (reverseMatchingOnly && reversal.getM_InOutLine_ID() > 0 && reversal.getC_OrderLine_ID() > 0) + { + MInOutLine sLine = new MInOutLine(Env.getCtx(), reversal.getM_InOutLine_ID(), get_TrxName()); + if (sLine.getMovementQty().compareTo(this.getQty()) == 0 && sLine.getC_OrderLine_ID() == reversal.getC_OrderLine_ID()) + { + //clear c_orderline from shipment so we can match the shipment again (to the same or different order line) + sLine.setC_OrderLine_ID(0); + sLine.saveEx(); + } + //add back qtyOrdered + MOrderLine oLine = new MOrderLine(Env.getCtx(), reversal.getC_OrderLine_ID(), get_TrxName()); + BigDecimal storageReservationToUpdate = oLine.getQtyReserved(); + oLine.setQtyReserved(oLine.getQtyReserved().add(getQty())); + BigDecimal reservedAndDelivered = oLine.getQtyDelivered().add(oLine.getQtyReserved()); + if (reservedAndDelivered.compareTo(oLine.getQtyOrdered()) > 0) + { + oLine.setQtyReserved(oLine.getQtyReserved().subtract(reservedAndDelivered.subtract(oLine.getQtyOrdered()))); + if (oLine.getQtyReserved().signum()==-1) + oLine.setQtyReserved(Env.ZERO); + } + oLine.saveEx(); + storageReservationToUpdate = storageReservationToUpdate.subtract(oLine.getQtyReserved()); + if (storageReservationToUpdate.signum() != 0) + { + IReservationTracer tracer = null; + IReservationTracerFactory factory = Core.getReservationTracerFactory(); + if (factory != null) + { + int docTypeId = DB.getSQLValue((String)null, Doc.DOC_TYPE_BY_DOC_BASE_TYPE_SQL, getAD_Client_ID(), Doc.DOCTYPE_MatMatchPO); + tracer = factory.newTracer(docTypeId, reversal.getDocumentNo(), 10, + reversal.get_Table_ID(), reversal.get_ID(), oLine.getM_Warehouse_ID(), + oLine.getM_Product_ID(), oLine.getM_AttributeSetInstance_ID(), oLine.getParent().isSOTrx(), + get_TrxName()); + } + boolean success = MStorageReservation.add (Env.getCtx(), oLine.getM_Warehouse_ID(), + oLine.getM_Product_ID(), + oLine.getM_AttributeSetInstance_ID(), + storageReservationToUpdate.negate(), oLine.getParent().isSOTrx(), get_TrxName(), tracer); + if (!success) + return false; + } + } + // auto create new matchpo if have invoice line if ( reversal.getC_InvoiceLine_ID() > 0 && reversal.getM_InOutLine_ID() > 0 ) { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WMatch.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WMatch.java index 6abbd042b1..a5150b6211 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WMatch.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WMatch.java @@ -55,7 +55,6 @@ import org.adempiere.webui.util.ZKUpdateUtil; import org.compiere.apps.form.Match; import org.compiere.minigrid.ColumnInfo; import org.compiere.minigrid.IDColumn; -import org.compiere.model.MMatchPO; import org.compiere.util.CLogger; import org.compiere.util.DisplayType; import org.compiere.util.Env; @@ -112,7 +111,6 @@ public class WMatch extends Match LayoutUtils.addSclass("status-border", statusBar); // - MMatchPO.consolidate(Env.getCtx()); cmd_matchTo(); } catch(Exception e) diff --git a/org.adempiere.ui/src/org/compiere/apps/form/Match.java b/org.adempiere.ui/src/org/compiere/apps/form/Match.java index b3748d77d5..f3319bf214 100644 --- a/org.adempiere.ui/src/org/compiere/apps/form/Match.java +++ b/org.adempiere.ui/src/org/compiere/apps/form/Match.java @@ -25,6 +25,7 @@ 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.IDColumn; import org.compiere.minigrid.IMiniTable; import org.compiere.model.MClient; @@ -35,6 +36,7 @@ import org.compiere.model.MMatchPO; import org.compiere.model.MOrderLine; 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; @@ -55,39 +57,33 @@ public class Match Msg.getElement(Env.getCtx(), "C_Invoice_ID", false), Msg.getElement(Env.getCtx(), "M_InOut_ID", false), Msg.getElement(Env.getCtx(), "C_Order_ID", false) }; - private static final int MATCH_INVOICE = 0; - private static final int MATCH_SHIPMENT = 1; - private static final int MATCH_ORDER = 2; + + public static final int MATCH_INVOICE = 0; + public static final int MATCH_SHIPMENT = 1; + public static final int MATCH_ORDER = 2; - private static final int MODE_NOTMATCHED = 0; + public static final int MODE_NOTMATCHED = 0; //private static final int MODE_MATCHED = 1; /** Indexes in Table */ - private static final int I_BPartner = 3; - private static final int I_Line = 4; - private static final int I_Product = 5; - private static final int I_QTY = 6; - private static final int I_MATCHED = 7; - //private static final int I_Org = 8; //JAVIER + public static final int I_BPartner = 3; + public static final int I_Line = 4; + public static final int I_Product = 5; + public static final int I_QTY = 6; + public static final int I_MATCHED = 7; - - private StringBuffer m_sql = null; private String m_dateColumn = ""; private String m_qtyColumn = ""; private String m_groupBy = ""; private StringBuffer m_linetype = null; - //private BigDecimal m_xMatched = Env.ZERO; - //private BigDecimal m_xMatchedTo = Env.ZERO; - + private String m_trxName = null; /** * Match From Changed - Fill Match To */ protected Vector cmd_matchFrom(String selection) { - // if (log.isLoggable(Level.FINE)) log.fine( "VMatch.cmd_matchFrom"); - //String selection = (String)matchFrom.getSelectedItem(); Vector vector = new Vector(2); if (selection.equals(m_matchOptions[MATCH_INVOICE])) vector.add(m_matchOptions[MATCH_SHIPMENT]); @@ -103,13 +99,11 @@ public class Match /** - * Search Button Pressed - Fill xMatched + * Search Button Pressed - Fill match from */ - protected IMiniTable cmd_search(IMiniTable xMatchedTable, int display, String matchToString, Integer Product, Integer Vendor, Timestamp from, Timestamp to, boolean matched) + public IMiniTable cmd_search(IMiniTable xMatchedTable, int display, String matchToString, Integer Product, Integer Vendor, Timestamp from, Timestamp to, boolean matched) { // ** Create SQL ** - //int display = matchFrom.getSelectedIndex(); - //String matchToString = (String)matchTo.getSelectedItem(); int matchToType = MATCH_INVOICE; if (matchToString.equals(m_matchOptions[MATCH_SHIPMENT])) matchToType = MATCH_SHIPMENT; @@ -132,8 +126,6 @@ public class Match m_sql.append(" AND hdr.C_BPartner_ID=").append(Vendor); } // Date - //Timestamp from = (Timestamp)dateFrom.getValue(); - //Timestamp to = (Timestamp)dateTo.getValue(); if (from != null && to != null) m_sql.append(" AND ").append(m_dateColumn).append(" BETWEEN ") .append(DB.TO_DATE(from)).append(" AND ").append(DB.TO_DATE(to)); @@ -152,14 +144,13 @@ public class Match /** * Process Button Pressed - Process Matching */ - protected void cmd_process(IMiniTable xMatchedTable, IMiniTable xMatchedToTable, int matchMode, int matchFrom, Object matchTo, BigDecimal m_xMatched) + public void cmd_process(IMiniTable xMatchedTable, IMiniTable xMatchedToTable, int matchMode, int matchFrom, String matchTo, BigDecimal m_xMatched) { log.config(""); // Matched From int matchedRow = xMatchedTable.getSelectedRow(); if (matchedRow < 0) return; - // KeyNamePair BPartner = (KeyNamePair)xMatchedTable.getValueAt(matchedRow, I_BPartner); KeyNamePair lineMatched = (KeyNamePair)xMatchedTable.getValueAt(matchedRow, I_Line); KeyNamePair Product = (KeyNamePair)xMatchedTable.getValueAt(matchedRow, I_Product); @@ -207,39 +198,45 @@ public class Match } // Create it - String innerTrxName = Trx.createTrxName("Match"); - Trx innerTrx = Trx.get(innerTrxName, true); - innerTrx.setDisplayName(getClass().getName()+"_cmd_process"); + String innerTrxName = m_trxName == null ? Trx.createTrxName("Match") : null; + Trx innerTrx = innerTrxName != null ? Trx.get(innerTrxName, true) : null; + if (innerTrx != null) + innerTrx.setDisplayName(getClass().getName()+"_cmd_process"); - try{ - if (createMatchRecord(invoice, M_InOutLine_ID, Line_ID, BigDecimal.valueOf(qty), innerTrxName)) - innerTrx.commit(); - else + try { + if (createMatchRecord(invoice, M_InOutLine_ID, Line_ID, BigDecimal.valueOf(qty), m_trxName != null ? m_trxName : innerTrxName)) { + if (innerTrx != null) + innerTrx.commit(); + } else { + if (innerTrx != null) + innerTrx.rollback(); + else + Trx.get(m_trxName, false).rollback(); + } + } catch(Exception ex) { + if (innerTrx != null) innerTrx.rollback(); - }catch(Exception ex){ - innerTrx.rollback(); throw new AdempiereException(ex); - }finally{ - innerTrx.close(); - innerTrx = null; + } finally { + if (innerTrx != null) { + innerTrx.close(); + innerTrx = null; + } } } } - // requery - //cmd_search(); } // cmd_process /** - * Fill xMatchedTo + * Fill match to */ - protected IMiniTable cmd_searchTo(IMiniTable xMatchedTable, IMiniTable xMatchedToTable, String displayString, int matchToType, boolean sameBPartner, boolean sameProduct, boolean sameQty, boolean matched) + public IMiniTable cmd_searchTo(IMiniTable xMatchedTable, IMiniTable xMatchedToTable, String displayString, int matchToType, boolean sameBPartner, boolean sameProduct, boolean sameQty, boolean matched) { int row = xMatchedTable.getSelectedRow(); if (log.isLoggable(Level.CONFIG)) log.config("Row=" + row); // ** Create SQL ** - //String displayString = (String)matchTo.getSelectedItem(); int display = MATCH_INVOICE; if (displayString.equals(m_matchOptions[MATCH_SHIPMENT])) display = MATCH_SHIPMENT; @@ -248,11 +245,9 @@ public class Match KeyNamePair lineMatched = (KeyNamePair)xMatchedTable.getValueAt(row, I_Line); - //int matchToType = matchFrom.getSelectedIndex(); tableInit (display, matchToType, matched, lineMatched); // sets m_sql // ** Add Where Clause ** KeyNamePair BPartner = (KeyNamePair)xMatchedTable.getValueAt(row, I_BPartner); - //KeyNamePair Org = (KeyNamePair)xMatchedTable.getValueAt(row, I_Org); //JAVIER KeyNamePair Product = (KeyNamePair)xMatchedTable.getValueAt(row, I_Product); if (log.isLoggable(Level.FINE)) log.fine("BPartner=" + BPartner + " - Product=" + Product); // @@ -327,7 +322,14 @@ public class Match 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,SUM(COALESCE(mo.Qty,0)), org.Name, hdr.AD_Org_ID " //JAVIER + + " 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)" @@ -355,8 +357,14 @@ public class Match 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") - + "<>SUM(COALESCE(mo.Qty,0))"; + + (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)) "; } else // Shipment { @@ -364,7 +372,12 @@ public class Match 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,SUM(NVL(m.Qty,0)),org.Name, hdr.AD_Org_ID " //JAVIER + + " 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),"); + 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)" @@ -383,10 +396,12 @@ public class Match 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 " //JAVIER + "HAVING " - + (matched ? "0" : "CASE WHEN (dt.DocBaseType='MMS' AND hdr.issotrx='N') THEN lin.MovementQty * -1 ELSE lin.MovementQty END") - + "<>SUM(NVL(m.Qty,0))"; + + (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)"; + else + m_groupBy = m_groupBy + "<>SUM(COALESCE(m.Qty,0))"; } - // Log.trace(7, "VMatch.tableInit", m_sql + "\n" + m_groupBy); } // tableInit @@ -396,7 +411,6 @@ public class Match */ protected void tableLoad (IMiniTable table) { - // log.finest(m_sql + " - " + m_groupBy); String sql = MRole.getDefault().addAccessSQL( m_sql.toString(), "hdr", MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO) + m_groupBy; @@ -405,7 +419,7 @@ public class Match ResultSet rs = null; try { - stmt = DB.createStatement(); + stmt = DB.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, m_trxName); rs = stmt.executeQuery(sql); table.loadTable(rs); } @@ -518,7 +532,7 @@ public class Match // Create PO - Shipment Link if (sLine.getM_Product_ID() != 0) { - MMatchPO match = new MMatchPO (sLine, null, qty); + MMatchPO match = getOrCreate(Line_ID, qty, sLine, trxName); match.setC_OrderLine_ID(Line_ID); if (!match.save()) { @@ -555,4 +569,43 @@ public class Match } 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 + */ + public void setTrxName(String trxName) { + m_trxName = trxName; + } + + /** + * + * @return trxName + */ + public String getTrxName() { + return m_trxName; + } + + /** + * + * @param matchType MATCH_INVOICE, MATCH_SHIPMENT or MATCH_ORDER + * @return display text for match type + */ + public String getMatchTypeText(int matchType) { + if (matchType >= 0 && matchType < m_matchOptions.length) + return m_matchOptions[matchType]; + return null; + } } diff --git a/org.idempiere.test/META-INF/MANIFEST.MF b/org.idempiere.test/META-INF/MANIFEST.MF index 3c98b5a6c2..ee821f6e1f 100644 --- a/org.idempiere.test/META-INF/MANIFEST.MF +++ b/org.idempiere.test/META-INF/MANIFEST.MF @@ -29,7 +29,8 @@ Require-Bundle: org.adempiere.base;bundle-version="9.0.0", org.adempiere.payment.processor;bundle-version="9.0.0", org.compiere.db.postgresql.provider;bundle-version="9.0.0", org.idempiere.webservices;bundle-version="9.0.0", - org.adempiere.ui.zk;bundle-version="9.0.0" + org.adempiere.ui.zk;bundle-version="9.0.0", + org.adempiere.ui;bundle-version="9.0.0" Bundle-ActivationPolicy: lazy Bundle-Activator: org.idempiere.test.TestActivator Bundle-RequiredExecutionEnvironment: JavaSE-11 diff --git a/org.idempiere.test/src/org/idempiere/test/base/MatchPOTest.java b/org.idempiere.test/src/org/idempiere/test/base/MatchPOTest.java index 4b8a70fc2d..f892556461 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/MatchPOTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/MatchPOTest.java @@ -29,7 +29,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; +import java.sql.Timestamp; +import org.compiere.apps.form.Match; +import org.compiere.minigrid.ColumnInfo; +import org.compiere.minigrid.IDColumn; import org.compiere.model.MBPartner; import org.compiere.model.MDocType; import org.compiere.model.MInOut; @@ -41,12 +45,18 @@ import org.compiere.model.MMatchPO; import org.compiere.model.MOrder; import org.compiere.model.MOrderLine; import org.compiere.model.MProduct; +import org.compiere.model.MStorageOnHand; +import org.compiere.model.MStorageReservation; import org.compiere.model.MWarehouse; import org.compiere.process.DocAction; import org.compiere.process.ProcessInfo; +import org.compiere.process.ServerProcessCtl; import org.compiere.util.Env; +import org.compiere.util.KeyNamePair; +import org.compiere.util.Msg; import org.compiere.wf.MWorkflow; import org.idempiere.test.AbstractTestCase; +import org.idempiere.test.ui.MiniTableImpl; import org.junit.jupiter.api.Test; /** @@ -484,4 +494,395 @@ public class MatchPOTest extends AbstractTestCase { rollback(); } + + @Test + public void testReverseFullyMatchPO() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // 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(); + + 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"); + + MInOut receipt = new MInOut(order, 122, 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(); + + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setOrderLine(orderLine, M_Locator_ID, orderQty); + receiptLine.setLine(10); + receiptLine.setQty(orderQty); + 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"); + int matchedPOReverse = 200016; + info = new ProcessInfo("MatchPOReverse", matchedPOReverse, MMatchPO.Table_ID, matchPOs[0].get_ID()); + ServerProcessCtl.process(info, getTrx(), false); + assertFalse(info.isError(), info.getSummary()); + + 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"); + receiptLine.load(getTrxName()); + assertEquals(0, receiptLine.getC_OrderLine_ID(), "Unexpected order line ID value for receipt line"); + matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName()); + assertEquals(2, matchPOs.length, "Unexpected number of MatchPO for order line"); + newOnOrdered = MStorageReservation.getQty(product.get_ID(), getM_Warehouse_ID(), 0, false, getTrxName()).intValue(); + assertEquals(initialOnOrdered+1, newOnOrdered, "Unexpected qty on ordered value"); + newOnHand = MStorageOnHand.getQtyOnHand(product.get_ID(), getM_Warehouse_ID(), 0, getTrxName()).intValue(); + assertEquals(initialOnHand+1, newOnHand, "Unexpected qty on hand value"); + + MiniTableImpl fromTable = new MiniTableImpl(); + MiniTableImpl toTable = new MiniTableImpl(); + ColumnInfo[] layout = new ColumnInfo[] { + new ColumnInfo(" ", ".", IDColumn.class, false, false, ""), + new ColumnInfo(Msg.translate(Env.getCtx(), "DocumentNo"), ".", String.class), // 1 + new ColumnInfo(Msg.translate(Env.getCtx(), "Date"), ".", Timestamp.class), + new ColumnInfo(Msg.translate(Env.getCtx(), "C_BPartner_ID"),".", KeyNamePair.class, "."), // 3 + new ColumnInfo(Msg.translate(Env.getCtx(), "Line"), ".", KeyNamePair.class, "."), + new ColumnInfo(Msg.translate(Env.getCtx(), "M_Product_ID"), ".", KeyNamePair.class, "."), // 5 + new ColumnInfo(Msg.translate(Env.getCtx(), "Qty"), ".", Double.class), + new ColumnInfo(Msg.translate(Env.getCtx(), "Matched"), ".", Double.class) + }; + fromTable.prepareTable(layout, null, null, false, null); + toTable.prepareTable(layout, null, null, false, null); + Match match = new Match(); + match.setTrxName(getTrxName()); + 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, 1); + if (receipt.getDocumentNo().equals(docNo)) { + int matched = ((Number)fromTable.getValueAt(i, 7)).intValue(); + assertEquals(0, matched, "Unexpected matched qty for Material Receipt line"); + int qty = ((Number)fromTable.getValueAt(i, 6)).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"); + fromTable.setSelectedRow(selectedRow); + match.cmd_searchTo(fromTable, toTable, match.getMatchTypeText(Match.MATCH_ORDER), Match.MATCH_SHIPMENT, true, true, false, false); + assertTrue(toTable.getRowCount()>0, "Unexpected number of records for not matched Order Line: " + fromTable.getRowCount()); + int selectedOrderRow = -1; + for(int i = 0; i < toTable.getRowCount(); i++) { + String docNo = (String)toTable.getValueAt(i, 1); + if (order.getDocumentNo().equals(docNo)) { + int matched = ((Number)toTable.getValueAt(i, 7)).intValue(); + assertEquals(0, matched, "Unexpected matched qty for PO line"); + int qty = ((Number)toTable.getValueAt(i, 6)).intValue(); + assertEquals(orderLine.getQtyOrdered().intValue(), qty, "Unexpected qty for PO line"); + selectedOrderRow = i; + break; + } + } + assertTrue(selectedOrderRow >= 0, "Can't find not matched PO line"); + + //create vendor invoice + MInvoice invoice = new MInvoice(order, MDocType.getOfDocBaseType(Env.getCtx(), MDocType.DOCBASETYPE_APInvoice)[0].getC_DocType_ID(), order.getDateAcct()); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setOrderLine(orderLine); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(orderQty); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + invoice.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + orderLine.load(getTrxName()); + assertEquals(invoiceLine.getQtyInvoiced().intValue(), orderLine.getQtyInvoiced().intValue(), "Unexpected order line qty invoiced"); + matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName()); + assertEquals(3, matchPOs.length, "Unexpected number of MatchPO for order line"); + + fromTable.prepareTable(layout, null, null, false, null); + toTable.prepareTable(layout, null, null, false, null); + 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()); + selectedRow = -1; + for(int i = 0; i < fromTable.getRowCount(); i++) { + String docNo = (String)fromTable.getValueAt(i, 1); + if (receipt.getDocumentNo().equals(docNo)) { + int matched = ((Number)fromTable.getValueAt(i, 7)).intValue(); + assertEquals(0, matched, "Unexpected matched qty for Material Receipt line"); + int qty = ((Number)fromTable.getValueAt(i, 6)).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"); + fromTable.setSelectedRow(selectedRow); + match.cmd_searchTo(fromTable, toTable, match.getMatchTypeText(Match.MATCH_ORDER), Match.MATCH_SHIPMENT, true, true, false, false); + assertTrue(toTable.getRowCount()>0, "Unexpected number of records for not matched Order Line: " + fromTable.getRowCount()); + selectedOrderRow = -1; + for(int i = 0; i < toTable.getRowCount(); i++) { + String docNo = (String)toTable.getValueAt(i, 1); + if (order.getDocumentNo().equals(docNo)) { + int matched = ((Number)toTable.getValueAt(i, 7)).intValue(); + assertEquals(0, matched, "Unexpected matched qty for PO line"); + int qty = ((Number)toTable.getValueAt(i, 6)).intValue(); + assertEquals(orderLine.getQtyOrdered().intValue(), qty, "Unexpected qty for PO line"); + selectedOrderRow = i; + break; + } + } + assertTrue(selectedOrderRow >= 0, "Can't find not matched PO line"); + + IDColumn idColumn = (IDColumn)toTable.getValueAt(selectedOrderRow, 0); + idColumn.setSelected(true); + match.cmd_process(fromTable, toTable, Match.MODE_NOTMATCHED, Match.MATCH_SHIPMENT, match.getMatchTypeText(Match.MATCH_ORDER), new BigDecimal(1)); + + 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"); + matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName()); + assertEquals(3, matchPOs.length, "Unexpected number of MatchPO for order line"); + newOnOrdered = MStorageReservation.getQty(product.get_ID(), getM_Warehouse_ID(), 0, false, getTrxName()).intValue(); + assertEquals(initialOnOrdered, newOnOrdered, "Unexpected qty on ordered value"); + newOnHand = MStorageOnHand.getQtyOnHand(product.get_ID(), getM_Warehouse_ID(), 0, getTrxName()).intValue(); + assertEquals(initialOnHand+1, newOnHand, "Unexpected qty on hand value"); + } + + @Test + public void testReversePartialMatchPO() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), 114); // Tree Farm Inc. + MProduct product = MProduct.get(Env.getCtx(), 124); // 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("2"); + 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(2, 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+2, newOnOrdered, "Unexpected qty on ordered value"); + + MInOut receipt = new MInOut(order, 122, 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(1, 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+1, 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"); + int matchedPOReverse = 200016; + info = new ProcessInfo("MatchPOReverse", matchedPOReverse, MMatchPO.Table_ID, matchPOs[0].get_ID()); + ServerProcessCtl.process(info, getTrx(), false); + assertFalse(info.isError(), info.getSummary()); + + orderLine.load(getTrxName()); + assertEquals(2, orderLine.getQtyReserved().intValue(), "Unexpected order line qty ordered value"); + assertEquals(0, orderLine.getQtyDelivered().intValue(), "Unexpected order line qty delivered value"); + receiptLine.load(getTrxName()); + assertEquals(0, receiptLine.getC_OrderLine_ID(), "Unexpected order line ID value for receipt line"); + matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName()); + assertEquals(2, matchPOs.length, "Unexpected number of MatchPO for order line"); + newOnOrdered = MStorageReservation.getQty(product.get_ID(), getM_Warehouse_ID(), 0, false, getTrxName()).intValue(); + assertEquals(initialOnOrdered+2, newOnOrdered, "Unexpected qty on ordered value"); + newOnHand = MStorageOnHand.getQtyOnHand(product.get_ID(), getM_Warehouse_ID(), 0, getTrxName()).intValue(); + assertEquals(initialOnHand+1, newOnHand, "Unexpected qty on hand value"); + + MiniTableImpl fromTable = new MiniTableImpl(); + MiniTableImpl toTable = new MiniTableImpl(); + ColumnInfo[] layout = new ColumnInfo[] { + new ColumnInfo(" ", ".", IDColumn.class, false, false, ""), + new ColumnInfo(Msg.translate(Env.getCtx(), "DocumentNo"), ".", String.class), // 1 + new ColumnInfo(Msg.translate(Env.getCtx(), "Date"), ".", Timestamp.class), + new ColumnInfo(Msg.translate(Env.getCtx(), "C_BPartner_ID"),".", KeyNamePair.class, "."), // 3 + new ColumnInfo(Msg.translate(Env.getCtx(), "Line"), ".", KeyNamePair.class, "."), + new ColumnInfo(Msg.translate(Env.getCtx(), "M_Product_ID"), ".", KeyNamePair.class, "."), // 5 + new ColumnInfo(Msg.translate(Env.getCtx(), "Qty"), ".", Double.class), + new ColumnInfo(Msg.translate(Env.getCtx(), "Matched"), ".", Double.class) + }; + fromTable.prepareTable(layout, null, null, false, null); + toTable.prepareTable(layout, null, null, false, null); + Match match = new Match(); + match.setTrxName(getTrxName()); + 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, 1); + if (receipt.getDocumentNo().equals(docNo)) { + int matched = ((Number)fromTable.getValueAt(i, 7)).intValue(); + assertEquals(0, matched, "Unexpected matched qty for Material Receipt line"); + int qty = ((Number)fromTable.getValueAt(i, 6)).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"); + fromTable.setSelectedRow(selectedRow); + match.cmd_searchTo(fromTable, toTable, match.getMatchTypeText(Match.MATCH_ORDER), Match.MATCH_SHIPMENT, true, true, false, false); + assertTrue(toTable.getRowCount()>0, "Unexpected number of records for not matched Order Line: " + fromTable.getRowCount()); + int selectedOrderRow = -1; + for(int i = 0; i < toTable.getRowCount(); i++) { + String docNo = (String)toTable.getValueAt(i, 1); + if (order.getDocumentNo().equals(docNo)) { + int matched = ((Number)toTable.getValueAt(i, 7)).intValue(); + assertEquals(0, matched, "Unexpected matched qty for PO line"); + int qty = ((Number)toTable.getValueAt(i, 6)).intValue(); + assertEquals(orderLine.getQtyOrdered().intValue(), qty, "Unexpected qty for PO line"); + selectedOrderRow = i; + break; + } + } + assertTrue(selectedOrderRow >= 0, "Can't find not matched PO line"); + + //create vendor invoice + MInvoice invoice = new MInvoice(order, MDocType.getOfDocBaseType(Env.getCtx(), MDocType.DOCBASETYPE_APInvoice)[0].getC_DocType_ID(), order.getDateAcct()); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setOrderLine(orderLine); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(orderQty); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + invoice.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + orderLine.load(getTrxName()); + assertEquals(invoiceLine.getQtyInvoiced().intValue(), orderLine.getQtyInvoiced().intValue(), "Unexpected order line qty invoiced"); + + fromTable.prepareTable(layout, null, null, false, null); + toTable.prepareTable(layout, null, null, false, null); + 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()); + selectedRow = -1; + for(int i = 0; i < fromTable.getRowCount(); i++) { + String docNo = (String)fromTable.getValueAt(i, 1); + if (receipt.getDocumentNo().equals(docNo)) { + int matched = ((Number)fromTable.getValueAt(i, 7)).intValue(); + assertEquals(0, matched, "Unexpected matched qty for Material Receipt line"); + int qty = ((Number)fromTable.getValueAt(i, 6)).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"); + fromTable.setSelectedRow(selectedRow); + match.cmd_searchTo(fromTable, toTable, match.getMatchTypeText(Match.MATCH_ORDER), Match.MATCH_SHIPMENT, true, true, false, false); + assertTrue(toTable.getRowCount()>0, "Unexpected number of records for not matched Order Line: " + fromTable.getRowCount()); + selectedOrderRow = -1; + for(int i = 0; i < toTable.getRowCount(); i++) { + String docNo = (String)toTable.getValueAt(i, 1); + if (order.getDocumentNo().equals(docNo)) { + int matched = ((Number)toTable.getValueAt(i, 7)).intValue(); + assertEquals(0, matched, "Unexpected matched qty for PO line"); + int qty = ((Number)toTable.getValueAt(i, 6)).intValue(); + assertEquals(orderLine.getQtyOrdered().intValue(), qty, "Unexpected qty for PO line"); + selectedOrderRow = i; + break; + } + } + assertTrue(selectedOrderRow >= 0, "Can't find not matched PO line"); + + IDColumn idColumn = (IDColumn)toTable.getValueAt(selectedOrderRow, 0); + idColumn.setSelected(true); + match.cmd_process(fromTable, toTable, Match.MODE_NOTMATCHED, Match.MATCH_SHIPMENT, match.getMatchTypeText(Match.MATCH_ORDER), new BigDecimal(1)); + + orderLine.load(getTrxName()); + assertEquals(1, 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"); + matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName()); + assertEquals(4, matchPOs.length, "Unexpected number of MatchPO for order line"); + newOnOrdered = MStorageReservation.getQty(product.get_ID(), getM_Warehouse_ID(), 0, false, getTrxName()).intValue(); + assertEquals(initialOnOrdered+1, newOnOrdered, "Unexpected qty on ordered value"); + newOnHand = MStorageOnHand.getQtyOnHand(product.get_ID(), getM_Warehouse_ID(), 0, getTrxName()).intValue(); + assertEquals(initialOnHand+1, newOnHand, "Unexpected qty on hand value"); + } } diff --git a/org.idempiere.test/src/org/idempiere/test/ui/MiniTableImpl.java b/org.idempiere.test/src/org/idempiere/test/ui/MiniTableImpl.java new file mode 100644 index 0000000000..657af1aa97 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/ui/MiniTableImpl.java @@ -0,0 +1,343 @@ +/*********************************************************************** + * 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.ui; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +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.model.PO; +import org.compiere.util.KeyNamePair; + +/** + * + * @author hengsin + * + */ +public class MiniTableImpl implements IMiniTable { + + /** Array of table details. */ + private List m_tableColumns = new ArrayList(); + + private List> model = new ArrayList>(); + + private ColumnInfo[] m_layout; + + private int m_keyColumnIndex; + + private int m_selectedRow; + + public MiniTableImpl() { + } + + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + @Override + public Object getValueAt(int row, int column) { + if (column < m_tableColumns.size()) { + String columnName = m_tableColumns.get(column).getHeaderValue(); + if (row < model.size()) { + return model.get(row).get(columnName); + } + } + return null; + } + + @Override + public void setValueAt(Object value, int row, int column) { + if (column < m_tableColumns.size()) { + String columnName = m_tableColumns.get(column).getHeaderValue(); + if (row < model.size()) { + model.get(row).put(columnName, value); + } + } + } + + @Override + public int convertColumnIndexToModel(int viewColumnIndex) { + return viewColumnIndex; + } + + @Override + public void setColumnReadOnly(int index, boolean readOnly) { + } + + @Override + public String prepareTable(ColumnInfo[] layout, String from, String where, boolean multiSelection, + String tableName) { + m_layout = layout; + m_tableColumns.clear(); + model.clear(); + for (int columnIndex = 0; columnIndex < layout.length; columnIndex++) { + addColumn(layout[columnIndex].getColHeader(), layout[columnIndex].getColDescription(), layout[columnIndex].getAD_Reference_ID(), layout[columnIndex].getColClass()); + if (layout[columnIndex].getColClass() == IDColumn.class) + { + m_keyColumnIndex = columnIndex; + } + } + return null; + } + + @Override + public void addColumn(String header) { + addColumn(header, null, 0, null); + } + + @Override + public void setColumnClass(int index, Class classType, boolean readOnly, String header) { + if (index < m_tableColumns.size()) { + m_tableColumns.get(index).setColumnClass(classType); + } + } + + @Override + public void setColumnClass(int index, Class classType, boolean readOnly) { + if (index < m_tableColumns.size()) { + m_tableColumns.get(index).setColumnClass(classType); + } + } + + @Override + public void loadTable(ResultSet rs) { + model.clear(); + try + { + while (rs.next()) + { + Map row = new HashMap(); + int rsColOffset = 1; + for (int col = 0; col < m_layout.length; col++) + { + //reset the data value + Object data = null; + Class columnClass = m_layout[col].getColClass(); + int rsColIndex = col + rsColOffset; + + if (columnClass == IDColumn.class) + { + data = new IDColumn(rs.getInt(rsColIndex)); + } + else if (columnClass == Boolean.class) + { + data = Boolean.valueOf(rs.getString(rsColIndex).equals("Y")); + } + else if (columnClass == Timestamp.class) + { + data = rs.getTimestamp(rsColIndex); + } + else if (columnClass == BigDecimal.class) + { + data = rs.getBigDecimal(rsColIndex); + } + else if (columnClass == Double.class) + { + data = Double.valueOf(rs.getDouble(rsColIndex)); + } + else if (columnClass == Integer.class) + { + data = Integer.valueOf(rs.getInt(rsColIndex)); + } + else if (columnClass == KeyNamePair.class) + { + String display = rs.getString(rsColIndex); + int key = rs.getInt(rsColIndex + 1); + data = new KeyNamePair(key, display); + rsColOffset++; + } + else + { + String s = rs.getString(rsColIndex); + if (s != null) + { + data = s.trim(); // problems with NCHAR + } + else + { + data=null; + } + } + // store in underlying model + row.put(m_tableColumns.get(col).headerValue, data); + } + model.add(row); + } + } + catch (SQLException exception) + { + throw new RuntimeException(exception); + } + + } + + @Override + public void loadTable(PO[] pos) { + model.clear(); + for (int poIndex = 0; poIndex < pos.length; poIndex++) + { + PO myPO = pos[poIndex]; + Map row = new HashMap(); + + for (int col = 0; col < m_layout.length; col++) + { + String columnName = m_layout[col].getColSQL(); + Object data = myPO.get_Value(columnName); + if (data != null) + { + Class columnClass = m_layout[col].getColClass(); + + if (columnClass == IDColumn.class) + { + data = new IDColumn(((Integer)data).intValue()); + } + else if (columnClass == Double.class) + { + data = Double.valueOf(((BigDecimal)data).doubleValue()); + } + } + // store + row.put(m_tableColumns.get(col).headerValue, data); + } + model.add(row); + } + } + + @Override + public Integer getSelectedRowKey() { + int row = getSelectedRow(); + + // make common function + return getRowKeyAt (row); + } + + /** + * get key of record at index + * @param index + * @return + */ + public Integer getRowKeyAt (int index){ + if (index < 0 || m_keyColumnIndex < 0 || index >= model.size()) + return null; + + + Object data = model.get(index).get(m_tableColumns.get(m_keyColumnIndex).headerValue); + + if (data instanceof IDColumn) + { + data = ((IDColumn)data).getRecord_ID(); + } + if (data instanceof Integer) + { + return (Integer)data; + } + return null; + } + + @Override + public int getSelectedRow() { + return m_selectedRow; + } + + public void setSelectedRow(int selected) { + if (selected < model.size()) + m_selectedRow = selected; + } + + @Override + public void setRowCount(int rowCount) { + } + + @Override + public ColumnInfo[] getLayoutInfo() { + return m_layout; + } + + @Override + public int getColumnCount() { + return m_tableColumns.size(); + } + + @Override + public int getRowCount() { + return model.size(); + } + + @Override + public void setMultiSelection(boolean multiSelection) { + } + + @Override + public boolean isMultiSelection() { + return false; + } + + @Override + public int getColorCode(int row) { + return 0; + } + + @Override + public void setColorCompare(Object dataCompare) { + } + + @Override + public void repaint() { + } + + @Override + public void autoSize() { + } + + @Override + public void setShowTotals(boolean show) { + } + + /** + * Add Table Column and specify the column header. + * + * @param header name of column header + * @param description + * @param colClass + */ + public void addColumn (String header, String description, int AD_Reference_ID, Class colClass) + { + TableColumn column = new TableColumn(); + column.setHeaderValue(header); + column.setTooltipText(description); + column.setAD_Reference_ID(AD_Reference_ID); + column.setColumnClass(colClass); + m_tableColumns.add(column); + } // addColumn +} diff --git a/org.idempiere.test/src/org/idempiere/test/ui/TableColumn.java b/org.idempiere.test/src/org/idempiere/test/ui/TableColumn.java new file mode 100644 index 0000000000..83925d671e --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/ui/TableColumn.java @@ -0,0 +1,122 @@ +/*********************************************************************** + * 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.ui; + +/** + * + * @author hengsin + * + */ +public class TableColumn +{ + /** The header value of the column. */ + protected String headerValue; + + protected Class columnClass; + + protected String tooltipText; + + private int AD_Reference_ID; + + /** + * Cover method, using a default width of 75 + * @see #WTableColumn(int) + */ + public TableColumn() { + headerValue = null; + } + + + /** + * Sets the Object whose string representation will be + * used as the value for the headerRenderer. When the + * WTableColumn is created, the default headerValue + * is null. + * + * @param headerValue the new headerValue + * @see #getHeaderValue + */ + public void setHeaderValue(String headerValue) + { + this.headerValue = headerValue; + + return; + } + + + /** + * Returns the Object used as the value for the header + * renderer. + * + * @return the headerValue property + * @see #setHeaderValue + */ + public String getHeaderValue() + { + return headerValue; + } + + + /** + * + * @return Class + */ + public Class getColumnClass() + { + return columnClass; + } + + /** + * + * @param columnClass + */ + public void setColumnClass(Class columnClass) + { + this.columnClass = columnClass; + } + + /** + * @return tooltip text + */ + public String getTooltipText() { + return tooltipText; + } + + /** + * @param tooltipText + */ + public void setTooltipText(String tooltipText) { + this.tooltipText = tooltipText; + } + + public int getAD_Reference_ID() { + return AD_Reference_ID; + } + + public void setAD_Reference_ID(int AD_Reference_ID) { + this.AD_Reference_ID=AD_Reference_ID; + } +}