IDEMPIERE-5138 Storage Cleanup Fixes (#1117)

* IDEMPIERE-5138 Storage Cleanup Fixes

* IDEMPIERE-5138 Storage Cleanup Fixes
This commit is contained in:
hengsin 2022-01-11 23:49:53 +08:00 committed by GitHub
parent 5843303a4d
commit ccdb4868f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 237 additions and 33 deletions

View File

@ -22,12 +22,14 @@ import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.logging.Level;
import org.compiere.model.MAttributeSetInstance;
import org.compiere.model.MMovement;
import org.compiere.model.MMovementLine;
import org.compiere.model.MRefList;
import org.compiere.model.MStorageOnHand;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Util;
/**
* StorageCleanup
@ -67,18 +69,27 @@ public class StorageCleanup extends SvrProcess
protected String doIt () throws Exception
{
log.info("");
// Clean up empty Storage
// Clean up empty Storage with no asi
String sql = "DELETE FROM M_StorageOnHand "
+ "WHERE QtyOnHand = 0"
+ "WHERE QtyOnHand = 0 AND M_AttributeSetInstance_ID=0 "
+ " AND Created < getDate()-3";
int no = DB.executeUpdate(sql, get_TrxName());
int no = DB.executeUpdateEx(sql, get_TrxName());
if (log.isLoggable(Level.INFO)) log.info("Delete Empty #" + no);
// Clean up empty Storage with asi but not using serial/lot
sql = "DELETE FROM M_StorageOnHand "
+ "WHERE QtyOnHand = 0 AND M_AttributeSetInstance_ID > 0 "
+ " AND Created < getDate()-3"
+ " AND EXISTS (SELECT 1 FROM M_AttributeSetInstance a WHERE a.M_AttributeSetInstance_ID=M_StorageOnHand.M_AttributeSetInstance_ID"
+ " AND a.Lot IS NULL AND a.SerNo IS NULL) ";
no = DB.executeUpdateEx(sql, get_TrxName());
if (log.isLoggable(Level.INFO)) log.info("Delete Empty #" + no);
// Clean up empty Reservation Storage
sql = "DELETE FROM M_StorageReservation "
+ "WHERE Qty = 0"
+ " AND Created < getDate()-3";
no = DB.executeUpdate(sql, get_TrxName());
no = DB.executeUpdateEx(sql, get_TrxName());
if (log.isLoggable(Level.INFO)) log.info("Delete Empty #" + no);
//
@ -97,14 +108,12 @@ public class StorageCleanup extends SvrProcess
+ " AND s.M_Product_ID=sw.M_Product_ID"
+ " AND s.M_Locator_ID=sl.M_Locator_ID"
+ " AND sl.M_Warehouse_ID=swl.M_Warehouse_ID)";
PreparedStatement pstmt = null;
ResultSet rs = null;
int lines = 0;
try
try (PreparedStatement pstmt = DB.prepareStatement (sql, get_TrxName()))
{
pstmt = DB.prepareStatement (sql, get_TrxName());
pstmt.setInt(1, Env.getAD_Client_ID(getCtx()));
rs = pstmt.executeQuery ();
ResultSet rs = pstmt.executeQuery ();
while (rs.next ())
{
lines += move (new MStorageOnHand(getCtx(), rs, get_TrxName()));
@ -114,11 +123,6 @@ public class StorageCleanup extends SvrProcess
{
log.log (Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
StringBuilder msgreturn = new StringBuilder("#").append(lines);
return msgreturn.toString();
} // doIt
@ -133,20 +137,42 @@ public class StorageCleanup extends SvrProcess
if (log.isLoggable(Level.INFO)) log.info(target.toString());
BigDecimal qty = target.getQtyOnHand().negate();
// Create Movement
MMovement mh = new MMovement (getCtx(), 0, get_TrxName());
mh.setAD_Org_ID(target.getAD_Org_ID());
mh.setC_DocType_ID(p_C_DocType_ID);
mh.setDescription(getName());
if (!mh.save())
return 0;
MMovement mh = null;
MAttributeSetInstance targetASI = null;
if (target.getM_AttributeSetInstance_ID() > 0)
{
targetASI = new MAttributeSetInstance(Env.getCtx(), target.getM_AttributeSetInstance_ID(), get_TrxName());
}
int lines = 0;
MStorageOnHand[] sources = getSources(target.getM_Product_ID(), target.getM_Locator_ID());
for (int i = 0; i < sources.length; i++)
{
MStorageOnHand source = sources[i];
//check serno and lot
if (source.getM_AttributeSetInstance_ID() > 0)
{
MAttributeSetInstance asi = new MAttributeSetInstance(Env.getCtx(), source.getM_AttributeSetInstance_ID(), get_TrxName());
if (!Util.isEmpty(asi.getSerNo(), true))
{
if (targetASI == null || !asi.getSerNo().equals(targetASI.getSerNo()))
continue;
}
if (!Util.isEmpty(asi.getLot(), true))
{
if (targetASI == null || !asi.getLot().equals(targetASI.getLot()))
continue;
}
}
if (mh == null)
{
// Create Movement
mh = new MMovement (getCtx(), 0, get_TrxName());
mh.setAD_Org_ID(target.getAD_Org_ID());
mh.setC_DocType_ID(p_C_DocType_ID);
mh.setDescription(getName());
mh.saveEx();
}
// Movement Line
MMovementLine ml = new MMovementLine(mh);
ml.setM_Product_ID(target.getM_Product_ID());
@ -163,8 +189,7 @@ public class StorageCleanup extends SvrProcess
//
lines++;
ml.setLine(lines*10);
if (!ml.save())
return 0;
ml.saveEx();
qty = qty.subtract(qtyMove);
if (qty.signum() <= 0)
@ -172,16 +197,18 @@ public class StorageCleanup extends SvrProcess
} // for all movements
// Process
if (!mh.processIt(MMovement.ACTION_Complete)) {
log.warning("Movement Process Failed: " + mh + " - " + mh.getProcessMsg());
throw new IllegalStateException("Movement Process Failed: " + mh + " - " + mh.getProcessMsg());
if (mh != null) {
if (!mh.processIt(MMovement.ACTION_Complete)) {
log.warning("Movement Process Failed: " + mh + " - " + mh.getProcessMsg());
throw new IllegalStateException("Movement Process Failed: " + mh + " - " + mh.getProcessMsg());
}
mh.saveEx();
StringBuilder msglog= new StringBuilder("@M_Movement_ID@ ").append(mh.getDocumentNo()).append(" (")
.append(MRefList.get(getCtx(), MMovement.DOCSTATUS_AD_Reference_ID,
mh.getDocStatus(), get_TrxName())).append(")");
addLog(0, null, new BigDecimal(lines), msglog.toString());
}
mh.saveEx();
StringBuilder msglog= new StringBuilder("@M_Movement_ID@ ").append(mh.getDocumentNo()).append(" (")
.append(MRefList.get(getCtx(), MMovement.DOCSTATUS_AD_Reference_ID,
mh.getDocStatus(), get_TrxName())).append(")");
addLog(0, null, new BigDecimal(lines), msglog.toString());
eliminateReservation(target);
return lines;

View File

@ -25,16 +25,24 @@
package org.idempiere.test.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.List;
import org.compiere.model.MAttributeSet;
import org.compiere.model.MAttributeSetInstance;
import org.compiere.model.MLocator;
import org.compiere.model.MPInstance;
import org.compiere.model.MPInstancePara;
import org.compiere.model.MProcess;
import org.compiere.model.MProduct;
import org.compiere.model.MStorageOnHand;
import org.compiere.model.Query;
import org.compiere.process.ProcessInfo;
import org.compiere.process.ServerProcessCtl;
import org.compiere.util.CacheMgt;
import org.compiere.util.DB;
import org.compiere.util.Env;
@ -55,6 +63,8 @@ public class MStorageOnHandTest extends AbstractTestCase {
private static final int TAX_CATEGORY_STANDARD_ID = 107;
private static final int CHEMICALS_CATEGORY_ID = 109;
private static final int UOM_EACH_ID = 100;
private static final int STORAGE_CLEANUP_PROCESS_ID = 325;
private static final int MATERIAL_MOVEMENT_DOCTYPE_ID = 143;
public MStorageOnHandTest() {
}
@ -193,4 +203,171 @@ public class MStorageOnHandTest extends AbstractTestCase {
M_Locator_ID = MStorageOnHand.getM_Locator_ID(hqLocator.getM_Warehouse_ID(), product.get_ID(), -1, new BigDecimal("1"), getTrxName());
assertEquals(hqLocator1.get_ID(), M_Locator_ID);
}
@Test
public void testStorageCleanUp() {
MProduct product = new MProduct(Env.getCtx(), 0, getTrxName());
product.setName("testStorageCleanUp");
product.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID);
product.setIsStocked(true);
product.setProductType(MProduct.PRODUCTTYPE_Item);
product.setC_UOM_ID(UOM_EACH_ID);
product.setM_Product_Category_ID(CHEMICALS_CATEGORY_ID);
product.setC_TaxCategory_ID(TAX_CATEGORY_STANDARD_ID);
product.saveEx();
Timestamp today = TimeUtil.getDay(null);
MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, product.get_ID(), 0, new BigDecimal("2"), today, getTrxName());
Query query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=?", getTrxName());
int count = query.setParameters(product.get_ID()).count();
assertEquals(1, count);
MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, product.get_ID(), 0, new BigDecimal("-2"), today, getTrxName());
DB.executeUpdateEx("UPDATE M_StorageOnHand SET Created=? WHERE M_Product_ID=?", new Object[] {TimeUtil.addDays(today, -7), product.get_ID()}, getTrxName());
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=?", getTrxName());
count = query.setParameters(product.get_ID()).count();
assertEquals(1, count);
//movement get product from cache
MProduct product1 = new MProduct(Env.getCtx(), 0, null);
product1.setName("testStorageCleanUp#1");
product1.setValue(product1.getName());
product1.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID);
product1.setIsStocked(true);
product1.setProductType(MProduct.PRODUCTTYPE_Item);
product1.setC_UOM_ID(UOM_EACH_ID);
product1.setM_Product_Category_ID(CHEMICALS_CATEGORY_ID);
product1.setC_TaxCategory_ID(TAX_CATEGORY_STANDARD_ID);
product1.saveEx();
try {
MAttributeSetInstance asi1 = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName());
asi1.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID);
asi1.setLot("Lot1");
asi1.saveEx();
MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, product1.get_ID(), asi1.get_ID(), new BigDecimal("-1"), today, getTrxName());
MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, product1.get_ID(), 0, new BigDecimal("1"), today, getTrxName());
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
MStorageOnHand onhand = query.setParameters(product1.get_ID(), asi1.get_ID()).first();
assertNotNull(onhand);
assertEquals(-1, onhand.getQtyOnHand().intValue());
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
onhand = query.setParameters(product1.get_ID(), 0).first();
assertNotNull(onhand);
assertEquals(1, onhand.getQtyOnHand().intValue());
MProduct product2 = new MProduct(Env.getCtx(), 0, getTrxName());
product2.setName("testStorageCleanUp#2");
product2.setValue(product2.getName());
product2.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID);
product2.setIsStocked(true);
product2.setProductType(MProduct.PRODUCTTYPE_Item);
product2.setC_UOM_ID(UOM_EACH_ID);
product2.setM_Product_Category_ID(CHEMICALS_CATEGORY_ID);
product2.setC_TaxCategory_ID(TAX_CATEGORY_STANDARD_ID);
product2.saveEx();
MAttributeSetInstance asi2 = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName());
asi2.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID);
asi2.setLot("LotX");
asi2.saveEx();
MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, product2.get_ID(), asi2.get_ID(), new BigDecimal("-1"), today, getTrxName());
MAttributeSetInstance asi3 = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName());
asi3.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID);
asi3.setLot("LotY");
asi3.saveEx();
MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, product2.get_ID(), asi3.get_ID(), new BigDecimal("1"), today, getTrxName());
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
onhand = query.setParameters(product2.get_ID(), asi2.get_ID()).first();
assertNotNull(onhand);
assertEquals(-1, onhand.getQtyOnHand().intValue());
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
onhand = query.setParameters(product2.get_ID(), asi3.get_ID()).first();
assertNotNull(onhand);
assertEquals(1, onhand.getQtyOnHand().intValue());
MProduct product3 = new MProduct(Env.getCtx(), 0, getTrxName());
product3.setName("testStorageCleanUp#3");
product3.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID);
product3.setIsStocked(true);
product3.setProductType(MProduct.PRODUCTTYPE_Item);
product3.setC_UOM_ID(UOM_EACH_ID);
product3.setM_Product_Category_ID(CHEMICALS_CATEGORY_ID);
product3.setC_TaxCategory_ID(TAX_CATEGORY_STANDARD_ID);
product3.saveEx();
MAttributeSetInstance asi4 = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName());
asi4.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID);
asi4.setSerNo(product3.getName()+"SerialNo#1");
asi4.saveEx();
MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, product3.get_ID(), asi4.get_ID(), new BigDecimal("1"), today, getTrxName());
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=?", getTrxName());
count = query.setParameters(product3.get_ID()).count();
assertEquals(1, count);
MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, product3.get_ID(), asi4.get_ID(), new BigDecimal("-1"), today, getTrxName());
DB.executeUpdateEx("UPDATE M_StorageOnHand SET Created=? WHERE M_Product_ID=?", new Object[] {TimeUtil.addDays(today, -7), product3.get_ID()}, getTrxName());
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=?", getTrxName());
count = query.setParameters(product3.get_ID()).count();
assertEquals(1, count);
MPInstance instance = new MPInstance(Env.getCtx(), STORAGE_CLEANUP_PROCESS_ID, 0);
instance.saveEx();
MPInstancePara para = new MPInstancePara(instance, 10);
para.setParameterName("C_DocType_ID");
para.setP_Number(MATERIAL_MOVEMENT_DOCTYPE_ID);
para.saveEx();
MProcess process = MProcess.get(Env.getCtx(), STORAGE_CLEANUP_PROCESS_ID);
ProcessInfo pi = new ProcessInfo(process.getName(), process.get_ID());
pi.setAD_PInstance_ID(instance.get_ID());
pi.setAD_Client_ID(getAD_Client_ID());
pi.setAD_User_ID(getAD_User_ID());
pi.setTransactionName(getTrxName());
ServerProcessCtl.process(pi, getTrx(), false);
assertFalse(pi.isError(), pi.getSummary());
//check 0 stock removed
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=?", getTrxName());
count = query.setParameters(product.get_ID()).count();
assertEquals(0, count);
//check 0 stock with serno not removed
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=?", getTrxName());
List<MStorageOnHand> onhands = query.setParameters(product3.get_ID()).list();
assertEquals(1, onhands.size());
assertEquals(0, onhands.get(0).getQtyOnHand().intValue());
assertEquals(asi4.get_ID(), onhands.get(0).getM_AttributeSetInstance_ID());
//check -1 and 1 consolidated
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
onhand = query.setParameters(product1.get_ID(), asi1.get_ID()).first();
assertNotNull(onhand);
assertEquals(0, onhand.getQtyOnHand().intValue());
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
onhand = query.setParameters(product1.get_ID(), 0).first();
assertNotNull(onhand);
assertEquals(0, onhand.getQtyOnHand().intValue());
//check -1 and 1 not consolidated due to different lot
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
onhand = query.setParameters(product2.get_ID(), asi2.get_ID()).first();
assertNotNull(onhand);
assertEquals(-1, onhand.getQtyOnHand().intValue());
query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
onhand = query.setParameters(product2.get_ID(), asi3.get_ID()).first();
assertNotNull(onhand);
assertEquals(1, onhand.getQtyOnHand().intValue());
} finally {
getTrx().rollback();
product1.deleteEx(true);
}
}
}