IDEMPIERE-4659 MStorageOnHand and MStorageReservation api improvements (#545)

This commit is contained in:
hengsin 2021-01-25 00:30:51 +08:00 committed by GitHub
parent 0046696a38
commit 9370dbb77e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 238 additions and 7 deletions

View File

@ -189,12 +189,24 @@ public class MLocator extends X_M_Locator implements ImmutablePOSupport
* @return MLocator * @return MLocator
*/ */
public static MLocator get (Properties ctx, int M_Locator_ID) 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); Integer key = Integer.valueOf(M_Locator_ID);
MLocator retValue = s_cache.get (ctx, key, e -> new MLocator(ctx, e)); MLocator retValue = s_cache.get (ctx, key, e -> new MLocator(ctx, e));
if (retValue != null) if (retValue != null)
return retValue; 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) if (retValue.get_ID () == M_Locator_ID)
{ {
s_cache.put (key, retValue, e -> new MLocator(Env.getCtx(), e)); s_cache.put (key, retValue, e -> new MLocator(Env.getCtx(), e));

View File

@ -858,7 +858,7 @@ public class MStorageOnHand extends X_M_StorageOnHand
{ {
if (m_M_Warehouse_ID == 0) 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(); m_M_Warehouse_ID = loc.getM_Warehouse_ID();
} }
return m_M_Warehouse_ID; return m_M_Warehouse_ID;
@ -973,7 +973,39 @@ public class MStorageOnHand extends X_M_StorageOnHand
params.add(M_AttributeSetInstance_ID); 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<Object> params = new ArrayList<Object>();
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) if (qty == null)
qty = Env.ZERO; qty = Env.ZERO;

View File

@ -158,15 +158,15 @@ public class MStorageReservation extends X_M_StorageReservation {
} // getOfProduct } // getOfProduct
/** /**
* Get Quantity Reserved of Warehouse * Get Quantity Reserved/Ordered of Warehouse
* @param M_Product_ID * @param M_Product_ID
* @param M_Warehouse_ID * @param M_Warehouse_ID
* @param M_AttributeSetInstance_ID * @param M_AttributeSetInstance_ID
* @param isSOTrx - true to get reserved, false to get ordered * @param isSOTrx - true to get reserved, false to get ordered
* @param trxName * @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<Object> params = new ArrayList<Object>(); ArrayList<Object> params = new ArrayList<Object>();
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
sql.append(" SELECT SUM(Qty) FROM M_StorageReservation sr") 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); params.add(M_AttributeSetInstance_ID);
} }
BigDecimal qty = DB.getSQLValueBD(trxName, sql.toString(), params); BigDecimal qty = DB.getSQLValueBDEx(trxName, sql.toString(), params);
if (qty==null) if (qty==null)
qty = Env.ZERO; qty = Env.ZERO;

View File

@ -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());
}
}