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

View File

@ -25,16 +25,24 @@
package org.idempiere.test.model; package org.idempiere.test.model;
import static org.junit.jupiter.api.Assertions.assertEquals; 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 static org.junit.jupiter.api.Assertions.assertNotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.List;
import org.compiere.model.MAttributeSet; import org.compiere.model.MAttributeSet;
import org.compiere.model.MAttributeSetInstance; import org.compiere.model.MAttributeSetInstance;
import org.compiere.model.MLocator; 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.MProduct;
import org.compiere.model.MStorageOnHand; 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.CacheMgt;
import org.compiere.util.DB; import org.compiere.util.DB;
import org.compiere.util.Env; 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 TAX_CATEGORY_STANDARD_ID = 107;
private static final int CHEMICALS_CATEGORY_ID = 109; private static final int CHEMICALS_CATEGORY_ID = 109;
private static final int UOM_EACH_ID = 100; 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() { 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()); 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); 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);
}
}
} }