IDEMPIERE-5327 Refactoring and clean up of BOM Drop form (#1381)

* IDEMPIERE-5327 Refactoring and clean up of BOM Drop form

* IDEMPIERE-5327 Refactoring and clean up of BOM Drop form

- Fix scrolling of center content.

* IDEMPIERE-5327 Refactoring and clean up of BOM Drop form

- add Feature group support
This commit is contained in:
hengsin 2022-07-07 21:12:36 +08:00 committed by GitHub
parent 2ba935a393
commit d4801e3cb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1033 additions and 364 deletions

View File

@ -18,7 +18,6 @@ import java.text.SimpleDateFormat;
import org.adempiere.webui.ISupportMask; import org.adempiere.webui.ISupportMask;
import org.adempiere.webui.LayoutUtils; import org.adempiere.webui.LayoutUtils;
import org.adempiere.webui.apps.AEnv;
import org.adempiere.webui.event.DialogEvents; import org.adempiere.webui.event.DialogEvents;
import org.adempiere.webui.factory.ButtonFactory; import org.adempiere.webui.factory.ButtonFactory;
import org.adempiere.webui.util.ZKUpdateUtil; import org.adempiere.webui.util.ZKUpdateUtil;
@ -58,6 +57,8 @@ public class ProcessInfoDialog extends Window implements EventListener<Event> {
public static final String INFORMATION = "~./zul/img/msgbox/info-btn.png"; public static final String INFORMATION = "~./zul/img/msgbox/info-btn.png";
public static final String ERROR = "~./zul/img/msgbox/info-btn.png"; public static final String ERROR = "~./zul/img/msgbox/info-btn.png";
private boolean isAutoCloseAfterZoom = false;
/** /**
* @deprecated Should use {@link #ProcessInfoDialog(String, String, ProcessInfo, boolean)} for flexible show message * @deprecated Should use {@link #ProcessInfoDialog(String, String, ProcessInfo, boolean)} for flexible show message
* @param title * @param title
@ -71,11 +72,22 @@ public class ProcessInfoDialog extends Window implements EventListener<Event> {
/** /**
* show result after run a process * show result after run a process
* @param title * @deprecated
* @param header * @param title ignore
* @param header ignore
* @param pi * @param pi
* @param needFillLogFromDb
*/ */
public ProcessInfoDialog(String title, String header, ProcessInfo pi, boolean needFillLogFromDb) { public ProcessInfoDialog(String title, String header, ProcessInfo pi, boolean needFillLogFromDb) {
this(pi, needFillLogFromDb);
}
/**
* show result after run a process
* @param pi
* @param needFillLogFromDb
*/
public ProcessInfoDialog(ProcessInfo pi, boolean needFillLogFromDb) {
if (needFillLogFromDb) if (needFillLogFromDb)
ProcessInfoUtil.setLogFromDB(pi); ProcessInfoUtil.setLogFromDB(pi);
init(pi.getTitle(), null, pi, null); init(pi.getTitle(), null, pi, null);
@ -181,6 +193,10 @@ public class ProcessInfoDialog extends Window implements EventListener<Event> {
if (log.getAD_Table_ID() > 0 if (log.getAD_Table_ID() > 0
&& log.getRecord_ID() > 0) { && log.getRecord_ID() > 0) {
DocumentLink recordLink = new DocumentLink(sb.toString(), log.getAD_Table_ID(), log.getRecord_ID()); DocumentLink recordLink = new DocumentLink(sb.toString(), log.getAD_Table_ID(), log.getRecord_ID());
recordLink.addEventListener(Events.ON_CLICK, e -> {
if (isAutoCloseAfterZoom())
this.detach();
});
pnlMessage.appendChild(recordLink); pnlMessage.appendChild(recordLink);
} else { } else {
@ -207,6 +223,22 @@ public class ProcessInfoDialog extends Window implements EventListener<Event> {
btnOk.focus(); btnOk.focus();
} }
/**
* enable/disable auto close of dialog after zoom using document link
* @param autoClose
*/
public void setAutoCloseAfterZoom(boolean autoClose) {
isAutoCloseAfterZoom = autoClose;
}
/**
*
* @return auto close after zoom state
*/
public boolean isAutoCloseAfterZoom() {
return isAutoCloseAfterZoom;
}
/** /**
* after run a process, call this function to show result in a dialog * after run a process, call this function to show result in a dialog
* @param pi * @param pi
@ -216,7 +248,7 @@ public class ProcessInfoDialog extends Window implements EventListener<Event> {
* just pass false, other pass true to avoid duplicate message * just pass false, other pass true to avoid duplicate message
*/ */
public static ProcessInfoDialog showProcessInfo (ProcessInfo pi, int windowNo, final Component comp, boolean needFillLogFromDb) { public static ProcessInfoDialog showProcessInfo (ProcessInfo pi, int windowNo, final Component comp, boolean needFillLogFromDb) {
ProcessInfoDialog dialog = new ProcessInfoDialog(AEnv.getDialogHeader(Env.getCtx(), windowNo),AEnv.getDialogHeader(Env.getCtx(), windowNo), pi, needFillLogFromDb); ProcessInfoDialog dialog = new ProcessInfoDialog(pi, needFillLogFromDb);
final ISupportMask supportMask = LayoutUtils.showWindowWithMask(dialog, comp, LayoutUtils.OVERLAP_PARENT); final ISupportMask supportMask = LayoutUtils.showWindowWithMask(dialog, comp, LayoutUtils.OVERLAP_PARENT);
dialog.addEventListener(DialogEvents.ON_WINDOW_CLOSE, new EventListener<Event>() { dialog.addEventListener(DialogEvents.ON_WINDOW_CLOSE, new EventListener<Event>() {
@Override @Override

View File

@ -11,12 +11,13 @@ Require-Bundle: org.adempiere.base;bundle-version="0.0.0",
Export-Package: org.compiere.apps, Export-Package: org.compiere.apps,
org.compiere.apps.form, org.compiere.apps.form,
org.compiere.apps.wf, org.compiere.apps.wf,
org.compiere.css,
org.compiere.grid, org.compiere.grid,
org.compiere.grid.ed, org.compiere.grid.ed,
org.compiere.css,
org.compiere.install, org.compiere.install,
org.compiere.minigrid, org.compiere.minigrid,
org.compiere.print, org.compiere.print,
org.idempiere.apps.form,
org.netbeans.api.visual.action, org.netbeans.api.visual.action,
org.netbeans.api.visual.anchor, org.netbeans.api.visual.anchor,
org.netbeans.api.visual.border, org.netbeans.api.visual.border,

View File

@ -0,0 +1,411 @@
/***********************************************************************
* This file is part of iDempiere ERP Open Source *
* http://www.idempiere.org *
* *
* Copyright (C) Contributors *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301, USA. *
* *
* Contributors: *
* - hengsin *
**********************************************************************/
package org.idempiere.apps.form;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.adempiere.exceptions.AdempiereException;
import org.adempiere.util.Callback;
import org.compiere.model.MClient;
import org.compiere.model.MInvoice;
import org.compiere.model.MInvoiceLine;
import org.compiere.model.MOrder;
import org.compiere.model.MOrderLine;
import org.compiere.model.MProduct;
import org.compiere.model.MProject;
import org.compiere.model.MProjectLine;
import org.compiere.model.MRole;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.KeyNamePair;
import org.eevolution.model.MPPProductBOM;
import org.eevolution.model.MPPProductBOMLine;
/**
*
* @author hengsin
*
*/
public class BOMDrop {
/**
* default constructor
*/
public BOMDrop() {
}
/**
*
* @param product
* @return
*/
public int getMaxBOMDeep(MProduct product) {
return getBOMDeep(product, 0);
}
private int getBOMDeep (MProduct product, int curentBomDeep) {
int bomDeep = curentBomDeep;
if (product.isBOM()) {
MPPProductBOM bom = MPPProductBOM.getDefault(product, (String)null);
if (bom != null) {
for (MPPProductBOMLine bomLine : bom.getLines()) {
int testBomDeep = getBOMDeep(bomLine.getProduct(), curentBomDeep + 1);
if (testBomDeep > bomDeep) {
bomDeep = testBomDeep;
}
}
}
}
return bomDeep;
}
/**
* Get Array of draft orders (C_Order_ID, DocumentNo_GrandTotal)
* @param trxName optional trx name
* @return array of draft orders
*/
public KeyNamePair[] getDraftOrders(String trxName) {
String sql = "SELECT C_Order_ID, DocumentNo || '_' || GrandTotal "
+ "FROM C_Order "
+ "WHERE Processed='N' AND DocStatus='DR' "
+ "ORDER BY DocumentNo";
return DB.getKeyNamePairs(trxName, MRole.getDefault().addAccessSQL(
sql, "C_Order", MRole.SQL_NOTQUALIFIED, MRole.SQL_RO), true);
}
/**
* Get Array of open non service Projects (C_Project_ID, Name)
* @param trxName optional trx name
* @return array of non service projects
*/
public KeyNamePair[] getNonServiceProjects(String trxName) {
String sql = "SELECT C_Project_ID, Name "
+ "FROM C_Project "
+ "WHERE Processed='N' AND IsSummary='N' AND IsActive='Y'"
+ " AND ProjectCategory<>'S' "
+ "ORDER BY Name";
return DB.getKeyNamePairs(trxName, MRole.getDefault().addAccessSQL(
sql, "C_Project", MRole.SQL_NOTQUALIFIED, MRole.SQL_RO), true);
}
/**
* Get Array of draft Invoices (C_Invoice_ID, DocumentNo_GrandTotal)
* @param trxName optional trx name
* @return array of draft invoices
*/
public KeyNamePair[] getDraftInvoices(String trxName) {
String sql = "SELECT C_Invoice_ID, DocumentNo || '_' || GrandTotal "
+ "FROM C_Invoice "
+ "WHERE Processed='N' AND DocStatus='DR' "
+ "ORDER BY DocumentNo";
return DB.getKeyNamePairs(trxName, MRole.getDefault().addAccessSQL(
sql, "C_Invoice", MRole.SQL_NOTQUALIFIED, MRole.SQL_RO), true);
}
/**
* Get BOM lines from default BOM of product, sorted by component type and line number
*
* @param product
* @return MPPProductBOMLine[]
*/
public MPPProductBOMLine[] getBOMLine(MProduct product) {
MPPProductBOM bom = MPPProductBOM.getDefault(product, (String)null);
MPPProductBOMLine[] bomLines = bom.getLines();
//sort by feature
Arrays.sort(bomLines, new Comparator<MPPProductBOMLine>() {
@Override
public int compare(MPPProductBOMLine arg0, MPPProductBOMLine arg1) {
String feature1 = arg0.getFeature() != null ? arg0.getFeature() : "";
String feature2 = arg1.getFeature() != null ? arg1.getFeature() : "";
return feature1.compareTo(feature2);
}
});
//sort by component type
Arrays.sort(bomLines, new Comparator<MPPProductBOMLine>() {
@Override
public int compare(MPPProductBOMLine arg0, MPPProductBOMLine arg1) {
return arg0.getComponentType().compareTo(arg1.getComponentType());
}
});
// 2nd sort by Line Number in order to correspond with BOM Structure, patch 2015-03-31
Arrays.sort(bomLines, new Comparator<MPPProductBOMLine>() {
@Override
public int compare(MPPProductBOMLine arg0, MPPProductBOMLine arg1) {
String t1 = String.valueOf(arg0.getLine()+100000);
String t2 = String.valueOf(arg1.getLine()+100000);
return t1.compareTo(t2);
}
});
return bomLines;
}
/**
*
* @param C_Order_ID
* @param selectedItems
* @param trxName
* @return {@link MOrder}
*/
public MOrder saveOrderLines(int C_Order_ID, List<SelectedItem> selectedItems, String trxName) {
MOrder order = new MOrder (Env.getCtx(), C_Order_ID, trxName);
if (order.get_ID() == 0) {
throw new AdempiereException("Not found - C_Order_ID=" + C_Order_ID);
}
for (SelectedItem selectedItem : selectedItems) {
addOrderLine(order, selectedItem.M_Product_ID, selectedItem.qty);
}
return order;
}
/**
*
* @param order
* @param M_Product_ID
* @param qty
* @return {@link MOrderLine}
*/
private MOrderLine addOrderLine(MOrder order, int M_Product_ID, BigDecimal qty) {
// Create Line
MOrderLine ol = new MOrderLine(order);
ol.setM_Product_ID(M_Product_ID, true);
ol.setQty(qty);
ol.setPrice();
ol.setTax();
ol.saveEx();
return ol;
}
/**
*
* @param C_Invoice_ID
* @param selectedItems
* @param trxName
* @return {@link MInvoice}
*/
public MInvoice saveInvoiceLines(int C_Invoice_ID, List<SelectedItem> selectedItems, String trxName) {
MInvoice invoice = new MInvoice (Env.getCtx(), C_Invoice_ID, trxName);
if (invoice.get_ID() == 0) {
throw new AdempiereException("Not found - C_Invoice_ID=" + C_Invoice_ID);
}
for(SelectedItem selectedItem : selectedItems) {
addInvoiceLine(invoice, selectedItem.M_Product_ID, selectedItem.qty);
}
return invoice;
}
/**
*
* @param invoice
* @param M_Product_ID
* @param qty
* @return {@link MInvoiceLine}
*/
private MInvoiceLine addInvoiceLine(MInvoice invoice, int M_Product_ID, BigDecimal qty) {
MInvoiceLine il = new MInvoiceLine (invoice);
il.setM_Product_ID(M_Product_ID, true);
il.setQty(qty);
il.setPrice();
il.setTax();
il.saveEx();
return il;
}
/**
*
* @param C_Project_ID
* @param selectedItems
* @param trxName
* @return {@link MProject}
*/
public MProject saveProjectLines(int C_Project_ID, List<SelectedItem> selectedItems, String trxName) {
MProject project = new MProject (Env.getCtx(), C_Project_ID, trxName);
if (project.get_ID() == 0) {
throw new AdempiereException("Not found - C_Project_ID=" + C_Project_ID);
}
for(SelectedItem selectedItem : selectedItems) {
addProjectLine(project, selectedItem.M_Product_ID, selectedItem.qty);
}
return project;
}
/**
*
* @param project
* @param M_Product_ID
* @param qty
* @return {@link MProjectLine}
*/
private MProjectLine addProjectLine(MProject project, int M_Product_ID, BigDecimal qty) {
MProjectLine pl = new MProjectLine (project);
pl.setM_Product_ID(M_Product_ID);
pl.setPlannedQty(qty);
pl.saveEx();
return pl;
}
/**
* @param product
* @param qty
* @param callback
*/
public void addBOMLines(MProduct product, BigDecimal qty, Callback<BOMLine> callback) {
addBOMLines(product, qty, callback, 0);
}
private void addBOMLines(MProduct product, BigDecimal qty, Callback<BOMLine> callback, int bomLevel) {
MPPProductBOMLine[] bomLines = getBOMLine(product);
for (int i = 0; i < bomLines.length; i++)
addBOMLines(bomLines[i], qty, callback, bomLevel);
}
private void addBOMLines(MPPProductBOMLine productBOMLine, BigDecimal qty, Callback<BOMLine> callback, int bomLevel) {
MProduct product = productBOMLine.getProduct();
if (product == null)
return;
BOMLine bomLine = new BOMLine(productBOMLine, qty, bomLevel);
callback.onCallback(bomLine);
if (product.isBOM() && product.isVerified()) {
addBOMLines(product, bomLine.getLineQty(), callback, bomLevel + 1); // recursive
}
}
/**
* selected bom item
*/
public static class SelectedItem {
private int M_Product_ID;
private BigDecimal qty;
/**
*
* @param M_Product_ID
* @param qty
*/
public SelectedItem(int M_Product_ID, BigDecimal qty) {
this.M_Product_ID = M_Product_ID;
this.qty = qty;
}
}
/**
* bom line
*/
public static class BOMLine {
private MPPProductBOMLine bomLine;
private BigDecimal lineQty;
private String bomType;
private int bomLevel;
private String feature;
/**
*
* @param bomLine
* @param qty
* @param level
*/
public BOMLine(MPPProductBOMLine bomLine, BigDecimal qty, int level) {
this.bomLine = bomLine;
lineQty = bomLine.getQtyBOM().multiply(qty).setScale(MClient.get(Env.getCtx()).getAcctSchema().getStdPrecision(), RoundingMode.HALF_UP);
bomType = bomLine.getComponentType();
if (bomType == null)
bomType = MPPProductBOMLine.COMPONENTTYPE_Component;
bomLevel = level;
feature = bomLine.getFeature();
if (feature == null)
feature = "";
}
/**
*
* @return {@link MPPProductBOMLine}
*/
public MPPProductBOMLine getProductBOMLine() {
return bomLine;
}
/**
*
* @return qty require
*/
public BigDecimal getLineQty() {
return lineQty;
}
/**
*
* @return type of bom (component, variant, phantom, etc)
*/
public String getBOMType() {
return bomType;
}
/**
*
* @return bom level (start from 0)
*/
public int getBOMLevel() {
return bomLevel;
}
/**
*
* @return feature of bom line
*/
public String getFeature() {
return feature;
}
/**
*
* @return parent product
*/
public MProduct getParentProduct() {
MPPProductBOM parent = bomLine.getParent();
return MProduct.get(parent.getM_Product_ID());
}
}
}

View File

@ -0,0 +1,247 @@
/***********************************************************************
* This file is part of iDempiere ERP Open Source *
* http://www.idempiere.org *
* *
* Copyright (C) Contributors *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301, USA. *
* *
* Contributors: *
* - hengsin *
**********************************************************************/
package org.idempiere.test.form;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.compiere.model.MBPartner;
import org.compiere.model.MDocType;
import org.compiere.model.MInvoice;
import org.compiere.model.MInvoiceLine;
import org.compiere.model.MOrder;
import org.compiere.model.MOrderLine;
import org.compiere.model.MProduct;
import org.compiere.model.MProject;
import org.compiere.model.MProjectLine;
import org.compiere.util.Env;
import org.compiere.util.KeyNamePair;
import org.eevolution.model.MPPProductBOMLine;
import org.idempiere.apps.form.BOMDrop;
import org.idempiere.apps.form.BOMDrop.SelectedItem;
import org.idempiere.test.AbstractTestCase;
import org.idempiere.test.DictionaryIDs;
import org.junit.jupiter.api.Test;
/**
*
* @author hengsin
*
*/
public class BOMDropFormTest extends AbstractTestCase {
public BOMDropFormTest() {
}
@Test
public void testDropToOrder() {
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
MBPartner bp = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id);
order.setBPartner(bp);
order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
order.saveEx();
BOMDrop bomDrop = new BOMDrop();
KeyNamePair[] orders = bomDrop.getDraftOrders(getTrxName());
boolean found = false;
for (KeyNamePair knp : orders) {
if (knp.getKey() == order.get_ID()) {
found = true;
break;
}
}
assertTrue(found, "Failed to retrieve open draft order");
MProduct product = MProduct.get(DictionaryIDs.M_Product.P_CHAIR.id);
MPPProductBOMLine[] bomLines = bomDrop.getBOMLine(product);
assertTrue(bomLines.length > 0, "Failed to retrieve BOM lines for " + product.toString());
List<SelectedItem> selectedItems = new ArrayList<>();
List<String> selectedParent = new ArrayList<>();
Map<Integer, Integer> variantMap = new HashMap<>();
bomDrop.addBOMLines(product, new BigDecimal("1"), b -> {
MProduct component = b.getProductBOMLine().getProduct();
if (MPPProductBOMLine.COMPONENTTYPE_Variant.equals(b.getBOMType())) {
if (variantMap.containsKey(b.getBOMLevel()))
return;
else
variantMap.put(b.getBOMLevel(), component.get_ID());
} else if (variantMap.containsKey(b.getBOMLevel())) {
variantMap.remove(b.getBOMLevel());
}
if (b.getBOMLevel() > 0) {
String key = b.getParentProduct().get_ID() + "_" + (b.getBOMLevel()-1);
if (!selectedParent.contains(key))
return;
}
if (component.isBOM() && component.isVerified()) {
String key = component.get_ID()+"_"+b.getBOMLevel();
selectedParent.add(key);
return;
}
SelectedItem si = new SelectedItem(component.get_ID(), b.getLineQty());
selectedItems.add(si);
});
assertEquals(8, selectedItems.size(), "Unexpected number of components");
bomDrop.saveOrderLines(order.getC_Order_ID(), selectedItems, getTrxName());
order.load(getTrxName());
MOrderLine[] orderLines = order.getLines(true, null);
assertEquals(selectedItems.size(), orderLines.length, "Unexpected number of order lines");
}
@Test
public void testDropToInvoice() {
MInvoice invoice = new MInvoice(Env.getCtx(), 0, getTrxName());
MBPartner bp = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id);
invoice.setBPartner(bp);
invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_ARInvoice);
invoice.saveEx();
BOMDrop bomDrop = new BOMDrop();
KeyNamePair[] invoices = bomDrop.getDraftInvoices(getTrxName());
boolean found = false;
for (KeyNamePair knp : invoices) {
if (knp.getKey() == invoice.get_ID()) {
found = true;
break;
}
}
assertTrue(found, "Failed to retrieve open draft invoice");
MProduct product = MProduct.get(DictionaryIDs.M_Product.P_CHAIR.id);
MPPProductBOMLine[] bomLines = bomDrop.getBOMLine(product);
assertTrue(bomLines.length > 0, "Failed to retrieve BOM lines for " + product.toString());
List<SelectedItem> selectedItems = new ArrayList<>();
List<String> selectedParent = new ArrayList<>();
Map<Integer, Integer> variantMap = new HashMap<>();
bomDrop.addBOMLines(product, new BigDecimal("1"), b -> {
MProduct component = b.getProductBOMLine().getProduct();
if (MPPProductBOMLine.COMPONENTTYPE_Variant.equals(b.getBOMType())) {
if (variantMap.containsKey(b.getBOMLevel()))
return;
else
variantMap.put(b.getBOMLevel(), component.get_ID());
} else if (variantMap.containsKey(b.getBOMLevel())) {
variantMap.remove(b.getBOMLevel());
}
if (b.getBOMLevel() > 0) {
String key = b.getParentProduct().get_ID() + "_" + (b.getBOMLevel()-1);
if (!selectedParent.contains(key))
return;
}
if (component.isBOM() && component.isVerified()) {
String key = component.get_ID()+"_"+b.getBOMLevel();
selectedParent.add(key);
return;
}
SelectedItem si = new SelectedItem(component.get_ID(), b.getLineQty());
selectedItems.add(si);
});
assertEquals(8, selectedItems.size(), "Unexpected number of components");
bomDrop.saveInvoiceLines(invoice.get_ID(), selectedItems, getTrxName());
invoice.load(getTrxName());
MInvoiceLine[] invoiceLines = invoice.getLines(true);
assertEquals(selectedItems.size(), invoiceLines.length, "Unexpected number of invoice lines");
}
@Test
public void testDropToProject() {
MProject project = new MProject(Env.getCtx(), 0, getTrxName());
MBPartner bp = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id);
project.setC_BPartner_ID(bp.get_ID());
project.setName("testDropToProject");
project.setC_Currency_ID(DictionaryIDs.C_Currency.USD.id);
project.saveEx();
BOMDrop bomDrop = new BOMDrop();
KeyNamePair[] projects = bomDrop.getNonServiceProjects(getTrxName());
boolean found = false;
for (KeyNamePair knp : projects) {
if (knp.getKey() == project.get_ID()) {
found = true;
break;
}
}
assertTrue(found, "Failed to retrieve open non service project");
MProduct product = MProduct.get(DictionaryIDs.M_Product.P_CHAIR.id);
MPPProductBOMLine[] bomLines = bomDrop.getBOMLine(product);
assertTrue(bomLines.length > 0, "Failed to retrieve BOM lines for " + product.toString());
List<SelectedItem> selectedItems = new ArrayList<>();
List<String> selectedParent = new ArrayList<>();
Map<Integer, Integer> variantMap = new HashMap<>();
bomDrop.addBOMLines(product, new BigDecimal("1"), b -> {
MProduct component = b.getProductBOMLine().getProduct();
if (MPPProductBOMLine.COMPONENTTYPE_Variant.equals(b.getBOMType())) {
if (variantMap.containsKey(b.getBOMLevel()))
return;
else
variantMap.put(b.getBOMLevel(), component.get_ID());
} else if (variantMap.containsKey(b.getBOMLevel())) {
variantMap.remove(b.getBOMLevel());
}
if (b.getBOMLevel() > 0) {
String key = b.getParentProduct().get_ID() + "_" + (b.getBOMLevel()-1);
if (!selectedParent.contains(key))
return;
}
if (component.isBOM() && component.isVerified()) {
String key = component.get_ID()+"_"+b.getBOMLevel();
selectedParent.add(key);
return;
}
SelectedItem si = new SelectedItem(component.get_ID(), b.getLineQty());
selectedItems.add(si);
});
assertEquals(8, selectedItems.size(), "Unexpected number of components");
bomDrop.saveProjectLines(project.get_ID(), selectedItems, getTrxName());
project.load(getTrxName());
MProjectLine[] projectLines = project.getLines();
assertEquals(selectedItems.size(), projectLines.length, "Unexpected number of project lines");
}
}