From d654ed57914e5e58e09728eedec49e14fb26de7a Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Wed, 9 Dec 2020 20:49:01 +0100 Subject: [PATCH] IDEMPIERE-4587 Wrong ordered qty when reactivating a purchase order and setting a line to zero (#445) --- .../src/org/compiere/model/MInOut.java | 6 +- .../src/org/compiere/model/MOrder.java | 4 + .../test/model/PurchaseOrderTest.java | 119 ++++++++++++++++++ 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/MInOut.java b/org.adempiere.base/src/org/compiere/model/MInOut.java index 9bdceb27c4..0f2a7cc4fd 100644 --- a/org.adempiere.base/src/org/compiere/model/MInOut.java +++ b/org.adempiere.base/src/org/compiere/model/MInOut.java @@ -1416,7 +1416,7 @@ public class MInOut extends X_M_InOut implements DocAction } } - if (oLine!=null && mtrx!=null && oLine.getQtyOrdered().signum() > 0) + if (oLine!=null && mtrx!=null && oLine.getQtyOrdered().signum() >= 0) { if (sLine.getC_OrderLine_ID() != 0) { @@ -1467,7 +1467,7 @@ public class MInOut extends X_M_InOut implements DocAction m_processMsg = "Cannot correct Inventory OnHand [" + product.getValue() + "] - " + lastError; return DocAction.STATUS_Invalid; } - if (oLine!=null && oLine.getQtyOrdered().signum() > 0) + if (oLine!=null && oLine.getQtyOrdered().signum() >= 0) { if (!MStorageReservation.add(getCtx(), oLine.getM_Warehouse_ID(), sLine.getM_Product_ID(), @@ -1496,7 +1496,7 @@ public class MInOut extends X_M_InOut implements DocAction // Correct Order Line if (product != null && oLine != null) // other in VMatch.createMatchRecord { - if (oLine.getQtyOrdered().signum() > 0) + if (oLine.getQtyOrdered().signum() >= 0) { oLine.setQtyReserved(oLine.getQtyReserved().subtract(sLine.getMovementQty().subtract(sLine.getQtyOverReceipt()))); diff --git a/org.adempiere.base/src/org/compiere/model/MOrder.java b/org.adempiere.base/src/org/compiere/model/MOrder.java index 44585aab0a..4a14bee6c9 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrder.java +++ b/org.adempiere.base/src/org/compiere/model/MOrder.java @@ -2188,6 +2188,10 @@ public class MOrder extends X_C_Order implements DocAction MInOutLine ioLine = new MInOutLine(shipment); // Qty = Ordered - Delivered BigDecimal MovementQty = oLine.getQtyOrdered().subtract(oLine.getQtyDelivered()); + if (MovementQty.signum() == 0 && getProcessedOn().signum() != 0) { + // do not create lines with qty = 0 when the order is reactivated and completed again + continue; + } // Location int M_Locator_ID = MStorageOnHand.getM_Locator_ID (oLine.getM_Warehouse_ID(), oLine.getM_Product_ID(), oLine.getM_AttributeSetInstance_ID(), diff --git a/org.idempiere.test/src/org/idempiere/test/model/PurchaseOrderTest.java b/org.idempiere.test/src/org/idempiere/test/model/PurchaseOrderTest.java index 0ff41bd55a..0b88d6b215 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/PurchaseOrderTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/PurchaseOrderTest.java @@ -39,6 +39,8 @@ import org.compiere.model.MInvoiceLine; import org.compiere.model.MOrder; import org.compiere.model.MOrderLine; import org.compiere.model.MProduct; +import org.compiere.model.MStorageOnHand; +import org.compiere.model.MStorageReservation; import org.compiere.process.DocAction; import org.compiere.process.ProcessInfo; import org.compiere.util.Env; @@ -61,7 +63,10 @@ public class PurchaseOrderTest extends AbstractTestCase { private static final int DOCTYPE_AP_INVOICE = 123; private static final int PRODUCT_SEEDER = 143; private static final int PRODUCT_WEEDER = 141; + private static final int PRODUCT_MULCH = 137; private static final int USER_GARDENADMIN = 101; + private static final BigDecimal THREE = new BigDecimal("3"); + private static final BigDecimal MINUS_THREE = new BigDecimal("-3"); /** * https://idempiere.atlassian.net/browse/IDEMPIERE-4575 @@ -198,4 +203,118 @@ public class PurchaseOrderTest extends AbstractTestCase { } + /* + * IDEMPIERE-4587 + */ + @Test + public void testOrderedStorageForReactivatedOrder() { + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + BigDecimal qtyOrderedOriginal = getQtyOrdered(ctx, PRODUCT_MULCH, trxName); + + MOrder order = new MOrder(ctx, 0, trxName); + order.setBPartner(MBPartner.get(ctx, BP_PATIO)); + order.setC_DocTypeTarget_ID(DOCTYPE_PO); + order.setIsSOTrx(false); + order.setSalesRep_ID(USER_GARDENADMIN); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + order.setDateOrdered(today); + order.setDatePromised(today); + order.saveEx(); + + MOrderLine line1 = new MOrderLine(order); + line1.setLine(10); + line1.setProduct(MProduct.get(ctx, PRODUCT_MULCH)); + line1.setQty(THREE); + line1.setDatePromised(today); + line1.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError()); + order.load(trxName); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + line1.load(trxName); + assertEquals(0, line1.getQtyReserved().compareTo(THREE)); + + BigDecimal newQtyOrdered = getQtyOrdered(ctx, PRODUCT_MULCH, trxName); + assertEquals(0, qtyOrderedOriginal.add(THREE).compareTo(newQtyOrdered)); + + MInOut receipt1 = new MInOut(order, DOCTYPE_RECEIPT, order.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine receiptLine1 = new MInOutLine(receipt1); + receiptLine1.setOrderLine(line1, 0, THREE); + receiptLine1.setQty(THREE); + receiptLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError()); + receipt1.load(trxName); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + + line1.load(trxName); + assertEquals(0, line1.getQtyReserved().compareTo(Env.ZERO)); + + newQtyOrdered = getQtyOrdered(ctx, PRODUCT_MULCH, trxName); + assertEquals(0, qtyOrderedOriginal.compareTo(newQtyOrdered)); + + // reactivate the purchase order + info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_ReActivate); + assertFalse(info.isError()); + order.load(trxName); + assertEquals(DocAction.STATUS_InProgress, order.getDocStatus()); + + // change the line quantity to zero + line1.load(trxName); + line1.setQty(Env.ZERO); + line1.saveEx(); + + // complete the order again + info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError()); + order.load(trxName); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + line1.load(trxName); + assertEquals(0, line1.getQtyReserved().compareTo(MINUS_THREE)); + + newQtyOrdered = getQtyOrdered(ctx, PRODUCT_MULCH, trxName); + assertEquals(0, qtyOrderedOriginal.add(MINUS_THREE).compareTo(newQtyOrdered)); + + // create a new material receipt for the -3 reversed + MInOut receipt2 = new MInOut(order, DOCTYPE_RECEIPT, order.getDateOrdered()); + receipt2.setDocStatus(DocAction.STATUS_Drafted); + receipt2.setDocAction(DocAction.ACTION_Complete); + receipt2.saveEx(); + + MInOutLine receiptLine2 = new MInOutLine(receipt2); + receiptLine2.setOrderLine(line1, 0, MINUS_THREE); + receiptLine2.setQty(MINUS_THREE); + receiptLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt2, DocAction.ACTION_Complete); + assertFalse(info.isError()); + receipt2.load(trxName); + assertEquals(DocAction.STATUS_Completed, receipt2.getDocStatus()); + + line1.load(trxName); + assertEquals(0, line1.getQtyReserved().compareTo(Env.ZERO)); + + newQtyOrdered = getQtyOrdered(ctx, PRODUCT_MULCH, trxName); + assertEquals(0, qtyOrderedOriginal.compareTo(newQtyOrdered)); + } + + private BigDecimal getQtyOrdered(Properties ctx, int M_Product_ID, String trxName) { + BigDecimal qtyOrdered = Env.ZERO; + for (MStorageReservation rs : MStorageReservation.getOfProduct(ctx, M_Product_ID, trxName)) { + if (! rs.isSOTrx()) + qtyOrdered = qtyOrdered.add(rs.getQty()); + } + return qtyOrdered; + } + }