IDEMPIERE-4659 MStorageOnHand and MStorageReservation api improvements (#545)
This commit is contained in:
parent
0046696a38
commit
9370dbb77e
|
@ -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));
|
||||||
|
|
|
@ -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,13 +973,45 @@ 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)
|
if (qty == null)
|
||||||
qty = Env.ZERO;
|
qty = Env.ZERO;
|
||||||
|
|
||||||
return qty;
|
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)
|
||||||
|
qty = Env.ZERO;
|
||||||
|
|
||||||
|
return qty;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Quantity On Hand of Locator
|
* Get Quantity On Hand of Locator
|
||||||
* @param M_Product_ID
|
* @param M_Product_ID
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue