From e46d57e1ea25dde8105edc9d4b1e92f95e9e21be Mon Sep 17 00:00:00 2001 From: hengsin Date: Tue, 8 Aug 2023 20:02:28 +0800 Subject: [PATCH] IDEMPIERE-5812 Reversal of Material Receipt for a Closed PO leave quantity ordered of product in a bad state (#1964) --- .../src/org/compiere/model/MInOut.java | 16 ++-- .../src/org/compiere/model/MOrderLine.java | 15 ++++ .../org/idempiere/test/base/MatchPOTest.java | 88 +++++++++++++++++++ 3 files changed, 111 insertions(+), 8 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/MInOut.java b/org.adempiere.base/src/org/compiere/model/MInOut.java index 584b20c086..a4cd8f8b48 100644 --- a/org.adempiere.base/src/org/compiere/model/MInOut.java +++ b/org.adempiere.base/src/org/compiere/model/MInOut.java @@ -1643,8 +1643,8 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess if (log.isLoggable(Level.FINE)) log.fine("OrderLine - Reserved=" + oLine.getQtyReserved() + ", Delivered=" + oLine.getQtyDelivered()); } - - + boolean orderClosed = oLine != null && DocAction.STATUS_Closed.equals(oLine.getParent().getDocStatus()); + // Load RMA Line MRMALine rmaLine = null; @@ -1726,7 +1726,7 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess return status; } - // Update Storage - see also VMatch.createMatchRecord + // Update Storage - see also Match.createMatchRecord if (!MStorageOnHand.add(getCtx(), sLine.getM_Locator_ID(), sLine.getM_Product_ID(), @@ -1760,8 +1760,8 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess return status; } } - - if (oLine!=null && mtrx!=null && + + if (oLine!=null && mtrx!=null && !orderClosed && ((!isReversal() && oLine.getQtyReserved().signum() > 0) || (isReversal() && oLine.getQtyOrdered().signum() > 0))) { if (sLine.getC_OrderLine_ID() != 0 && oLine.getM_Product_ID() > 0) @@ -1847,7 +1847,7 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess if (dateMPolicy == null) dateMPolicy = getMovementDate(); - // Fallback: Update Storage - see also VMatch.createMatchRecord + // Fallback: Update Storage - see also Match.createMatchRecord if (pendingQty.signum() != 0 && !MStorageOnHand.add(getCtx(), sLine.getM_Locator_ID(), @@ -1859,7 +1859,7 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess m_processMsg = "Cannot correct Inventory OnHand [" + product.getValue() + "] - " + lastError; return DocAction.STATUS_Invalid; } - if (oLine!=null && oLine.getM_Product_ID() > 0 && + if (oLine!=null && oLine.getM_Product_ID() > 0 && !orderClosed && ((!isReversal() && oLine.getQtyReserved().signum() > 0) || (isReversal() && oLine.getQtyOrdered().signum() > 0))) { IReservationTracer tracer = null; @@ -1903,7 +1903,7 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess } // stock movement // Correct Order Line - if (product != null && oLine != null) // other in VMatch.createMatchRecord + if (product != null && oLine != null && !orderClosed) // other in Match.createMatchRecord { if (oLine.getQtyOrdered().signum() >= 0) { diff --git a/org.adempiere.base/src/org/compiere/model/MOrderLine.java b/org.adempiere.base/src/org/compiere/model/MOrderLine.java index 08f5aad419..fa09435c3a 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrderLine.java +++ b/org.adempiere.base/src/org/compiere/model/MOrderLine.java @@ -29,6 +29,7 @@ import org.adempiere.base.IProductPricing; import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.ProductNotOnPriceListException; import org.adempiere.model.ITaxProvider; +import org.compiere.process.DocAction; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.Env; @@ -897,6 +898,20 @@ public class MOrderLine extends X_C_OrderLine } } + //sync qtyordered and qtylostsales for closed order + if (!newRecord && DocAction.STATUS_Closed.equals(getParent().getDocStatus()) && is_ValueChanged(COLUMNNAME_QtyDelivered) + && !getParent().is_ValueChanged(MOrder.COLUMNNAME_DocStatus)) { + if (getQtyOrdered().compareTo(getQtyDelivered()) > 0) + { + setQtyLostSales(getQtyLostSales().add(getQtyOrdered().subtract(getQtyDelivered()))); + setQtyOrdered(getQtyDelivered()); + } + else + { + setQtyLostSales(Env.ZERO); + } + } + return true; } // beforeSave 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 57eef26571..ebc0c3707a 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/MatchPOTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/MatchPOTest.java @@ -960,4 +960,92 @@ public class MatchPOTest extends AbstractTestCase { receipt.load(getTrxName()); assertEquals(0, receipt.getC_Order_ID(), "Material receipt: order not clear after void of purchase order"); } + + @Test + public void testReverseReceiptAfterClosePO() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id); + MProduct product = MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.MULCH.id); + + //Create PO of 4 + 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(); + + MOrderLine orderLine = new MOrderLine(order); + orderLine.setLine(10); + orderLine.setProduct(product); + orderLine.setQty(new BigDecimal("4")); + orderLine.saveEx(); + + BigDecimal qtyOrdered = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName()); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + order.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + BigDecimal qtyOrdered1 = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName()); + assertEquals(4, qtyOrdered1.subtract(qtyOrdered).intValue(), "QtyOrdered not increase as expected"); + + //Create MR + 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(); + + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setOrderLine(orderLine, M_Locator_ID, new BigDecimal("2")); + receiptLine.setLine(10); + receiptLine.setQty(new BigDecimal("2")); + receiptLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus()); + + qtyOrdered1 = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName()); + assertEquals(qtyOrdered.intValue()+2, qtyOrdered1.intValue(), "QtyOrdered not release as expected"); + + orderLine.load(getTrxName()); + assertEquals(2, orderLine.getQtyDelivered().intValue(), "Unexpected QtyDelivered"); + assertEquals(2, orderLine.getQtyReserved().intValue(), "Unexpected QtyReserved"); + + //Close PO + order.load(getTrxName()); + info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Close); + assertFalse(info.isError(), info.getSummary()); + order.load(getTrxName()); + assertEquals(DocAction.STATUS_Closed, order.getDocStatus()); + orderLine.load(getTrxName()); + assertEquals(4, orderLine.getQtyEntered().intValue(), "Unexpected QtyEntered"); + assertEquals(2, orderLine.getQtyDelivered().intValue(), "Unexpected QtyDelivered"); + assertEquals(2, orderLine.getQtyOrdered().intValue(), "Unexpected QtyOrdered"); + assertEquals(2, orderLine.getQtyLostSales().intValue(), "Unexpected QtyLostSales"); + assertEquals(0, orderLine.getQtyReserved().intValue(), "Unexpected QtyReserved"); + + qtyOrdered1 = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName()); + assertEquals(qtyOrdered.intValue(), qtyOrdered1.intValue(), "Unexpected change in QtyOrdered"); + + //Reverse MR + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Reverse_Accrual); + assertFalse(info.isError(), info.getSummary()); + receipt.load(getTrxName()); + assertEquals(DocAction.STATUS_Reversed, receipt.getDocStatus()); + + orderLine.load(getTrxName()); + assertEquals(4, orderLine.getQtyEntered().intValue(), "Unexpected QtyEntered"); + assertEquals(0, orderLine.getQtyDelivered().intValue(), "Unexpected QtyDelivered"); + assertEquals(0, orderLine.getQtyOrdered().intValue(), "Unexpected QtyOrdered"); + assertEquals(4, orderLine.getQtyLostSales().intValue(), "Unexpected QtyLostSales"); + assertEquals(0, orderLine.getQtyReserved().intValue(), "Unexpected QtyReserved"); + + qtyOrdered1 = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName()); + assertEquals(qtyOrdered.intValue(), qtyOrdered1.intValue(), "Unexpected change in QtyOrdered"); + } }