diff --git a/org.adempiere.base/src/org/compiere/model/MProduct.java b/org.adempiere.base/src/org/compiere/model/MProduct.java index e229bfbad3..5bf475491d 100644 --- a/org.adempiere.base/src/org/compiere/model/MProduct.java +++ b/org.adempiere.base/src/org/compiere/model/MProduct.java @@ -651,6 +651,8 @@ public class MProduct extends X_M_Product implements ImmutablePOSupport log.saveError("Error", Msg.parseTranslation(getCtx(), errMsg)); return false; } + + removeStorageRecords(); } // storage // it checks if UOM has been changed , if so disallow the change if the condition is true. @@ -724,6 +726,33 @@ public class MProduct extends X_M_Product implements ImmutablePOSupport return errMsg.toString(); } + private void removeStorageRecords() { + int cnt = 0; + //safe to remove if not using lot or serial + if (isLot() || isSerial()) { + //for lot/serial, make sure everything is zero + cnt = DB.executeUpdateEx("UPDATE M_StorageOnHand SET QtyOnHand=0 WHERE M_Product_ID=? AND QtyOnHand != 0", new Object[] {getM_Product_ID()}, get_TrxName()); + if (log.isLoggable(Level.INFO)) { + log.log(Level.INFO, toString()+" #M_StorageOnHand Updated=" + cnt); + } + } else { + cnt = DB.executeUpdateEx("DELETE FROM M_StorageOnHand WHERE M_Product_ID=?", new Object[] {getM_Product_ID()}, get_TrxName()); + if (log.isLoggable(Level.INFO)) { + log.log(Level.INFO, toString()+" #M_StorageOnHand Deleted=" + cnt); + } + } + + //clear all reservation data + cnt = DB.executeUpdateEx("DELETE FROM M_StorageReservation WHERE M_Product_ID=?", new Object[] {getM_Product_ID()}, get_TrxName()); + if (log.isLoggable(Level.INFO)) { + log.log(Level.INFO, toString()+" #M_StorageReservation Deleted=" + cnt); + } + cnt = DB.executeUpdateEx("DELETE FROM M_StorageReservationLog WHERE M_Product_ID=?", new Object[] {getM_Product_ID()}, get_TrxName()); + if (log.isLoggable(Level.INFO)) { + log.log(Level.INFO, toString()+" #M_StorageReservationLog Deleted=" + cnt); + } + } + /** * HasInventoryOrCost * @return true if it has Inventory or Cost @@ -1011,4 +1040,19 @@ public class MProduct extends X_M_Product implements ImmutablePOSupport else return false; } + + /** + * + * @return true if instance of product is managed with lot + */ + public boolean isLot() { + if (getM_AttributeSet_ID() == 0) + return false; + + MAttributeSet as = MAttributeSet.get(getM_AttributeSet_ID()); + if (as.isInstanceAttribute() && as.isLot()) + return true; + else + return false; + } } // MProduct diff --git a/org.idempiere.test/src/org/idempiere/test/model/MProductTest.java b/org.idempiere.test/src/org/idempiere/test/model/MProductTest.java index af9203efb7..59acc4165c 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/MProductTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/MProductTest.java @@ -28,11 +28,13 @@ 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.sql.Timestamp; +import org.adempiere.exceptions.AdempiereException; import org.compiere.model.MAcctSchema; import org.compiere.model.MAttributeInstance; import org.compiere.model.MAttributeSet; @@ -44,11 +46,15 @@ import org.compiere.model.MInOutLine; 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.model.MUOM; +import org.compiere.model.Query; import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; import org.compiere.process.ProcessInfo; import org.compiere.util.CacheMgt; +import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.TimeUtil; import org.compiere.wf.MWorkflow; @@ -204,4 +210,35 @@ public class MProductTest extends AbstractTestCase { attributeSet.saveEx(); } } + + @Test + public void testRemoveStorageRecords() { + int count = 0; + + //make sure there's on hand and reservation records + MProduct product = new MProduct(Env.getCtx(), MULCH_PRODUCT_ID, getTrxName()); + createPOAndMRForProduct(product.get_ID()); + Query query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=?", getTrxName()); + count = query.setParameters(product.get_ID()).count(); + assertTrue(count > 0, "No Storage On Hand Record"); + query = new Query(Env.getCtx(), MStorageReservation.Table_Name, "M_Product_ID=?", getTrxName()); + count = query.setParameters(product.get_ID()).count(); + assertTrue(count > 0, "No Storage Reservation Record"); + + //this should faiil due to on hand > 0 + product.setIsActive(false); + assertThrows(AdempiereException.class, () -> product.saveEx()); + + //clear on hand so that we can deactivate product + DB.executeUpdateEx("UPDATE M_StorageOnHand SET QtyOnHand=0 WHERE M_Product_ID=?", new Object[] {product.get_ID()}, getTrxName()); + product.setIsActive(false); + product.saveEx(); + + query = new Query(Env.getCtx(), MStorageOnHand.Table_Name, "M_Product_ID=?", getTrxName()); + count = query.setParameters(product.get_ID()).count(); + assertEquals(0, count, "Storage On Hand Record > 0"); + query = new Query(Env.getCtx(), MStorageReservation.Table_Name, "M_Product_ID=?", getTrxName()); + count = query.setParameters(product.get_ID()).count(); + assertEquals(0, count, "Storage Reservation Record > 0"); + } }