From 3641e398ca5185b006a893b8b3246ab5a02e6c47 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 22 Jun 2023 08:00:27 +0200 Subject: [PATCH] IDEMPIERE-5776 Cannot generate shipment for an order with multiple lines for same product and Complete Order delivery rule (#1900) --- .../org/compiere/process/InOutGenerate.java | 26 +++++-- .../org/compiere/model/MStorageOnHand.java | 56 +++++++-------- .../idempiere/test/model/SalesOrderTest.java | 70 +++++++++++++++++++ 3 files changed, 118 insertions(+), 34 deletions(-) diff --git a/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java b/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java index ab67d0466b..a6277683f9 100644 --- a/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java +++ b/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java @@ -362,6 +362,10 @@ public class InOutGenerate extends SvrProcess { MStorageOnHand storage = storages[j]; onHand = onHand.add(storage.getQtyOnHand()); + if (completeOrder && j == 0) { + // CompleteOrder is created at the end, so we need to subtract here to keep track of what is "consumed" + storage.setQtyOnHand(storage.getQtyOnHand().subtract(toDeliver)); + } } boolean autoProduce = product.isBOM() && product.isVerified() && product.isAutoProduce(); boolean fullLine = onHand.compareTo(toDeliver) >= 0 @@ -426,6 +430,9 @@ public class InOutGenerate extends SvrProcess // Complete Order successful if (completeOrder && MOrder.DELIVERYRULE_CompleteOrder.equals(order.getDeliveryRule())) { + // reset storage cache - it was updated in memory above + resetStorageCache(); + for (int i = 0; i < lines.length; i++) { MOrderLine line = lines[i]; @@ -637,8 +644,17 @@ public class InOutGenerate extends SvrProcess } return m_lastStorages; } // getStorages - - + + /** + * Reset in memory map, array and parameters + */ + public void resetStorageCache() + { + m_map = new HashMap(); + m_lastPP = null; + m_lastStorages = null; + } + /** * Complete Shipment */ @@ -658,11 +674,9 @@ public class InOutGenerate extends SvrProcess String message = Msg.parseTranslation(getCtx(), "@ShipmentProcessed@ " + m_shipment.getDocumentNo()); addBufferLog(m_shipment.getM_InOut_ID(), m_shipment.getMovementDate(), null, message, m_shipment.get_Table_ID(),m_shipment.getM_InOut_ID()); m_created++; - + //reset storage cache as MInOut.completeIt will update m_storage - m_map = new HashMap(); - m_lastPP = null; - m_lastStorages = null; + resetStorageCache(); } m_shipment = null; m_line = 0; diff --git a/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java b/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java index 804b226a5f..44bb1d073c 100644 --- a/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java +++ b/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java @@ -406,34 +406,38 @@ public class MStorageOnHand extends X_M_StorageOnHand allAttributeInstances = true; ArrayList list = new ArrayList(); - // Specific Attribute Set Instance - String sql = "SELECT s.M_Product_ID,s.M_Locator_ID,s.M_AttributeSetInstance_ID," - + "s.AD_Client_ID,s.AD_Org_ID,s.IsActive,s.Created,s.CreatedBy,s.Updated,s.UpdatedBy," - + "s.QtyOnHand,s.DateLastInventory,s.M_StorageOnHand_UU,s.DateMaterialPolicy " - + "FROM M_StorageOnHand s" - + " INNER JOIN M_Locator l ON (l.M_Locator_ID=s.M_Locator_ID) "; - if (M_Locator_ID > 0) - sql += "WHERE l.M_Locator_ID = ?"; - else - sql += "WHERE l.M_Warehouse_ID=?"; - sql += " AND s.M_Product_ID=?" - + " AND COALESCE(s.M_AttributeSetInstance_ID,0)=? "; - if (positiveOnly) + String sql; + if (! allAttributeInstances) { - sql += " AND s.QtyOnHand > 0 "; + // Specific Attribute Set Instance + sql = "SELECT s.M_Product_ID,s.M_Locator_ID,s.M_AttributeSetInstance_ID," + + "s.AD_Client_ID,s.AD_Org_ID,s.IsActive,s.Created,s.CreatedBy,s.Updated,s.UpdatedBy," + + "s.QtyOnHand,s.DateLastInventory,s.M_StorageOnHand_UU,s.DateMaterialPolicy " + + "FROM M_StorageOnHand s" + + " INNER JOIN M_Locator l ON (l.M_Locator_ID=s.M_Locator_ID) "; + if (M_Locator_ID > 0) + sql += "WHERE l.M_Locator_ID = ?"; + else + sql += "WHERE l.M_Warehouse_ID=?"; + sql += " AND s.M_Product_ID=?" + + " AND COALESCE(s.M_AttributeSetInstance_ID,0)=? "; + if (positiveOnly) + { + sql += " AND s.QtyOnHand > 0 "; + } + else + { + sql += " AND s.QtyOnHand <> 0 "; + } + sql += " ORDER BY l.PriorityNo DESC, DateMaterialPolicy "; + if (!FiFo) + sql += " DESC, s.M_AttributeSetInstance_ID DESC "; + else + sql += ", s.M_AttributeSetInstance_ID "; } else { - sql += " AND s.QtyOnHand <> 0 "; - } - sql += " ORDER BY l.PriorityNo DESC, DateMaterialPolicy "; - if (!FiFo) - sql += " DESC, s.M_AttributeSetInstance_ID DESC "; - else - sql += ", s.M_AttributeSetInstance_ID "; - // All Attribute Set Instances - if (allAttributeInstances) - { + // All Attribute Set Instances sql = "SELECT s.M_Product_ID,s.M_Locator_ID,s.M_AttributeSetInstance_ID," + " s.AD_Client_ID,s.AD_Org_ID,s.IsActive,s.Created,s.CreatedBy,s.Updated,s.UpdatedBy," + " s.QtyOnHand,s.DateLastInventory,s.M_StorageOnHand_UU,s.DateMaterialPolicy " @@ -453,14 +457,11 @@ public class MStorageOnHand extends X_M_StorageOnHand { sql += " AND s.QtyOnHand <> 0 "; } - if (minGuaranteeDate != null) { sql += " AND (asi.GuaranteeDate IS NULL OR asi.GuaranteeDate>?) "; } - MProduct product = MProduct.get(Env.getCtx(), M_Product_ID); - if(product.isUseGuaranteeDateForMPolicy()){ sql += " ORDER BY l.PriorityNo DESC, COALESCE(asi.GuaranteeDate,s.DateMaterialPolicy)"; if (!FiFo) @@ -476,7 +477,6 @@ public class MStorageOnHand extends X_M_StorageOnHand else sql += ", s.M_AttributeSetInstance_ID "; } - sql += ", s.QtyOnHand DESC"; } PreparedStatement pstmt = null; diff --git a/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java b/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java index 68dc35d188..4690096bd3 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java @@ -1526,4 +1526,74 @@ public class SalesOrderTest extends AbstractTestCase { } assertEquals(orderTaxes.length, match, "MOrdexTax record doesn't match child tax records"); } + + @Test + public void testGenerateShipmentCompleteMultiSameLine() { + // CompleteOrder with 2 lines with almost the available on hand + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + //Joe Block + order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id)); + order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + order.setDateOrdered(today); + order.setDatePromised(today); + order.saveEx(); + + BigDecimal qtyOnHandMinusOne = MStorageOnHand.getQtyOnHandForShipping(DictionaryIDs.M_Product.AZALEA_BUSH.id, order.getM_Warehouse_ID(), 0, getTrxName()); + if (qtyOnHandMinusOne.signum() > 0) + qtyOnHandMinusOne = qtyOnHandMinusOne.subtract(Env.ONE); + + MOrderLine line1 = new MOrderLine(order); + line1.setLine(10); + line1.setProduct(MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id)); + line1.setQty(qtyOnHandMinusOne); + line1.setDatePromised(today); + line1.saveEx(); + + MOrderLine line2 = new MOrderLine(order); + line2.setLine(20); + line2.setProduct(MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id)); + line2.setQty(qtyOnHandMinusOne); + line2.setDatePromised(today); + line2.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + order.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + int AD_Process_ID = PROCESS_M_INOUT_GENERATE_MANUAL; + MPInstance instance = new MPInstance(Env.getCtx(), AD_Process_ID, 0); + instance.saveEx(); + + String insert = "INSERT INTO T_SELECTION(AD_PINSTANCE_ID, T_SELECTION_ID) Values (?, ?)"; + DB.executeUpdateEx(insert, new Object[] {instance.getAD_PInstance_ID(), order.getC_Order_ID()}, null); + + //call process + ProcessInfo pi = new ProcessInfo ("InOutGen", AD_Process_ID); + pi.setAD_PInstance_ID (instance.getAD_PInstance_ID()); + + // Add Parameter - Selection=Y + MPInstancePara ip = new MPInstancePara(instance, 10); + ip.setParameter("Selection","Y"); + ip.saveEx(); + //Add Document action parameter + ip = new MPInstancePara(instance, 20); + ip.setParameter("DocAction", "PR"); + ip.saveEx(); + // Add Parameter - M_Warehouse_ID=x + ip = new MPInstancePara(instance, 30); + ip.setParameter("M_Warehouse_ID", getM_Warehouse_ID()); + ip.saveEx(); + + ServerProcessCtl processCtl = new ServerProcessCtl(pi, getTrx()); + processCtl.setManagedTrxForJavaProcess(false); + processCtl.run(); + + assertFalse(pi.isError(), pi.getSummary()); + } + }