From 890862de47407d0f60b11464ae769aba10ac79a9 Mon Sep 17 00:00:00 2001 From: hengsin Date: Mon, 31 Jul 2023 21:20:26 +0800 Subject: [PATCH] =?UTF-8?q?IDEMPIERE-4416=20invalidating=20a=20purchase=20?= =?UTF-8?q?order=20doesn't=20remove=20connectio=E2=80=A6=20(#1942)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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. --- .../src/org/compiere/model/MInOut.java | 238 +++++++++++- .../src/org/compiere/model/MInOutLine.java | 142 +++++++ .../src/org/compiere/model/MInvoice.java | 122 +++++- .../src/org/compiere/model/MMatchPO.java | 18 + .../src/org/compiere/model/MOrder.java | 293 ++++++++++++++- .../server.product.launch | 2 +- .../src/org/compiere/apps/form/Match.java | 288 ++++---------- .../org/compiere/minigrid}/MiniTableImpl.java | 15 +- .../org/compiere/minigrid}/TableColumn.java | 2 +- org.idempiere.test/idempiere.unit.test.launch | 2 +- .../org/idempiere/test/base/MatchPOTest.java | 112 +++++- .../test/form/AllocationFormTest.java | 2 +- .../idempiere/test/form/ChargeFormTest.java | 2 +- .../form/CreateFromDepositBatchFormTest.java | 2 +- .../test/form/CreateFromInvoiceFormTest.java | 2 +- .../CreateFromPackageShipmentFormTest.java | 2 +- .../test/form/CreateFromRMAFormTest.java | 2 +- .../test/form/CreateFromShipmentFormTest.java | 2 +- .../form/CreateFromStatementFormTest.java | 2 +- .../test/form/FactReconcileFormTest.java | 2 +- .../idempiere/test/form/MatchFormTest.java | 279 +++++++++++++- .../test/form/PaySelectFormTest.java | 2 +- .../idempiere/test/model/MatchingTest.java | 355 ++++++++++++++++++ 23 files changed, 1629 insertions(+), 259 deletions(-) rename {org.idempiere.test/src/org/idempiere/test/ui => org.adempiere.ui/src/org/compiere/minigrid}/MiniTableImpl.java (96%) rename {org.idempiere.test/src/org/idempiere/test/ui => org.adempiere.ui/src/org/compiere/minigrid}/TableColumn.java (99%) create mode 100644 org.idempiere.test/src/org/idempiere/test/model/MatchingTest.java diff --git a/org.adempiere.base/src/org/compiere/model/MInOut.java b/org.adempiere.base/src/org/compiere/model/MInOut.java index fbfb3ec209..584b20c086 100644 --- a/org.adempiere.base/src/org/compiere/model/MInOut.java +++ b/org.adempiere.base/src/org/compiere/model/MInOut.java @@ -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 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 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 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 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 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 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 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 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 diff --git a/org.adempiere.base/src/org/compiere/model/MInOutLine.java b/org.adempiere.base/src/org/compiere/model/MInOutLine.java index 15c20fa768..df8118cc59 100644 --- a/org.adempiere.base/src/org/compiere/model/MInOutLine.java +++ b/org.adempiere.base/src/org/compiere/model/MInOutLine.java @@ -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 diff --git a/org.adempiere.base/src/org/compiere/model/MInvoice.java b/org.adempiere.base/src/org/compiere/model/MInvoice.java index 4c04c2157c..23b6d37b3a 100644 --- a/org.adempiere.base/src/org/compiere/model/MInvoice.java +++ b/org.adempiere.base/src/org/compiere/model/MInvoice.java @@ -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 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 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 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 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 diff --git a/org.adempiere.base/src/org/compiere/model/MMatchPO.java b/org.adempiere.base/src/org/compiere/model/MMatchPO.java index 369f80ce57..1b9f9fef21 100644 --- a/org.adempiere.base/src/org/compiere/model/MMatchPO.java +++ b/org.adempiere.base/src/org/compiere/model/MMatchPO.java @@ -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 diff --git a/org.adempiere.base/src/org/compiere/model/MOrder.java b/org.adempiere.base/src/org/compiere/model/MOrder.java index 0301f82d98..433139775d 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrder.java +++ b/org.adempiere.base/src/org/compiere/model/MOrder.java @@ -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 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 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 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 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 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 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 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 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 diff --git a/org.adempiere.server-feature/server.product.launch b/org.adempiere.server-feature/server.product.launch index 17c348b45e..c7a5073e90 100644 --- a/org.adempiere.server-feature/server.product.launch +++ b/org.adempiere.server-feature/server.product.launch @@ -258,7 +258,7 @@ - + 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 1c7948128d..fcad972a34 100644 --- a/org.adempiere.ui/src/org/compiere/apps/form/Match.java +++ b/org.adempiere.ui/src/org/compiere/apps/form/Match.java @@ -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 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. *
- * 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=..
+ * The line table is "lin", e.g. for lin.M_Product_ID=..
* You use the dateColumn/qtyColumn variable directly as it is table specific. *
- * The sql is dependent on MatchMode: - * - If Matched - all (fully or partially) matched records are listed + * The sql is dependent on MatchMode:
+ * - If Matched - all (fully or partially) matched records are listed
* - 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 diff --git a/org.idempiere.test/src/org/idempiere/test/ui/MiniTableImpl.java b/org.adempiere.ui/src/org/compiere/minigrid/MiniTableImpl.java similarity index 96% rename from org.idempiere.test/src/org/idempiere/test/ui/MiniTableImpl.java rename to org.adempiere.ui/src/org/compiere/minigrid/MiniTableImpl.java index 0f5c6a6853..08fb243538 100644 --- a/org.idempiere.test/src/org/idempiere/test/ui/MiniTableImpl.java +++ b/org.adempiere.ui/src/org/compiere/minigrid/MiniTableImpl.java @@ -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}.
+ * 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; diff --git a/org.idempiere.test/src/org/idempiere/test/ui/TableColumn.java b/org.adempiere.ui/src/org/compiere/minigrid/TableColumn.java similarity index 99% rename from org.idempiere.test/src/org/idempiere/test/ui/TableColumn.java rename to org.adempiere.ui/src/org/compiere/minigrid/TableColumn.java index 83925d671e..5670c1132b 100644 --- a/org.idempiere.test/src/org/idempiere/test/ui/TableColumn.java +++ b/org.adempiere.ui/src/org/compiere/minigrid/TableColumn.java @@ -23,7 +23,7 @@ * - hengsin * **********************************************************************/ -package org.idempiere.test.ui; +package org.compiere.minigrid; /** * diff --git a/org.idempiere.test/idempiere.unit.test.launch b/org.idempiere.test/idempiere.unit.test.launch index 3763ddab85..4b661bca8f 100644 --- a/org.idempiere.test/idempiere.unit.test.launch +++ b/org.idempiere.test/idempiere.unit.test.launch @@ -255,7 +255,7 @@ - + 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 7a27b3bf0d..57eef26571 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/MatchPOTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/MatchPOTest.java @@ -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"); + } } diff --git a/org.idempiere.test/src/org/idempiere/test/form/AllocationFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/AllocationFormTest.java index 1c1f043e83..44306ee941 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/AllocationFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/AllocationFormTest.java @@ -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; /** diff --git a/org.idempiere.test/src/org/idempiere/test/form/ChargeFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/ChargeFormTest.java index fec2c61573..a8153f014e 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/ChargeFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/ChargeFormTest.java @@ -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; /** diff --git a/org.idempiere.test/src/org/idempiere/test/form/CreateFromDepositBatchFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/CreateFromDepositBatchFormTest.java index 0855606a29..6945703583 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/CreateFromDepositBatchFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/CreateFromDepositBatchFormTest.java @@ -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; /** diff --git a/org.idempiere.test/src/org/idempiere/test/form/CreateFromInvoiceFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/CreateFromInvoiceFormTest.java index 5d3a50fa76..853a99cd57 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/CreateFromInvoiceFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/CreateFromInvoiceFormTest.java @@ -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; /** diff --git a/org.idempiere.test/src/org/idempiere/test/form/CreateFromPackageShipmentFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/CreateFromPackageShipmentFormTest.java index c2fa77e361..e64ed4db93 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/CreateFromPackageShipmentFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/CreateFromPackageShipmentFormTest.java @@ -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; /** diff --git a/org.idempiere.test/src/org/idempiere/test/form/CreateFromRMAFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/CreateFromRMAFormTest.java index ffc3e3ae5d..614bd555d6 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/CreateFromRMAFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/CreateFromRMAFormTest.java @@ -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; /** diff --git a/org.idempiere.test/src/org/idempiere/test/form/CreateFromShipmentFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/CreateFromShipmentFormTest.java index cd8609308a..d0cb01e421 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/CreateFromShipmentFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/CreateFromShipmentFormTest.java @@ -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; /** diff --git a/org.idempiere.test/src/org/idempiere/test/form/CreateFromStatementFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/CreateFromStatementFormTest.java index 4db6436b6a..8fa8b5c056 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/CreateFromStatementFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/CreateFromStatementFormTest.java @@ -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; /** diff --git a/org.idempiere.test/src/org/idempiere/test/form/FactReconcileFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/FactReconcileFormTest.java index 375f217683..be93a718ac 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/FactReconcileFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/FactReconcileFormTest.java @@ -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; /** diff --git a/org.idempiere.test/src/org/idempiere/test/form/MatchFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/MatchFormTest.java index f4f45999af..30cb4b5450 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/MatchFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/MatchFormTest.java @@ -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"); + } } diff --git a/org.idempiere.test/src/org/idempiere/test/form/PaySelectFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/PaySelectFormTest.java index 1629f2f51e..ed8f76684d 100644 --- a/org.idempiere.test/src/org/idempiere/test/form/PaySelectFormTest.java +++ b/org.idempiere.test/src/org/idempiere/test/form/PaySelectFormTest.java @@ -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 { diff --git a/org.idempiere.test/src/org/idempiere/test/model/MatchingTest.java b/org.idempiere.test/src/org/idempiere/test/model/MatchingTest.java new file mode 100644 index 0000000000..ad90a51a97 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/model/MatchingTest.java @@ -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 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 optionalReceipt = notMatchList.stream().filter(m -> receipt.getDocumentNo().equals(m.documentNo())).findFirst(); + assertTrue(optionalReceipt.isPresent(), "Can't find not matched Material Receipt line"); + + List 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 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 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 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 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 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 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 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 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 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"); + } +}