From 916a5a9233eb36f341fb4e8726290d31e89b12ed Mon Sep 17 00:00:00 2001 From: hengsin Date: Wed, 12 Jan 2022 00:15:25 +0800 Subject: [PATCH] IDEMPIERE-4849 Nonsensical code in MProduction.createLines() (#1116) --- .../src/org/compiere/model/MProduction.java | 19 +- .../org/compiere/model/MProductionLine.java | 24 +- .../org/compiere/model/MProductionPlan.java | 24 +- .../idempiere/test/model/ProductionTest.java | 372 ++++++++++++++++++ 4 files changed, 391 insertions(+), 48 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/MProduction.java b/org.adempiere.base/src/org/compiere/model/MProduction.java index 659622ea40..55365d82b1 100644 --- a/org.adempiere.base/src/org/compiere/model/MProduction.java +++ b/org.adempiere.base/src/org/compiere/model/MProduction.java @@ -263,8 +263,6 @@ public class MProduction extends X_M_Production implements DocAction { int M_Warehouse_ID = finishedLocator.getM_Warehouse_ID(); - int asi = 0; - // products used in production String sql = " SELECT bl.M_Product_ID, bl.QtyBOM" + " FROM PP_Product_BOMLine bl" + " JOIN PP_Product_BOM b ON b.PP_Product_BOM_ID = bl.PP_Product_BOM_ID " @@ -354,7 +352,6 @@ public class MProduction extends X_M_Production implements DocAction { MProductionLine BOMLine = null; int prevLoc = -1; - int previousAttribSet = -1; // Create lines from storage until qty is reached for (int sl = 0; sl < storages.length; sl++) { @@ -365,13 +362,9 @@ public class MProduction extends X_M_Production implements DocAction { int loc = storages[sl].getM_Locator_ID(); - int slASI = storages[sl].getM_AttributeSetInstance_ID(); - int locAttribSet = new MAttributeSetInstance(getCtx(), asi, - get_TrxName()).getM_AttributeSet_ID(); - // roll up costing attributes if in the same locator - if (locAttribSet == 0 && previousAttribSet == 0 - && prevLoc == loc) { + // same locator + if (prevLoc == loc) { BOMLine.setQtyUsed(BOMLine.getQtyUsed() .add(lineQty)); BOMLine.setPlannedQty(BOMLine.getQtyUsed()); @@ -386,15 +379,12 @@ public class MProduction extends X_M_Production implements DocAction { BOMLine.setM_Locator_ID( loc ); BOMLine.setQtyUsed( lineQty); BOMLine.setPlannedQty( lineQty); - if ( slASI != 0 && locAttribSet != 0 ) // ie non costing attribute - BOMLine.setM_AttributeSetInstance_ID(slASI); BOMLine.saveEx(get_TrxName()); lineno = lineno + 10; count++; } prevLoc = loc; - previousAttribSet = locAttribSet; // enough ? BOMMovementQty = BOMMovementQty.subtract(lineQty); if (BOMMovementQty.signum() == 0) @@ -407,9 +397,8 @@ public class MProduction extends X_M_Production implements DocAction { if (!mustBeStocked) { - // roll up costing attributes if in the same locator - if ( previousAttribSet == 0 - && prevLoc == defaultLocator) { + // same locator + if (prevLoc == defaultLocator) { BOMLine.setQtyUsed(BOMLine.getQtyUsed() .add(BOMMovementQty)); BOMLine.setPlannedQty(BOMLine.getQtyUsed()); diff --git a/org.adempiere.base/src/org/compiere/model/MProductionLine.java b/org.adempiere.base/src/org/compiere/model/MProductionLine.java index 83c87cdefe..0dec237a4d 100644 --- a/org.adempiere.base/src/org/compiere/model/MProductionLine.java +++ b/org.adempiere.base/src/org/compiere/model/MProductionLine.java @@ -95,11 +95,11 @@ public class MProductionLine extends X_M_ProductionLine { StringBuilder errorString = new StringBuilder(); MAttributeSetInstance asi = new MAttributeSetInstance(getCtx(), getM_AttributeSetInstance_ID(), get_TrxName()); - I_M_AttributeSet attributeset = prod.getM_AttributeSet(); + I_M_AttributeSet attributeset = prod.getM_AttributeSet_ID() > 0 ? MAttributeSet.get(prod.getM_AttributeSet_ID()) : null; boolean isAutoGenerateLot = false; if (attributeset != null) isAutoGenerateLot = attributeset.isAutoGenerateLot(); - String asiString = asi.getDescription(); + String asiString = asi.get_ID() > 0 ? asi.getDescription() : ""; if ( asiString == null ) asiString = ""; @@ -165,16 +165,15 @@ public class MProductionLine extends X_M_ProductionLine { if (lineQty.compareTo(qtyToMove ) > 0) lineQty = qtyToMove; - MAttributeSetInstance slASI = new MAttributeSetInstance(getCtx(), - storages[sl].getM_AttributeSetInstance_ID(),get_TrxName()); - String slASIString = slASI.getDescription(); + MAttributeSetInstance slASI = storages[sl].getM_AttributeSetInstance_ID() > 0 ? new MAttributeSetInstance(getCtx(), + storages[sl].getM_AttributeSetInstance_ID(),get_TrxName()) : null; + String slASIString = slASI != null ? slASI.getDescription() : ""; if (slASIString == null) slASIString = ""; if (log.isLoggable(Level.FINEST))log.log(Level.FINEST,"slASI-Description =" + slASIString); - if ( slASIString.compareTo(asiString) == 0 - || asi.getM_AttributeSet_ID() == 0 ) + if (asi.getM_AttributeSet_ID() == 0 || slASIString.equals(asiString)) //storage matches specified ASI or is a costing asi (inc. 0) // This process will move negative stock on hand quantities { @@ -227,7 +226,7 @@ public class MProductionLine extends X_M_ProductionLine { { setM_AttributeSetInstance_ID(storage.getM_AttributeSetInstance_ID()); asi = new MAttributeSetInstance(getCtx(), storage.getM_AttributeSetInstance_ID(), get_TrxName()); - asiString = asi.getDescription(); + asiString = asi.get_ID() > 0 ? asi.getDescription() : ""; } else { @@ -273,16 +272,15 @@ public class MProductionLine extends X_M_ProductionLine { asi.get_ID(), date, get_TrxName(), true); BigDecimal lineQty = qtyToMove; - MAttributeSetInstance slASI = new MAttributeSetInstance(getCtx(), - storage.getM_AttributeSetInstance_ID(),get_TrxName()); - String slASIString = slASI.getDescription(); + MAttributeSetInstance slASI = storage.getM_AttributeSetInstance_ID() > 0 + ? new MAttributeSetInstance(getCtx(), storage.getM_AttributeSetInstance_ID(),get_TrxName()) : null; + String slASIString = slASI != null ? slASI.getDescription() : ""; if (slASIString == null) slASIString = ""; if (log.isLoggable(Level.FINEST))log.log(Level.FINEST,"slASI-Description =" + slASIString); - if ( slASIString.compareTo(asiString) == 0 - || asi.getM_AttributeSet_ID() == 0 ) + if (asi.getM_AttributeSet_ID() == 0 || slASIString.compareTo(asiString) == 0) //storage matches specified ASI or is a costing asi (inc. 0) // This process will move negative stock on hand quantities { diff --git a/org.adempiere.base/src/org/compiere/model/MProductionPlan.java b/org.adempiere.base/src/org/compiere/model/MProductionPlan.java index bd4a2d8569..1aebcf808e 100644 --- a/org.adempiere.base/src/org/compiere/model/MProductionPlan.java +++ b/org.adempiere.base/src/org/compiere/model/MProductionPlan.java @@ -122,8 +122,6 @@ public class MProductionPlan extends X_M_ProductionPlan { int M_Warehouse_ID = finishedLocator.getM_Warehouse_ID(); - int asi = 0; - // products used in production String sql = " SELECT bl.M_Product_ID, bl.QtyBOM" + " FROM PP_Product_BOMLine bl" + " JOIN PP_Product_BOM b ON b.PP_Product_BOM_ID = bl.PP_Product_BOM_ID " @@ -209,7 +207,6 @@ public class MProductionPlan extends X_M_ProductionPlan { MProductionLine BOMLine = null; int prevLoc = -1; - int previousAttribSet = -1; // Create lines from storage until qty is reached for (int sl = 0; sl < storages.length; sl++) { @@ -218,15 +215,9 @@ public class MProductionPlan extends X_M_ProductionPlan { if (lineQty.compareTo(BOMMovementQty) > 0) lineQty = BOMMovementQty; - int loc = storages[sl].getM_Locator_ID(); - int slASI = storages[sl].getM_AttributeSetInstance_ID(); - int locAttribSet = new MAttributeSetInstance(getCtx(), asi, - get_TrxName()).getM_AttributeSet_ID(); - - // roll up costing attributes if in the same locator - if (locAttribSet == 0 && previousAttribSet == 0 - && prevLoc == loc) { + // same locator + if (prevLoc == loc) { BOMLine.setQtyUsed(BOMLine.getQtyUsed() .add(lineQty)); BOMLine.setPlannedQty(BOMLine.getQtyUsed()); @@ -241,15 +232,12 @@ public class MProductionPlan extends X_M_ProductionPlan { BOMLine.setM_Locator_ID( loc ); BOMLine.setQtyUsed( lineQty); BOMLine.setPlannedQty( lineQty); - if ( slASI != 0 && locAttribSet != 0 ) // ie non costing attribute - BOMLine.setM_AttributeSetInstance_ID(slASI); BOMLine.saveEx(get_TrxName()); lineno = lineno + 10; count++; } prevLoc = loc; - previousAttribSet = locAttribSet; // enough ? BOMMovementQty = BOMMovementQty.subtract(lineQty); if (BOMMovementQty.signum() == 0) @@ -261,10 +249,8 @@ public class MProductionPlan extends X_M_ProductionPlan { if (BOMMovementQty.signum() != 0 ) { if (!mustBeStocked) { - - // roll up costing attributes if in the same locator - if ( previousAttribSet == 0 - && prevLoc == defaultLocator) { + //same locator + if (prevLoc == defaultLocator) { BOMLine.setQtyUsed(BOMLine.getQtyUsed() .add(BOMMovementQty)); BOMLine.setPlannedQty(BOMLine.getQtyUsed()); @@ -273,7 +259,6 @@ public class MProductionPlan extends X_M_ProductionPlan { } // otherwise create new line else { - BOMLine = new MProductionLine( this ); BOMLine.setLine( lineno ); BOMLine.setM_Product_ID( BOMProduct_ID ); @@ -285,7 +270,6 @@ public class MProductionPlan extends X_M_ProductionPlan { lineno = lineno + 10; count++; } - } else { diff --git a/org.idempiere.test/src/org/idempiere/test/model/ProductionTest.java b/org.idempiere.test/src/org/idempiere/test/model/ProductionTest.java index a88e28151b..c0fb9072fd 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/ProductionTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/ProductionTest.java @@ -36,6 +36,7 @@ import java.util.List; import org.compiere.model.MAccount; import org.compiere.model.MAcctSchema; +import org.compiere.model.MAttributeSetInstance; import org.compiere.model.MBPartner; import org.compiere.model.MClient; import org.compiere.model.MCost; @@ -85,6 +86,10 @@ public class ProductionTest extends AbstractTestCase { private static final int DOCTYPE_PO = 126; private static final int DOCTYPE_RECEIPT = 122; private static final int USER_GARDENADMIN = 101; + private static final int FERTILIZER_LOT_ATTRIBUTESET_ID = 101; + private static final int UOM_EACH_ID = 100; + private static final int TAX_CATEGORY_STANDARD_ID = 107; + private static final int HQ_LOCATOR_ID = 101; @Test public void testAverageCostingProduction() { @@ -546,4 +551,371 @@ public class ProductionTest extends AbstractTestCase { mulchX.deleteEx(true); } } + + @Test + public void testMultipleASI() { + //use standard costing only to avoid negative qty exception + DB.executeUpdateEx("UPDATE M_CostElement SET IsActive = 'N' WHERE AD_Client_ID=? AND CostingMethod IS NOT NULL AND CostingMethod != ?", + new Object[] {getAD_Client_ID(), MCostElement.COSTINGMETHOD_StandardCosting}, getTrxName()); + + MProductCategory category = new MProductCategory(Env.getCtx(), 0, null); + category.setName("Standard Costing"); + category.saveEx(); + + String whereClause = "M_Product_Category_ID=?"; + List categoryAccts = new Query(Env.getCtx(), MProductCategoryAcct.Table_Name, whereClause, null) + .setParameters(category.get_ID()) + .list(); + for (MProductCategoryAcct categoryAcct : categoryAccts) { + categoryAcct.setCostingMethod(MAcctSchema.COSTINGMETHOD_StandardCosting); + categoryAcct.saveEx(); + } + + //storageonhand api doesn't use trx to retrieve product + MProduct component = new MProduct(Env.getCtx(), 0, null); + component.setName("testMultipleASI_Child"); + component.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID); + component.setIsStocked(true); + component.setProductType(MProduct.PRODUCTTYPE_Item); + component.setC_UOM_ID(UOM_EACH_ID); + component.setM_Product_Category_ID(category.get_ID()); + component.setC_TaxCategory_ID(TAX_CATEGORY_STANDARD_ID); + component.saveEx(); + + try { + Timestamp today = TimeUtil.getDay(null); + MProduct parent = new MProduct(Env.getCtx(), 0, getTrxName()); + parent.setName("testMultipleASI_Parent"); + parent.setIsBOM(true); + parent.setIsStocked(true); + parent.setC_UOM_ID(component.getC_UOM_ID()); + parent.setM_Product_Category_ID(component.getM_Product_Category_ID()); + parent.setProductType(component.getProductType()); + parent.setC_TaxCategory_ID(component.getC_TaxCategory_ID()); + parent.saveEx(); + BigDecimal endProductOnHand1 = MStorageOnHand.getQtyOnHand(parent.get_ID(), getM_Warehouse_ID(), 0, getTrxName()); + assertEquals(0, endProductOnHand1.intValue(), "On hand of new product is not zero"); + + MPPProductBOM bom = new MPPProductBOM(Env.getCtx(), 0, getTrxName()); + bom.setM_Product_ID(parent.get_ID()); + bom.setBOMType(MPPProductBOM.BOMTYPE_CurrentActive); + bom.setBOMUse(MPPProductBOM.BOMUSE_Master); + bom.setName(parent.getName()); + bom.saveEx(); + + MPPProductBOMLine line = new MPPProductBOMLine(bom); + line.setM_Product_ID(component.get_ID()); + line.setQtyBOM(new BigDecimal("2")); + line.saveEx(); + + parent.load(getTrxName()); + parent.setIsVerified(true); + parent.saveEx(); + + 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, component.get_ID(), asi1.get_ID(), new BigDecimal("1"), today, getTrxName()); + + MAttributeSetInstance asi2 = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName()); + asi2.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID); + asi2.setLot("Lot2"); + asi2.saveEx(); + MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, component.get_ID(), asi2.get_ID(), new BigDecimal("1"), today, getTrxName()); + + MProduction production = new MProduction(Env.getCtx(), 0, getTrxName()); + production.setM_Product_ID(parent.get_ID()); + production.setM_Locator_ID(HQ_LOCATOR_ID); + production.setIsUseProductionPlan(false); + production.setMovementDate(getLoginDate()); + production.setDocAction(DocAction.ACTION_Complete); + production.setDocStatus(DocAction.STATUS_Drafted); + production.setIsComplete(false); + production.setProductionQty(new BigDecimal("1")); + production.setPP_Product_BOM_ID(bom.getPP_Product_BOM_ID()); + production.saveEx(); + + int productionCreate = 53226; + MProcess process = MProcess.get(Env.getCtx(), productionCreate); + ProcessInfo pi = new ProcessInfo(process.getName(), process.get_ID()); + pi.setAD_Client_ID(getAD_Client_ID()); + pi.setAD_User_ID(getAD_User_ID()); + pi.setRecord_ID(production.get_ID()); + pi.setTransactionName(getTrxName()); + ServerProcessCtl.process(pi, getTrx(), false); + assertFalse(pi.isError(), pi.getSummary()); + + production.load(getTrxName()); + assertEquals("Y", production.getIsCreated(), "MProduction.IsCreated != Y"); + assertTrue(production.getLines().length > 0, "No Production Lines"); + assertEquals(2, production.getLines().length, "Unexpected number of production lines"); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(production, DocAction.ACTION_Complete); + production.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, production.getDocStatus(), "Production Status="+production.getDocStatus()); + + BigDecimal endProductOnHand2 = MStorageOnHand.getQtyOnHand(parent.get_ID(), getM_Warehouse_ID(), 0, getTrxName()); + + assertEquals(1, endProductOnHand2.intValue(), "On hand of end product doesn't increase as expected"); + } finally { + getTrx().rollback(); + component.deleteEx(true); + category.deleteEx(true); + } + } + + @Test + public void testMultipleDateMPolicy() { + //use standard costing only to avoid negative qty exception + DB.executeUpdateEx("UPDATE M_CostElement SET IsActive = 'N' WHERE AD_Client_ID=? AND CostingMethod IS NOT NULL AND CostingMethod != ?", + new Object[] {getAD_Client_ID(), MCostElement.COSTINGMETHOD_StandardCosting}, getTrxName()); + + MProductCategory category = new MProductCategory(Env.getCtx(), 0, null); + category.setName("Standard Costing"); + category.saveEx(); + + String whereClause = "M_Product_Category_ID=?"; + List categoryAccts = new Query(Env.getCtx(), MProductCategoryAcct.Table_Name, whereClause, null) + .setParameters(category.get_ID()) + .list(); + for (MProductCategoryAcct categoryAcct : categoryAccts) { + categoryAcct.setCostingMethod(MAcctSchema.COSTINGMETHOD_StandardCosting); + categoryAcct.saveEx(); + } + + //storageonhand api doesn't use trx to retrieve product + MProduct component = new MProduct(Env.getCtx(), 0, null); + component.setName("testMultipleDateMPolicy_Child"); + component.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID); + component.setIsStocked(true); + component.setProductType(MProduct.PRODUCTTYPE_Item); + component.setC_UOM_ID(UOM_EACH_ID); + component.setM_Product_Category_ID(category.get_ID()); + component.setC_TaxCategory_ID(TAX_CATEGORY_STANDARD_ID); + component.saveEx(); + + try { + Timestamp today = TimeUtil.getDay(null); + MProduct parent = new MProduct(Env.getCtx(), 0, getTrxName()); + parent.setName("testMultipleDateMPolicy_Parent"); + parent.setIsBOM(true); + parent.setIsStocked(true); + parent.setC_UOM_ID(component.getC_UOM_ID()); + parent.setM_Product_Category_ID(component.getM_Product_Category_ID()); + parent.setProductType(component.getProductType()); + parent.setC_TaxCategory_ID(component.getC_TaxCategory_ID()); + parent.saveEx(); + BigDecimal endProductOnHand1 = MStorageOnHand.getQtyOnHand(parent.get_ID(), getM_Warehouse_ID(), 0, getTrxName()); + assertEquals(0, endProductOnHand1.intValue(), "On hand of new product is not zero"); + + MPPProductBOM bom = new MPPProductBOM(Env.getCtx(), 0, getTrxName()); + bom.setM_Product_ID(parent.get_ID()); + bom.setBOMType(MPPProductBOM.BOMTYPE_CurrentActive); + bom.setBOMUse(MPPProductBOM.BOMUSE_Master); + bom.setName(parent.getName()); + bom.saveEx(); + + MPPProductBOMLine line = new MPPProductBOMLine(bom); + line.setM_Product_ID(component.get_ID()); + line.setQtyBOM(new BigDecimal("2")); + line.saveEx(); + + parent.load(getTrxName()); + parent.setIsVerified(true); + parent.saveEx(); + + 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, component.get_ID(), asi1.get_ID(), new BigDecimal("1"), TimeUtil.addDays(today, -1), getTrxName()); + MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, component.get_ID(), asi1.get_ID(), new BigDecimal("1"), today, getTrxName()); + + MProduction production = new MProduction(Env.getCtx(), 0, getTrxName()); + production.setM_Product_ID(parent.get_ID()); + production.setM_Locator_ID(HQ_LOCATOR_ID); + production.setIsUseProductionPlan(false); + production.setMovementDate(getLoginDate()); + production.setDocAction(DocAction.ACTION_Complete); + production.setDocStatus(DocAction.STATUS_Drafted); + production.setIsComplete(false); + production.setProductionQty(new BigDecimal("1")); + production.setPP_Product_BOM_ID(bom.getPP_Product_BOM_ID()); + production.saveEx(); + + int productionCreate = 53226; + MProcess process = MProcess.get(Env.getCtx(), productionCreate); + ProcessInfo pi = new ProcessInfo(process.getName(), process.get_ID()); + pi.setAD_Client_ID(getAD_Client_ID()); + pi.setAD_User_ID(getAD_User_ID()); + pi.setRecord_ID(production.get_ID()); + pi.setTransactionName(getTrxName()); + ServerProcessCtl.process(pi, getTrx(), false); + assertFalse(pi.isError(), pi.getSummary()); + + production.load(getTrxName()); + assertEquals("Y", production.getIsCreated(), "MProduction.IsCreated != Y"); + assertTrue(production.getLines().length > 0, "No Production Lines"); + assertEquals(2, production.getLines().length, "Unexpected number of production lines"); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(production, DocAction.ACTION_Complete); + production.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, production.getDocStatus(), "Production Status="+production.getDocStatus()); + + BigDecimal endProductOnHand2 = MStorageOnHand.getQtyOnHand(parent.get_ID(), getM_Warehouse_ID(), 0, getTrxName()); + + assertEquals(1, endProductOnHand2.intValue(), "On hand of end product doesn't increase as expected"); + } finally { + getTrx().rollback(); + component.deleteEx(true); + category.deleteEx(true); + } + } + + @Test + public void testMultipleInProgressProduction() { + //use standard costing only to avoid negative qty exception + DB.executeUpdateEx("UPDATE M_CostElement SET IsActive = 'N' WHERE AD_Client_ID=? AND CostingMethod IS NOT NULL AND CostingMethod != ?", + new Object[] {getAD_Client_ID(), MCostElement.COSTINGMETHOD_StandardCosting}, getTrxName()); + + MProductCategory category = new MProductCategory(Env.getCtx(), 0, null); + category.setName("Standard Costing"); + category.saveEx(); + + String whereClause = "M_Product_Category_ID=?"; + List categoryAccts = new Query(Env.getCtx(), MProductCategoryAcct.Table_Name, whereClause, null) + .setParameters(category.get_ID()) + .list(); + for (MProductCategoryAcct categoryAcct : categoryAccts) { + categoryAcct.setCostingMethod(MAcctSchema.COSTINGMETHOD_StandardCosting); + categoryAcct.saveEx(); + } + + //storageonhand api doesn't use trx to retrieve product + MProduct component = new MProduct(Env.getCtx(), 0, null); + component.setName("testMultipleDateMPolicy_Child"); + component.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID); + component.setIsStocked(true); + component.setProductType(MProduct.PRODUCTTYPE_Item); + component.setC_UOM_ID(UOM_EACH_ID); + component.setM_Product_Category_ID(category.get_ID()); + component.setC_TaxCategory_ID(TAX_CATEGORY_STANDARD_ID); + component.saveEx(); + + try { + Timestamp today = TimeUtil.getDay(null); + MProduct parent = new MProduct(Env.getCtx(), 0, getTrxName()); + parent.setName("testMultipleDateMPolicy_Parent"); + parent.setIsBOM(true); + parent.setIsStocked(true); + parent.setC_UOM_ID(component.getC_UOM_ID()); + parent.setM_Product_Category_ID(component.getM_Product_Category_ID()); + parent.setProductType(component.getProductType()); + parent.setC_TaxCategory_ID(component.getC_TaxCategory_ID()); + parent.saveEx(); + BigDecimal endProductOnHand1 = MStorageOnHand.getQtyOnHand(parent.get_ID(), getM_Warehouse_ID(), 0, getTrxName()); + assertEquals(0, endProductOnHand1.intValue(), "On hand of new product is not zero"); + + MPPProductBOM bom = new MPPProductBOM(Env.getCtx(), 0, getTrxName()); + bom.setM_Product_ID(parent.get_ID()); + bom.setBOMType(MPPProductBOM.BOMTYPE_CurrentActive); + bom.setBOMUse(MPPProductBOM.BOMUSE_Master); + bom.setName(parent.getName()); + bom.saveEx(); + + MPPProductBOMLine line = new MPPProductBOMLine(bom); + line.setM_Product_ID(component.get_ID()); + line.setQtyBOM(new BigDecimal("2")); + line.saveEx(); + + parent.load(getTrxName()); + parent.setIsVerified(true); + parent.saveEx(); + + 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, component.get_ID(), asi1.get_ID(), new BigDecimal("2"), today, getTrxName()); + + MAttributeSetInstance asi2 = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName()); + asi2.setM_AttributeSet_ID(FERTILIZER_LOT_ATTRIBUTESET_ID); + asi2.setLot("Lot2"); + asi2.saveEx(); + MStorageOnHand.add(Env.getCtx(), HQ_LOCATOR_ID, component.get_ID(), asi2.get_ID(), new BigDecimal("2"), today, getTrxName()); + + MProduction production1 = new MProduction(Env.getCtx(), 0, getTrxName()); + production1.setM_Product_ID(parent.get_ID()); + production1.setM_Locator_ID(HQ_LOCATOR_ID); + production1.setIsUseProductionPlan(false); + production1.setMovementDate(getLoginDate()); + production1.setDocAction(DocAction.ACTION_Complete); + production1.setDocStatus(DocAction.STATUS_Drafted); + production1.setIsComplete(false); + production1.setProductionQty(new BigDecimal("1")); + production1.setPP_Product_BOM_ID(bom.getPP_Product_BOM_ID()); + production1.saveEx(); + + int productionCreate = 53226; + MProcess process = MProcess.get(Env.getCtx(), productionCreate); + ProcessInfo pi = new ProcessInfo(process.getName(), process.get_ID()); + pi.setAD_Client_ID(getAD_Client_ID()); + pi.setAD_User_ID(getAD_User_ID()); + pi.setRecord_ID(production1.get_ID()); + pi.setTransactionName(getTrxName()); + ServerProcessCtl.process(pi, getTrx(), false); + assertFalse(pi.isError(), pi.getSummary()); + + production1.load(getTrxName()); + assertEquals("Y", production1.getIsCreated(), "MProduction.IsCreated != Y"); + assertTrue(production1.getLines().length > 0, "No Production Lines"); + assertEquals(2, production1.getLines().length, "Unexpected number of production lines"); + + MProduction production2 = new MProduction(Env.getCtx(), 0, getTrxName()); + production2.setM_Product_ID(parent.get_ID()); + production2.setM_Locator_ID(HQ_LOCATOR_ID); + production2.setIsUseProductionPlan(false); + production2.setMovementDate(getLoginDate()); + production2.setDocAction(DocAction.ACTION_Complete); + production2.setDocStatus(DocAction.STATUS_Drafted); + production2.setIsComplete(false); + production2.setProductionQty(new BigDecimal("1")); + production2.setPP_Product_BOM_ID(bom.getPP_Product_BOM_ID()); + production2.saveEx(); + + pi = new ProcessInfo(process.getName(), process.get_ID()); + pi.setAD_Client_ID(getAD_Client_ID()); + pi.setAD_User_ID(getAD_User_ID()); + pi.setRecord_ID(production2.get_ID()); + pi.setTransactionName(getTrxName()); + ServerProcessCtl.process(pi, getTrx(), false); + assertFalse(pi.isError(), pi.getSummary()); + + production2.load(getTrxName()); + assertEquals("Y", production2.getIsCreated(), "MProduction.IsCreated != Y"); + assertTrue(production2.getLines().length > 0, "No Production Lines"); + assertEquals(2, production2.getLines().length, "Unexpected number of production lines"); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(production1, DocAction.ACTION_Complete); + production1.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, production1.getDocStatus(), "Production Status="+production1.getDocStatus()); + BigDecimal endProductOnHand2 = MStorageOnHand.getQtyOnHand(parent.get_ID(), getM_Warehouse_ID(), 0, getTrxName()); + assertEquals(1, endProductOnHand2.intValue(), "On hand of end product doesn't increase as expected"); + + info = MWorkflow.runDocumentActionWorkflow(production2, DocAction.ACTION_Complete); + production2.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, production2.getDocStatus(), "Production Status="+production2.getDocStatus()); + endProductOnHand2 = MStorageOnHand.getQtyOnHand(parent.get_ID(), getM_Warehouse_ID(), 0, getTrxName()); + assertEquals(2, endProductOnHand2.intValue(), "On hand of end product doesn't increase as expected"); + } finally { + getTrx().rollback(); + component.deleteEx(true); + category.deleteEx(true); + } + } }