IDEMPIERE-5063 Improve Unit Tests - MatchInv Tests (#1144)

- Add MatchInv test using standard costing and check Invoice Price Variance 
- General improvements using assertEquals where appropriate and
  comparing actual values rather than just >0
- minor fixes to MProduct test
This commit is contained in:
Tony Snook 2022-01-26 12:51:56 +11:00 committed by GitHub
parent 0db580a7a0
commit bfecd412a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 209 additions and 28 deletions

View File

@ -26,24 +26,29 @@ 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.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.util.List;
import org.compiere.acct.Doc;
import org.compiere.acct.DocManager;
import org.compiere.model.MAccount;
import org.compiere.model.MAcctSchema;
import org.compiere.model.MBPartner;
import org.compiere.model.MClientInfo;
import org.compiere.model.MClient;
import org.compiere.model.MConversionRate;
import org.compiere.model.MCost;
import org.compiere.model.MCurrency;
import org.compiere.model.MDocType;
import org.compiere.model.MFactAcct;
import org.compiere.model.MInOut;
import org.compiere.model.MInOutLine;
import org.compiere.model.MInventory;
import org.compiere.model.MInventoryLine;
import org.compiere.model.MInvoice;
import org.compiere.model.MInvoiceLine;
import org.compiere.model.MMatchInv;
@ -52,6 +57,8 @@ import org.compiere.model.MOrderLine;
import org.compiere.model.MPriceList;
import org.compiere.model.MPriceListVersion;
import org.compiere.model.MProduct;
import org.compiere.model.MProductCategory;
import org.compiere.model.MProductCategoryAcct;
import org.compiere.model.MProductPrice;
import org.compiere.model.MRMA;
import org.compiere.model.MRMALine;
@ -203,8 +210,8 @@ public class MatchInvTest extends AbstractTestCase {
creditMemo.load(getTrxName());
assertTrue(creditMemo.isPosted());
int C_AcctSchema_ID = MClientInfo.get(Env.getCtx()).getC_AcctSchema1_ID();
MAcctSchema as = MAcctSchema.get(Env.getCtx(), C_AcctSchema_ID);
MAcctSchema as = MClient.get(getAD_Client_ID()).getAcctSchema();
BigDecimal credMatchAmt = creditMemoLine.getMatchedQty().negate().multiply(creditMemoLine.getPriceActual()).setScale(as.getStdPrecision(), RoundingMode.HALF_UP);
MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName());
for (MMatchInv mi : miList) {
if (!mi.isPosted()) {
@ -223,14 +230,14 @@ public class MatchInvTest extends AbstractTestCase {
String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID()
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + C_AcctSchema_ID;
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID();
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName());
for (int id : ids) {
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
if (fa.getAccount_ID() == acctNIR.getAccount_ID())
assertTrue(fa.getAmtAcctCr().compareTo(Env.ZERO) >= 0);
assertEquals(fa.getAmtAcctCr(), credMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString());
else if (fa.getAccount_ID() == acctInvClr.getAccount_ID())
assertTrue(fa.getAmtAcctDr().compareTo(Env.ZERO) >= 0);
assertEquals(fa.getAmtAcctDr(), credMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctDr().toPlainString());
}
}
@ -314,8 +321,8 @@ public class MatchInvTest extends AbstractTestCase {
invoice.load(getTrxName());
assertTrue(invoice.isPosted());
int C_AcctSchema_ID = MClientInfo.get(Env.getCtx()).getC_AcctSchema1_ID();
MAcctSchema as = MAcctSchema.get(Env.getCtx(), C_AcctSchema_ID);
MAcctSchema as = MClient.get(getAD_Client_ID()).getAcctSchema();
BigDecimal invMatchAmt = invoiceLine.getMatchedQty().multiply(invoiceLine.getPriceActual()).setScale(as.getStdPrecision(), RoundingMode.HALF_UP);
MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName());
for (MMatchInv mi : miList) {
if (!mi.isPosted()) {
@ -334,20 +341,190 @@ public class MatchInvTest extends AbstractTestCase {
String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID()
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + C_AcctSchema_ID;
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID();
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName());
for (int id : ids) {
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
if (fa.getAccount_ID() == acctNIR.getAccount_ID())
assertTrue(fa.getAmtAcctDr().compareTo(Env.ZERO) >= 0);
assertEquals(fa.getAmtAcctDr(), invMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString());
else if (fa.getAccount_ID() == acctInvClr.getAccount_ID())
assertTrue(fa.getAmtAcctCr().compareTo(Env.ZERO) >= 0);
assertEquals(fa.getAmtAcctCr(), invMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString());
}
}
rollback();
}
@Test
/**
* Test Standard Cost and Invoice Price Variance posting
*/
public void testMatchInvStdCost() {
MProductCategory category = new MProductCategory(Env.getCtx(), 0, null);
category.setName("Standard Costing");
category.saveEx();
String whereClause = "M_Product_Category_ID=?";
List<MProductCategoryAcct> 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();
}
try {
int mulchId = 137; // Mulch product
MProduct mulch = new MProduct(Env.getCtx(), mulchId, getTrxName());
mulch.setM_Product_Category_ID(category.get_ID());
mulch.saveEx();
int purchaseId = 102; // Purchase Price List
MBPartner bpartner = MBPartner.get(Env.getCtx(), 120); // Seed Farm Inc.
MAcctSchema as = MClient.get(getAD_Client_ID()).getAcctSchema();
BigDecimal mulchCost = MCost.getCurrentCost(mulch, 0, getTrxName()).setScale(as.getCostingPrecision(), RoundingMode.HALF_UP);
// Change standard cost of mulch product to 2.1234
int hqLocator = 101;
int costAdjustmentDocTypeId = 200004;
MInventory inventory = new MInventory(Env.getCtx(), 0, getTrxName());
inventory.setCostingMethod(MAcctSchema.COSTINGMETHOD_StandardCosting);
inventory.setC_DocType_ID(costAdjustmentDocTypeId);
inventory.setM_Warehouse_ID(getM_Warehouse_ID());
inventory.setMovementDate(getLoginDate());
inventory.setDocAction(DocAction.ACTION_Complete);
inventory.saveEx();
BigDecimal endProductCost = new BigDecimal("2.1234").setScale(as.getCostingPrecision(), RoundingMode.HALF_UP);
MInventoryLine il = new MInventoryLine(Env.getCtx(), 0, getTrxName());
il.setM_Inventory_ID(inventory.get_ID());
il.setM_Locator_ID(hqLocator);
il.setM_Product_ID(mulch.getM_Product_ID());
il.setCurrentCostPrice(mulchCost);
il.setNewCostPrice(endProductCost);
il.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(inventory, DocAction.ACTION_Complete);
inventory.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Completed, inventory.getDocStatus(), "Cost Adjustment Status="+inventory.getDocStatus());
if (!inventory.isPosted()) {
String msg = DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MInventory.Table_ID, inventory.get_ID(), false, getTrxName());
assertNull(msg, msg);
}
mulchCost = MCost.getCurrentCost(mulch, 0, getTrxName()).setScale(as.getCostingPrecision(), RoundingMode.HALF_UP);
assertEquals(endProductCost, mulchCost, "Cost not adjusted: " + mulchCost.toPlainString());
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
order.setBPartner(bpartner);
order.setIsSOTrx(false);
order.setC_DocTypeTarget_ID();
order.setM_PriceList_ID(purchaseId);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
order.saveEx();
MOrderLine orderLine = new MOrderLine(order);
orderLine.setLine(10);
orderLine.setProduct(mulch);
orderLine.setQty(BigDecimal.ONE);
orderLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
order.load(getTrxName());
assertFalse(info.isError());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
MInOut receipt = new MInOut(order, 122, order.getDateOrdered()); // MM Receipt
receipt.saveEx();
MInOutLine receiptLine = new MInOutLine(receipt);
receiptLine.setC_OrderLine_ID(orderLine.get_ID());
receiptLine.setLine(10);
receiptLine.setProduct(mulch);
receiptLine.setQty(BigDecimal.ONE);
MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID());
int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID();
receiptLine.setM_Locator_ID(M_Locator_ID);
receiptLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
receipt.load(getTrxName());
assertFalse(info.isError());
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
if (!receipt.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName());
assertTrue(error == null);
}
receipt.load(getTrxName());
assertTrue(receipt.isPosted());
MInvoice invoice = new MInvoice(receipt, receipt.getMovementDate());
invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice);
invoice.setDocStatus(DocAction.STATUS_Drafted);
invoice.setDocAction(DocAction.ACTION_Complete);
invoice.saveEx();
MInvoiceLine invoiceLine = new MInvoiceLine(invoice);
invoiceLine.setM_InOutLine_ID(receiptLine.get_ID());
invoiceLine.setLine(10);
invoiceLine.setProduct(mulch);
invoiceLine.setQty(BigDecimal.ONE);
invoiceLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete);
invoice.load(getTrxName());
assertFalse(info.isError());
assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus());
if (!invoice.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName());
assertTrue(error == null);
}
invoice.load(getTrxName());
assertTrue(invoice.isPosted());
MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName());
for (MMatchInv mi : miList) {
if (!mi.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), mi.getAD_Client_ID(), MMatchInv.Table_ID, mi.get_ID(), false, getTrxName());
assertTrue(error == null);
}
mi.load(getTrxName());
assertTrue(mi.isPosted());
Doc doc = DocManager.getDocument(as, MMatchInv.Table_ID, mi.get_ID(), getTrxName());
doc.setC_BPartner_ID(mi.getC_InvoiceLine().getC_Invoice().getC_BPartner_ID());
MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as);
ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName());
MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as);
MAccount acctIPV = pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as);
int C_AcctSchema_ID = as.getC_AcctSchema_ID();
String whereClause2 = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID()
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + C_AcctSchema_ID;
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause2, getTrxName());
BigDecimal invMatchAmt = invoiceLine.getMatchedQty().multiply(invoiceLine.getPriceActual()).setScale(as.getStdPrecision(), RoundingMode.HALF_UP);
mulchCost = mulchCost.setScale(as.getStdPrecision(), RoundingMode.HALF_UP);
for (int id : ids) {
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
if (fa.getAccount_ID() == acctNIR.getAccount_ID())
assertEquals(fa.getAmtAcctDr(), mulchCost, "");
else if (fa.getAccount_ID() == acctInvClr.getAccount_ID())
assertEquals(fa.getAmtAcctCr(), invMatchAmt, "");
else if (fa.getAccount_ID() == acctIPV.getAccount_ID())
assertEquals(fa.getAmtAcctDr().subtract(fa.getAmtAcctCr()), invMatchAmt.subtract(mulchCost), "");
}
}
} finally {
getTrx().rollback();
category.deleteEx(true);
}
}
@Test
/**
* Test the matched invoice posting for credit memo
@ -432,8 +609,8 @@ public class MatchInvTest extends AbstractTestCase {
receipt.load(getTrxName());
assertTrue(receipt.isPosted());
int C_AcctSchema_ID = MClientInfo.get(Env.getCtx()).getC_AcctSchema1_ID();
MAcctSchema as = MAcctSchema.get(Env.getCtx(), C_AcctSchema_ID);
MAcctSchema as = MClient.get(getAD_Client_ID()).getAcctSchema();
BigDecimal invMatchAmt = invoiceLine.getMatchedQty().multiply(invoiceLine.getPriceActual()).setScale(as.getStdPrecision(), RoundingMode.HALF_UP);
MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName());
for (MMatchInv mi : miList) {
if (!mi.isPosted()) {
@ -452,14 +629,14 @@ public class MatchInvTest extends AbstractTestCase {
String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID()
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + C_AcctSchema_ID;
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID();
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName());
for (int id : ids) {
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
if (fa.getAccount_ID() == acctNIR.getAccount_ID())
assertTrue(fa.getAmtAcctDr().compareTo(Env.ZERO) >= 0);
assertEquals(fa.getAmtAcctDr(), invMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctDr().toPlainString());
else if (fa.getAccount_ID() == acctInvClr.getAccount_ID())
assertTrue(fa.getAmtAcctCr().compareTo(Env.ZERO) >= 0);
assertEquals(fa.getAmtAcctCr(), invMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString());
}
}
@ -494,6 +671,7 @@ public class MatchInvTest extends AbstractTestCase {
creditMemo.load(getTrxName());
assertTrue(creditMemo.isPosted());
BigDecimal credMatchAmt = creditMemoLine.getMatchedQty().negate().multiply(creditMemoLine.getPriceActual()).setScale(as.getStdPrecision(), RoundingMode.HALF_UP);
miList = MMatchInv.getInvoiceLine(Env.getCtx(), creditMemoLine.get_ID(), getTrxName());
for (MMatchInv mi : miList) {
if (!mi.isPosted()) {
@ -510,16 +688,16 @@ public class MatchInvTest extends AbstractTestCase {
BigDecimal amtAcctCrInvClr = BigDecimal.ZERO;
String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID()
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + C_AcctSchema_ID;
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID();
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName());
for (int id : ids) {
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
if (fa.getAccount_ID() == acctInvClr.getAccount_ID() && fa.getQty().compareTo(BigDecimal.ZERO) < 0) {
assertTrue(fa.getAmtAcctCr().compareTo(Env.ZERO) >= 0);
assertEquals(fa.getAmtAcctCr(), credMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString());
amtAcctCrInvClr = amtAcctCrInvClr.add(fa.getAmtAcctCr());
}
else if (fa.getAccount_ID() == acctInvClr.getAccount_ID() && fa.getQty().compareTo(BigDecimal.ZERO) > 0) {
assertTrue(fa.getAmtAcctDr().compareTo(Env.ZERO) >= 0);
assertEquals(fa.getAmtAcctDr(), credMatchAmt, "MatchInv incorrect amount posted "+fa.getAmtAcctDr().toPlainString());
amtAcctDrInvClr = amtAcctDrInvClr.add(fa.getAmtAcctDr());
}
}
@ -651,9 +829,8 @@ public class MatchInvTest extends AbstractTestCase {
invoice.load(getTrxName());
assertTrue(invoice.isPosted());
int C_AcctSchema_ID = MClientInfo.get(Env.getCtx()).getC_AcctSchema1_ID(); //usd schema
MAcctSchema as = MAcctSchema.get(Env.getCtx(), C_AcctSchema_ID);
BigDecimal acctAmount = priceInYen.multiply(yenToUsd).multiply(qtyInvoiced).setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP);
MAcctSchema as = MClient.get(getAD_Client_ID()).getAcctSchema();
BigDecimal acctAmount = priceInYen.multiply(yenToUsd).multiply(qtyInvoiced).setScale(as.getStdPrecision(), RoundingMode.HALF_UP);
BigDecimal acctSource = priceInYen.multiply(qtyInvoiced).setScale(japaneseYen.getStdPrecision(), RoundingMode.HALF_UP);
MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName());
for (MMatchInv mi : miList) {
@ -673,22 +850,22 @@ public class MatchInvTest extends AbstractTestCase {
String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID
+ " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID()
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + C_AcctSchema_ID;
+ " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID();
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName());
for (int id : ids) {
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
if (fa.getAccount_ID() == acctNIR.getAccount_ID()) {
assertTrue(fa.getAmtAcctDr().compareTo(Env.ZERO) >= 0);
assertTrue(fa.getAmtAcctDr().toPlainString().compareTo(acctAmount.toPlainString()) == 0, fa.getAmtAcctDr().toPlainString() + " != " + acctAmount.toPlainString());
assertEquals(acctAmount, fa.getAmtAcctDr(), fa.getAmtAcctDr().toPlainString() + " != " + acctAmount.toPlainString());
// verify source amt and currency
assertTrue(fa.getC_Currency_ID() == japaneseYen.getC_Currency_ID());
assertTrue(fa.getAmtSourceDr().toPlainString().compareTo(acctSource.toPlainString()) == 0, fa.getAmtSourceDr().toPlainString() + " != " + acctSource.toPlainString());
assertEquals(acctSource, fa.getAmtSourceDr(), fa.getAmtSourceDr().toPlainString() + " != " + acctSource.toPlainString());
} else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) {
assertTrue(fa.getAmtAcctCr().compareTo(Env.ZERO) >= 0);
assertTrue(fa.getAmtAcctCr().toPlainString().compareTo(acctAmount.toPlainString()) == 0, fa.getAmtAcctCr().toPlainString() + " != " + acctAmount.toPlainString());
assertEquals(acctAmount, fa.getAmtAcctCr(), fa.getAmtAcctCr().toPlainString() + " != " + acctAmount.toPlainString());
// verify source amt and currency
assertTrue(fa.getC_Currency_ID() == japaneseYen.getC_Currency_ID());
assertTrue(fa.getAmtSourceCr().toPlainString().compareTo(acctSource.toPlainString()) == 0, fa.getAmtSourceCr().toPlainString() + " != " + acctSource.toPlainString());
assertEquals(acctSource, fa.getAmtSourceCr(), fa.getAmtSourceCr().toPlainString() + " != " + acctSource.toPlainString());
}
}
}

View File

@ -227,7 +227,7 @@ public class MProductTest extends AbstractTestCase {
count = query.setParameters(product.get_ID()).count();
assertTrue(count > 0, "No Storage Reservation Record");
//this should faiil due to on hand > 0
//this should fail due to on hand > 0
product.setIsActive(false);
assertThrows(AdempiereException.class, () -> product.saveEx());
@ -250,10 +250,14 @@ public class MProductTest extends AbstractTestCase {
MPPProductBOM bom = query.setClient_ID().setOnlyActiveRecords(true).first();
MPPProductBOMLine[] lines = bom.getLines();
final MProduct product = new MProduct(Env.getCtx(), lines[0].getM_Product_ID(), getTrxName());
//clear on hand so that exception is not due to QtyOnHand
DB.executeUpdateEx("UPDATE M_StorageOnHand SET QtyOnHand=0 WHERE M_Product_ID=?", new Object[] {product.get_ID()}, getTrxName());
product.setIsActive(false);
assertThrows(AdempiereException.class, () -> product.saveEx(), "No exception throw for deactivation of product in active BOM");
MProduct parent = new MProduct(Env.getCtx(), bom.getM_Product_ID(), getTrxName());
//clear on hand so that we can deactivate product
DB.executeUpdateEx("UPDATE M_StorageOnHand SET QtyOnHand=0 WHERE M_Product_ID=?", new Object[] {parent.get_ID()}, getTrxName());
parent.setIsActive(false);
parent.saveEx();
bom.load(getTrxName());