From 9370dbb77ec802930f00546950f913096080b162 Mon Sep 17 00:00:00 2001 From: hengsin Date: Mon, 25 Jan 2021 00:30:51 +0800 Subject: [PATCH] IDEMPIERE-4659 MStorageOnHand and MStorageReservation api improvements (#545) --- .../src/org/compiere/model/MLocator.java | 14 +- .../org/compiere/model/MStorageOnHand.java | 36 +++- .../compiere/model/MStorageReservation.java | 8 +- .../org/idempiere/test/base/MStorageTest.java | 187 ++++++++++++++++++ 4 files changed, 238 insertions(+), 7 deletions(-) create mode 100644 org.idempiere.test/src/org/idempiere/test/base/MStorageTest.java diff --git a/org.adempiere.base/src/org/compiere/model/MLocator.java b/org.adempiere.base/src/org/compiere/model/MLocator.java index 1ed2d654ff..be038ab121 100644 --- a/org.adempiere.base/src/org/compiere/model/MLocator.java +++ b/org.adempiere.base/src/org/compiere/model/MLocator.java @@ -189,12 +189,24 @@ public class MLocator extends X_M_Locator implements ImmutablePOSupport * @return MLocator */ public static MLocator get (Properties ctx, int M_Locator_ID) + { + return get(ctx, M_Locator_ID, (String)null); + } + + /** + * Get Locator from Cache (immutable) + * @param ctx context + * @param M_Locator_ID id + * @param trxName + * @return MLocator + */ + public static MLocator get (Properties ctx, int M_Locator_ID, String trxName) { Integer key = Integer.valueOf(M_Locator_ID); MLocator retValue = s_cache.get (ctx, key, e -> new MLocator(ctx, e)); if (retValue != null) return retValue; - retValue = new MLocator (ctx, M_Locator_ID, (String)null); + retValue = new MLocator (ctx, M_Locator_ID, trxName); if (retValue.get_ID () == M_Locator_ID) { s_cache.put (key, retValue, e -> new MLocator(Env.getCtx(), e)); diff --git a/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java b/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java index b0221b5a5d..b21ac9fb94 100644 --- a/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java +++ b/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java @@ -858,7 +858,7 @@ public class MStorageOnHand extends X_M_StorageOnHand { if (m_M_Warehouse_ID == 0) { - MLocator loc = MLocator.get(getCtx(), getM_Locator_ID()); + MLocator loc = MLocator.get(getCtx(), getM_Locator_ID(), get_TrxName()); m_M_Warehouse_ID = loc.getM_Warehouse_ID(); } return m_M_Warehouse_ID; @@ -973,13 +973,45 @@ public class MStorageOnHand extends X_M_StorageOnHand params.add(M_AttributeSetInstance_ID); } - BigDecimal qty = DB.getSQLValueBD(trxName, sql.toString(), params); + BigDecimal qty = DB.getSQLValueBDEx(trxName, sql.toString(), params); if (qty == null) qty = Env.ZERO; return qty; } + /** + * Get Quantity On Hand of Warehouse that's available for shipping + * @param M_Product_ID + * @param M_Warehouse_ID + * @param M_AttributeSetInstance_ID + * @param trxName + * @return QtyOnHand + */ + public static BigDecimal getQtyOnHandForShipping(int M_Product_ID, int M_Warehouse_ID, int M_AttributeSetInstance_ID, String trxName) { + StringBuilder sql = new StringBuilder(); + sql.append(" SELECT SUM(QtyOnHand) FROM M_StorageOnHand oh JOIN M_Locator loc ON (oh.M_Locator_ID=loc.M_Locator_ID)") + .append(" LEFT JOIN M_LocatorType lt ON (loc.M_LocatorType_ID=lt.M_LocatorType_ID)") + .append(" WHERE oh.M_Product_ID=?") + .append(" AND loc.M_Warehouse_ID=? AND COALESCE(lt.IsAvailableForShipping,'Y')='Y'"); + + ArrayList params = new ArrayList(); + params.add(M_Product_ID); + params.add(M_Warehouse_ID); + + // With ASI + if (M_AttributeSetInstance_ID != 0) { + sql.append(" AND oh.M_AttributeSetInstance_ID=?"); + params.add(M_AttributeSetInstance_ID); + } + + BigDecimal qty = DB.getSQLValueBDEx(trxName, sql.toString(), params); + if (qty == null) + qty = Env.ZERO; + + return qty; + } + /** * Get Quantity On Hand of Locator * @param M_Product_ID diff --git a/org.adempiere.base/src/org/compiere/model/MStorageReservation.java b/org.adempiere.base/src/org/compiere/model/MStorageReservation.java index 94fdbf3523..f4786abb27 100644 --- a/org.adempiere.base/src/org/compiere/model/MStorageReservation.java +++ b/org.adempiere.base/src/org/compiere/model/MStorageReservation.java @@ -158,15 +158,15 @@ public class MStorageReservation extends X_M_StorageReservation { } // getOfProduct /** - * Get Quantity Reserved of Warehouse + * Get Quantity Reserved/Ordered of Warehouse * @param M_Product_ID * @param M_Warehouse_ID * @param M_AttributeSetInstance_ID * @param isSOTrx - true to get reserved, false to get ordered * @param trxName - * @return + * @return quantity reserved/ordered */ - private static BigDecimal getQty(int M_Product_ID, int M_Warehouse_ID, int M_AttributeSetInstance_ID, boolean isSOTrx, String trxName) { + public static BigDecimal getQty(int M_Product_ID, int M_Warehouse_ID, int M_AttributeSetInstance_ID, boolean isSOTrx, String trxName) { ArrayList params = new ArrayList(); StringBuilder sql = new StringBuilder(); sql.append(" SELECT SUM(Qty) FROM M_StorageReservation sr") @@ -183,7 +183,7 @@ public class MStorageReservation extends X_M_StorageReservation { params.add(M_AttributeSetInstance_ID); } - BigDecimal qty = DB.getSQLValueBD(trxName, sql.toString(), params); + BigDecimal qty = DB.getSQLValueBDEx(trxName, sql.toString(), params); if (qty==null) qty = Env.ZERO; diff --git a/org.idempiere.test/src/org/idempiere/test/base/MStorageTest.java b/org.idempiere.test/src/org/idempiere/test/base/MStorageTest.java new file mode 100644 index 0000000000..5dcb023bec --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/base/MStorageTest.java @@ -0,0 +1,187 @@ +/*********************************************************************** + * 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.base; + +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.sql.Timestamp; +import java.util.Arrays; + +import org.compiere.model.MBPartner; +import org.compiere.model.MInOut; +import org.compiere.model.MInOutLine; +import org.compiere.model.MLocator; +import org.compiere.model.MLocatorType; +import org.compiere.model.MMovement; +import org.compiere.model.MMovementLine; +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; +import org.compiere.util.TimeUtil; +import org.compiere.wf.MWorkflow; +import org.idempiere.test.AbstractTestCase; +import org.junit.jupiter.api.Test; + +/** + * + * @author hengsin + * + */ +public class MStorageTest extends AbstractTestCase { + + private static final int BP_JOE_BLOCK = 118; + private static final int PRODUCT_AZALEA = 128; + + public MStorageTest() { + } + + @Test + public void testStorageOnHandAndReservation() { + MProduct azalea = MProduct.get(Env.getCtx(), PRODUCT_AZALEA); + + BigDecimal onhandForReservation = MStorageOnHand.getQtyOnHandForReservation(azalea.getM_Product_ID(), getM_Warehouse_ID(), 0, getTrxName()); + BigDecimal onhandForShipping = MStorageOnHand.getQtyOnHandForShipping(azalea.getM_Product_ID(), getM_Warehouse_ID(), 0, getTrxName()); + BigDecimal qtyReserved = MStorageReservation.getQty(azalea.getM_Product_ID(), getM_Warehouse_ID(), 0, true, getTrxName()); + BigDecimal availableForReservation = MStorageReservation.getQtyAvailable(getM_Warehouse_ID(), azalea.getM_Product_ID(), 0, getTrxName()); + + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + //Joe Block + order.setBPartner(MBPartner.get(Env.getCtx(), BP_JOE_BLOCK)); + 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(); + + MOrderLine line1 = new MOrderLine(order); + line1.setLine(10); + //Azalea Bush + line1.setProduct(azalea); + line1.setQty(new BigDecimal("1")); + line1.setDatePromised(today); + line1.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + order.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + line1.load(getTrxName()); + assertEquals(1, line1.getQtyReserved().intValue()); + + BigDecimal qtyReserved1 = MStorageReservation.getQty(azalea.getM_Product_ID(), getM_Warehouse_ID(), 0, true, getTrxName()); + assertTrue(qtyReserved1.compareTo(qtyReserved) > 0, "Qty reserved doesn't increase as expected (Before=" + qtyReserved.toPlainString() + " After=" + qtyReserved1.toPlainString()); + BigDecimal availableForReservation1 = MStorageReservation.getQtyAvailable(getM_Warehouse_ID(), azalea.getM_Product_ID(), 0, getTrxName()); + assertTrue(availableForReservation1.compareTo(availableForReservation) < 0, "Qty available for reservation doesn't reduce as expected (Before=" + availableForReservation.toPlainString() + " After=" + availableForReservation1.toPlainString()); + + MInOut shipment = new MInOut(order, 120, order.getDateOrdered()); + shipment.setDocStatus(DocAction.STATUS_Drafted); + shipment.setDocAction(DocAction.ACTION_Complete); + shipment.saveEx(); + + //over shipment + MInOutLine shipmentLine = new MInOutLine(shipment); + shipmentLine.setOrderLine(line1, 0, new BigDecimal("1")); + shipmentLine.setQty(new BigDecimal("1")); + shipmentLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus()); + + line1.load(getTrxName()); + assertEquals(0, line1.getQtyReserved().intValue()); + + BigDecimal onhandForReservation1 = MStorageOnHand.getQtyOnHandForReservation(azalea.getM_Product_ID(), getM_Warehouse_ID(), 0, getTrxName()); + assertTrue(onhandForReservation1.compareTo(onhandForReservation) < 0, "Qty on hand for reservation doesn't reduce as expected (Before=" + onhandForReservation.toPlainString() + " After=" + onhandForReservation1.toPlainString()); + + BigDecimal onhandForShipping1 = MStorageOnHand.getQtyOnHandForShipping(azalea.getM_Product_ID(), getM_Warehouse_ID(), 0, getTrxName()); + assertTrue(onhandForShipping1.compareTo(onhandForShipping) < 0, "Qty on hand for shipping doesn't reduce as expected (Before=" + onhandForShipping.toPlainString() + " After=" + onhandForShipping1.toPlainString()); + + MLocatorType reservedLocatorType = new MLocatorType(Env.getCtx(), 0, getTrxName()); + reservedLocatorType.setName("Reserved Locator1"); + reservedLocatorType.setIsAvailableForReservation(true); + reservedLocatorType.setIsAvailableForReplenishment(false); + reservedLocatorType.setIsAvailableForShipping(false); + reservedLocatorType.saveEx(); + + MLocatorType shippingLocatorType = new MLocatorType(Env.getCtx(), 0, getTrxName()); + shippingLocatorType.setName("Shipping Locator1"); + shippingLocatorType.setIsAvailableForReservation(false); + shippingLocatorType.setIsAvailableForReplenishment(false); + shippingLocatorType.setIsAvailableForShipping(true); + shippingLocatorType.saveEx(); + + MLocator shippingLocator = new MLocator(Env.getCtx(), 0, getTrxName()); + shippingLocator.setM_LocatorType_ID(shippingLocatorType.getM_LocatorType_ID()); + shippingLocator.setM_Warehouse_ID(getM_Warehouse_ID()); + shippingLocator.setXYZ("x1", "y1", "z1"); + shippingLocator.saveEx(); + + MMovement movement = new MMovement(Env.getCtx(), 0, getTrxName()); + //143 | Material Movement + movement.setC_DocType_ID(143); + movement.setDocAction(DocAction.ACTION_Complete); + movement.saveEx(); + + MStorageOnHand[] storages = MStorageOnHand.getWarehouse(Env.getCtx(), getM_Warehouse_ID(), azalea.getM_Product_ID(), 0, null, true, true, 0, getTrxName(), false); + final int[] line = new int[] {0}; + Arrays.stream(storages).forEach(e -> { + MMovementLine ml = new MMovementLine(movement); + ml.setM_Product_ID(azalea.getM_Product_ID()); + line[0] += 10; + ml.setLine(line[0]); + ml.setM_Locator_ID(e.getM_Locator_ID()); + ml.setM_LocatorTo_ID(shippingLocator.getM_Locator_ID()); + ml.setMovementQty(new BigDecimal("1")); + ml.saveEx(); + + MLocator locator = new MLocator(Env.getCtx(), e.getM_Locator_ID(), getTrxName()); + locator.setM_LocatorType_ID(reservedLocatorType.getM_LocatorType_ID()); + locator.saveEx(); + }); + info = MWorkflow.runDocumentActionWorkflow(movement, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + movement.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, movement.getDocStatus()); + + BigDecimal onhandForReservation2 = MStorageOnHand.getQtyOnHandForReservation(azalea.getM_Product_ID(), getM_Warehouse_ID(), 0, getTrxName()); + assertTrue(onhandForReservation2.compareTo(onhandForReservation1) < 0, "Qty on hand for reservation doesn't reduce as expected (Before=" + onhandForReservation1.toPlainString() + " After=" + onhandForReservation2.toPlainString()); + + BigDecimal onhandForShipping2 = MStorageOnHand.getQtyOnHandForShipping(azalea.getM_Product_ID(), getM_Warehouse_ID(), 0, getTrxName()); + assertTrue(onhandForShipping2.compareTo(onhandForShipping1) < 0, "Qty on hand for shipping doesn't reduce as expected (Before=" + onhandForShipping1.toPlainString() + " After=" + onhandForShipping2.toPlainString()); + } +}