From 0aa862e1053fe83c2032ed2016bc820228795b2a Mon Sep 17 00:00:00 2001 From: hengsin Date: Fri, 19 Apr 2024 19:19:14 +0800 Subject: [PATCH 001/169] IDEMPIERE-6106 Performance indicator (the Gauge chart): rendering of needle is wrong when value is > the max interval (#2312) --- org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml | 6 +++--- .../src/org/idempiere/zk/billboard/Version.java | 2 +- .../src/web/js/zul/billboard/ext/billboard.gauge.js | 7 ++++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml b/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml index f75d108997..521d0c8666 100644 --- a/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml +++ b/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml @@ -6,7 +6,7 @@ xul/html org.idempiere.zk.billboard.Version - 3.10.3.20231118 + 3.10.3.20240411 billboard @@ -19,7 +19,7 @@ - + - + diff --git a/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java b/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java index 265c5d6bae..5d658e628f 100644 --- a/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java +++ b/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java @@ -34,5 +34,5 @@ public class Version { * Returns the version UID.
* Must match with version-uid value in lang-addon.xml */ - public static final String UID = "3.10.3.20231118"; + public static final String UID = "3.10.3.20240411"; } diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js index 1e31e4e53f..8e9d09ce5a 100644 --- a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js @@ -13,7 +13,7 @@ billboard.GaugeRenderer = class { if (rendererOptions) { if (rendererOptions["showNeedle"] && rendererOptions["showNeedle"] == true) { showNeedle = true; - gauge.title = "\n{=NEEDLE_VALUE}%"; + gauge.title = "\n"+wgt.getSeriesData()[1]+"%"; gauge.width = 20; gauge.label = { format: function(_value, _ratio, id) { return id; } @@ -69,6 +69,11 @@ billboard.GaugeRenderer = class { value: wgt.getSeriesData()[1][0] } }; + if (rendererOptions["intervals"]) { + if (model.arc.needle.value > rendererOptions["intervals"][rendererOptions["intervals"].length-1]) { + model.arc.needle.value = rendererOptions["intervals"][rendererOptions["intervals"].length-1]+3; + } + } model.interaction = { enabled: false }; From e4e27f4ee5dc14b483f29c7d114ca759ee256aca Mon Sep 17 00:00:00 2001 From: Vitor Henrique Dos Santos <82419632+VitorHenri@users.noreply.github.com> Date: Fri, 19 Apr 2024 08:57:54 -0300 Subject: [PATCH 002/169] IDEMPIERE-6110 New Error Message (#2318) * IDEMPIERE-6110 New Error Message * IDEMPIERE-6110 - Added script in iD11 * IDEMPIERE - 6110 Change validation to beforeSave * IDEMPIERE-6110 Improve messsage * IDEMPIERE-6110 - Adjust message --- migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql | 10 ++++++++++ .../iD11/postgresql/202404171125_IDEMPIERE-6110.sql | 7 +++++++ .../src/org/compiere/model/MOrderLine.java | 10 ++++++++++ 3 files changed, 27 insertions(+) create mode 100644 migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql create mode 100644 migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql diff --git a/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql b/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql new file mode 100644 index 0000000000..dbeb7bfc22 --- /dev/null +++ b/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-6110 +SELECT register_migration_script('202404171125_IDEMPIERE-6110.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Apr 17, 2024, 11:25:53 AM BRT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Products or charges configured as ''Freight Product'' or ''Freight Charge'' cannot be added to this order due to the combination of delivery via rule and freight cost rule',0,0,'Y',TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,nextidfunc(9,'N'),'FreightOrderLineNotAllowed','D','204c8bb9-d002-4beb-bbc3-1d1a11d7471d') +; + diff --git a/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql b/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql new file mode 100644 index 0000000000..387e855eca --- /dev/null +++ b/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-6110 +SELECT register_migration_script('202404171125_IDEMPIERE-6110.sql') FROM dual; + +-- Apr 17, 2024, 11:25:53 AM BRT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Products or charges configured as ''Freight Product'' or ''Freight Charge'' cannot be added to this order due to the combination of delivery via rule and freight cost rule',0,0,'Y',TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,nextidfunc(9,'N'),'FreightOrderLineNotAllowed','D','204c8bb9-d002-4beb-bbc3-1d1a11d7471d') +; + diff --git a/org.adempiere.base/src/org/compiere/model/MOrderLine.java b/org.adempiere.base/src/org/compiere/model/MOrderLine.java index e2f8a1e097..0999811023 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrderLine.java +++ b/org.adempiere.base/src/org/compiere/model/MOrderLine.java @@ -906,6 +906,16 @@ public class MOrderLine extends X_C_OrderLine } } + MClientInfo ci = MClientInfo.get(getCtx(), getAD_Client_ID(), get_TrxName()); + if (MOrder.DELIVERYVIARULE_Shipper.equals(getParent().getDeliveryViaRule()) && MOrder.FREIGHTCOSTRULE_FreightIncluded.equals(getParent().getFreightCostRule()) + && ( (getM_Product_ID() > 0 && getM_Product_ID() == ci.getM_ProductFreight_ID()) + || (getC_Charge_ID() > 0 && getC_Charge_ID() == ci.getC_ChargeFreight_ID()) + ) + ) { + log.saveError("Error", Msg.getMsg(getCtx(), "FreightOrderLineNotAllowed")); + return false; + } + return true; } // beforeSave From 0eabdd9d0861ce33592cf550a6e6292332df5533 Mon Sep 17 00:00:00 2001 From: hengsin Date: Sat, 20 Apr 2024 10:55:41 +0800 Subject: [PATCH 003/169] IDEMPIERE-6105 Implement recently access menu items (#2311) --- .../src/org/compiere/model/SystemIDs.java | 1 + .../adempiere/webui/apps/GlobalSearch.java | 9 +- .../webui/apps/MenuSearchController.java | 107 +++++++++++++++--- .../component/FavoriteSimpleTreeModel.java | 29 +---- .../webui/desktop/AbstractDesktop.java | 83 +++++++++++++- .../org/adempiere/webui/desktop/IDesktop.java | 8 +- .../webui/panel/AbstractMenuPanel.java | 37 +----- 7 files changed, 196 insertions(+), 78 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/SystemIDs.java b/org.adempiere.base/src/org/compiere/model/SystemIDs.java index e4270ae248..ab75753e01 100644 --- a/org.adempiere.base/src/org/compiere/model/SystemIDs.java +++ b/org.adempiere.base/src/org/compiere/model/SystemIDs.java @@ -219,6 +219,7 @@ public class SystemIDs public final static int WINDOW_LOT = 257; public final static int WINDOW_MATERIAL_RECEIPT = 184; public final static int WINDOW_MATERIALTRANSACTIONS_INDIRECTUSER = 223; + public final static int WINDOW_MENU = 105; public final static int WINDOW_MY_REQUESTS = 237; public final static int WINDOW_NOTICE = 193; public final static int WINDOW_PAYMENTS_INTO_BATCH = 200031; diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/GlobalSearch.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/GlobalSearch.java index 8876e6ce6d..ff3a5e896a 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/GlobalSearch.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/GlobalSearch.java @@ -101,8 +101,9 @@ public class GlobalSearch extends Div implements EventListener { bandbox.setCtrlKeys("#up#down"); bandbox.addEventListener(Events.ON_CTRL_KEY, this); bandbox.addEventListener(Events.ON_FOCUS, e -> { - if (!bandbox.isOpen()) - bandbox.setOpen(true); + bandbox.setOpen(true); + if (Util.isEmpty(bandbox.getValue(), true) && tabbox.getSelectedIndex() == 0) + menuController.updateRecentItems(); }); Bandpopup popup = new Bandpopup(); @@ -224,6 +225,8 @@ public class GlobalSearch extends Div implements EventListener { } } } else if (event.getName().equals(Events.ON_SELECT)) { + if (tabbox.getSelectedIndex() == 0) + menuController.updateRecentItems(); String value = (String) bandbox.getAttribute(LAST_ONCHANGING_ATTR); if (value == null) { value = bandbox.getValue(); @@ -254,7 +257,7 @@ public class GlobalSearch extends Div implements EventListener { * Handle client info event from browser. */ public void onClientInfo() { - ZKUpdateUtil.setWindowHeightX(bandbox.getDropdown(), ClientInfo.get().desktopHeight-50); + ZKUpdateUtil.setWindowHeightX(bandbox.getDropdown(), ClientInfo.get().desktopHeight-100); } /** diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/MenuSearchController.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/MenuSearchController.java index 28530e2dbb..de4cf4e142 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/MenuSearchController.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/MenuSearchController.java @@ -31,8 +31,11 @@ import org.adempiere.webui.util.TreeNodeAction; import org.adempiere.webui.util.TreeUtils; import org.adempiere.webui.util.ZKUpdateUtil; import org.compiere.model.MMenu; +import org.compiere.model.MPreference; import org.compiere.model.MToolBarButtonRestrict; import org.compiere.model.MTreeNode; +import org.compiere.model.Query; +import org.compiere.model.SystemIDs; import org.compiere.util.Env; import org.compiere.util.Msg; import org.compiere.util.Util; @@ -68,6 +71,9 @@ import org.zkoss.zul.impl.LabelImageElement; */ public class MenuSearchController implements EventListener{ + /** Initial number of menu items loaded into listbox */ + private static final int INITIAL_LOADING_SIZE = 50; + /** Component attribute to hold reference of {@link MTreeNode} **/ public static final String M_TREE_NODE_ATTR = "MTreeNode"; @@ -77,7 +83,7 @@ public class MenuSearchController implements EventListener{ private static final String Z_ICON_STAR = "z-icon-star"; /** Event echo from {@link #search(String)} to initiate search action **/ private static final String ON_SEARCH_ECHO_EVENT = "onSearchEcho"; - /** Event to load all menu items into {@link #listbox}. Default is to load the first 50 only. **/ + /** Event to load all menu items into {@link #listbox}. Default is to load the first {@link #INITIAL_LOADING_SIZE} only. **/ private static final String ON_LOAD_MORE_EVENT = "onLoadMore"; /** {@link Listitem} attribute to store the last timestamp of ON_CLICK or ON_SELECT event **/ private static final String ONSELECT_TIMESTAMP_ATTR = "onselect.timestamp"; @@ -100,20 +106,58 @@ public class MenuSearchController implements EventListener{ private String highlightText = null; + /** List of recently access menu items (AD_Menu_ID) */ + private List recentMenuItemIds = new ArrayList<>(); + /** Event post from {@link #selectTreeitem(Object, Boolean)} **/ private static final String ON_POST_SELECT_TREEITEM_EVENT = "onPostSelectTreeitem"; /** - * @param tree + * @param tree usually the tree instance from {@link} */ public MenuSearchController(Tree tree) { this.tree = tree; } + /** + * Load recently access menu items + */ + private List loadRecentItems() { + List recents = new ArrayList(); + int AD_User_ID = Env.getAD_User_ID(Env.getCtx()); + int AD_Role_ID = Env.getAD_Role_ID(Env.getCtx()); + int AD_Org_ID = 0; + String attribute = AD_Role_ID+"|RecentMenuItems"; + Query query = new Query(Env.getCtx(), MPreference.Table_Name, "PreferenceFor=? AND Attribute=? AND AD_Org_ID=? AND AD_User_ID=? AND AD_Window_ID=?", null); + MPreference preference = query.setClient_ID().setParameters("W", attribute, AD_Org_ID, AD_User_ID, SystemIDs.WINDOW_MENU).first(); + if (preference != null) { + String[] recentItems = preference.getValue().split("[,]"); + for (String recentItem : recentItems) { + recents.add(recentItem); + } + } + return recents; + } + + /** + * If there are changes in the recent menu items for user, reload and update menu items model + */ + public void updateRecentItems() { + List recents = loadRecentItems(); + if (!recents.equals(recentMenuItemIds)) { + recentMenuItemIds = recents; + sortMenuItemModel(); + moveRecentItems(); + if (fullModel != null) + updateListboxModel(model); + } + } + /** * Populate {@link #model} from {@link #tree} */ public void refreshModel() { + recentMenuItemIds = loadRecentItems(); final List list = new ArrayList(); if (tree.getModel() == null) { TreeUtils.traverse(tree, new TreeItemAction() { @@ -130,8 +174,15 @@ public class MenuSearchController implements EventListener{ }); } model = new ListModelList(list, true); - model.sort(new Comparator() { - + sortMenuItemModel(); + moveRecentItems(); + } + + /** + * Sort menu items model in alphabetical order + */ + private void sortMenuItemModel() { + model.sort(new Comparator() { @Override public int compare(MenuItem o1, MenuItem o2) { return o1.getLabel().compareTo(o2.getLabel()); @@ -139,6 +190,35 @@ public class MenuSearchController implements EventListener{ }, true); } + /** + * Move the 7 most recently access menu items to the top of menu items model + */ + private void moveRecentItems() { + if (recentMenuItemIds.size() > 0) { + List recents = new ArrayList(); + for(String id : recentMenuItemIds) { + for(int i = 0; i < model.getSize(); i++) { + if (model.get(i).getData() instanceof Treeitem ti) { + if (ti.getValue() instanceof String tis) { + if (tis.equals(id)) { + recents.add(model.get(i)); + break; + } + } + } + } + } + if (recents.size() > 0) { + for (MenuItem mi : recents) { + model.remove(mi); + } + for(int i = recents.size()-1; i >= 0; i--) { + model.add(0, recents.get(i)); + } + } + } + } + /** * Add treeNode to list * @param list @@ -373,16 +453,16 @@ public class MenuSearchController implements EventListener{ /** * Load {@link #fullModel} to {@link #listbox}. - * Only first 50 loaded to {@link #listbox} initially. + * Only first {@link #INITIAL_LOADING_SIZE} loaded to {@link #listbox} initially. */ private void loadMore() { ListModel listModel = listbox.getModel(); ListModelList lml = (ListModelList) listModel; lml.remove(lml.size()-1); - List subList = fullModel.subList(50, fullModel.size()); + List subList = fullModel.subList(INITIAL_LOADING_SIZE, fullModel.size()); lml.addAll(subList); fullModel = null; - listbox.setSelectedIndex(50); + listbox.setSelectedIndex(INITIAL_LOADING_SIZE); Clients.scrollIntoView(listbox.getSelectedItem()); } @@ -430,8 +510,8 @@ public class MenuSearchController implements EventListener{ } /** - * Handle {@link #ON_POST_SELECT_TREEITEM_EVENT} event. - * Post ON_CLICK event to link ({@link A} or {@link Treerow}). + * Handle {@link #ON_POST_SELECT_TREEITEM_EVENT} event.
+ * Post ON_CLICK event to link ({@link A} or {@link Treerow}, handle in {@link AbstractMenuPanel}). * @param newRecord */ private void onPostSelectTreeitem(Boolean newRecord) { @@ -441,9 +521,10 @@ public class MenuSearchController implements EventListener{ event = new Event(Events.ON_CLICK, tree.getSelectedItem().getTreerow().getFirstChild().getFirstChild(), newRecord); } else - { + { event = new Event(Events.ON_CLICK, tree.getSelectedItem().getTreerow(), newRecord); } + Events.postEvent(event); } @@ -474,15 +555,15 @@ public class MenuSearchController implements EventListener{ /** * Update {@link #listbox} with newModel. - * If newModel has > 50 items, only first 50 is loaded into {@link #listbox}. + * If newModel has > {@link #INITIAL_LOADING_SIZE} items, only first {@link #INITIAL_LOADING_SIZE} is loaded into {@link #listbox}. * User has to click the load more link (...) to load the rest of the items into {@link #listbox}. * @param newModel */ private void updateListboxModel(ListModelList newModel) { fullModel = null; - if (newModel.size() > 50) { + if (newModel.size() > INITIAL_LOADING_SIZE) { List list = newModel.getInnerList(); - List subList = list.subList(0, 50); + List subList = list.subList(0, INITIAL_LOADING_SIZE); fullModel = newModel; newModel = new ListModelList(subList.toArray(new MenuItem[0])); MenuItem more = new MenuItem(); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/FavoriteSimpleTreeModel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/FavoriteSimpleTreeModel.java index 680427b055..343ecdfac0 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/FavoriteSimpleTreeModel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/FavoriteSimpleTreeModel.java @@ -15,19 +15,12 @@ import java.util.List; import java.util.Objects; import java.util.logging.Level; -import org.adempiere.util.Callback; import org.adempiere.webui.ClientInfo; -import org.adempiere.webui.adwindow.ADTabpanel; -import org.adempiere.webui.adwindow.ADWindow; -import org.adempiere.webui.desktop.AbstractDesktop; import org.adempiere.webui.desktop.FavouriteController; -import org.adempiere.webui.desktop.IDesktop; import org.adempiere.webui.exception.ApplicationException; import org.adempiere.webui.session.SessionManager; import org.adempiere.webui.theme.ThemeManager; import org.compiere.model.MMenu; -import org.compiere.model.MQuery; -import org.compiere.model.MTable; import org.compiere.model.MToolBarButtonRestrict; import org.compiere.model.MTreeNode; import org.compiere.util.CLogger; @@ -331,27 +324,7 @@ public class FavoriteSimpleTreeModel extends SimpleTreeModel implements EventLis { try { - MMenu menu = (MMenu) MTable.get(Env.getCtx(), MMenu.Table_ID).getPO(menuID, null); - IDesktop desktop = SessionManager.getAppDesktop(); - if (desktop instanceof AbstractDesktop) - ((AbstractDesktop)desktop).setPredefinedContextVariables(menu.getPredefinedContextVariables()); - - MQuery query = new MQuery(""); - query.addRestriction("1=2"); - query.setRecordCount(0); - - SessionManager.getAppDesktop().openWindow(menu.getAD_Window_ID(), query, new Callback() { - @Override - public void onCallback(ADWindow result) - { - if (result == null) - return; - - result.getADWindowContent().onNew(); - ADTabpanel adtabpanel = (ADTabpanel) result.getADWindowContent().getADTab().getSelectedTabpanel(); - adtabpanel.focusToFirstEditor(false); - } - }); + SessionManager.getAppDesktop().onNewRecord(menuID); } catch (Exception e) { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/AbstractDesktop.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/AbstractDesktop.java index dbaeb15377..2eb1244a81 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/AbstractDesktop.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/AbstractDesktop.java @@ -16,15 +16,24 @@ package org.adempiere.webui.desktop; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import org.adempiere.util.Callback; import org.adempiere.webui.AdempiereWebUI; import org.adempiere.webui.ClientInfo; import org.adempiere.webui.LayoutUtils; +import org.adempiere.webui.adwindow.ADTabpanel; +import org.adempiere.webui.adwindow.ADWindow; import org.adempiere.webui.component.Window; import org.adempiere.webui.event.DialogEvents; import org.adempiere.webui.exception.ApplicationException; import org.adempiere.webui.part.AbstractUIPart; +import org.adempiere.webui.session.SessionManager; import org.compiere.model.MMenu; +import org.compiere.model.MPreference; +import org.compiere.model.MQuery; +import org.compiere.model.Query; +import org.compiere.model.SystemIDs; import org.compiere.util.CLogger; import org.compiere.util.Env; import org.zkoss.zk.ui.Component; @@ -58,8 +67,8 @@ public abstract class AbstractDesktop extends AbstractUIPart implements IDesktop /** * Event listener for menu item selection.
- * Identifies the action associated with the selected - * menu item and acts accordingly. + * Identifies the action associated with the selected menu item and acts accordingly.
+ * Event from favourite panel, global search and application menu tree will be routed here. * * @param menuId Identifier for the selected menu item * @@ -110,8 +119,78 @@ public abstract class AbstractDesktop extends AbstractUIPart implements IDesktop { setPredefinedContextVariables(null); } + updateRecentMenuItem(menuId); } + /** + * Open AD window in new record mode.
+ * Call by global search, application menu tree and favourite panel. + * @param menuId + */ + @Override + public void onNewRecord(int menuId) { + MMenu menu = new MMenu(Env.getCtx(), menuId, null); + setPredefinedContextVariables(menu.getPredefinedContextVariables()); + + MQuery query = new MQuery(""); + query.addRestriction("1=2"); + query.setRecordCount(0); + + SessionManager.getAppDesktop().openWindow(menu.getAD_Window_ID(), query, new Callback() { + @Override + public void onCallback(ADWindow result) { + if(result == null) + return; + + result.getADWindowContent().onNew(); + ADTabpanel adtabpanel = (ADTabpanel) result.getADWindowContent().getADTab().getSelectedTabpanel(); + adtabpanel.focusToFirstEditor(false); + } + }); + updateRecentMenuItem(menuId); + } + + /** + * Perform asynchronous update of recent menu items preference for user + * @param menuId + */ + protected void updateRecentMenuItem(int menuId) { + Runnable runnable = () -> { + int AD_User_ID = Env.getAD_User_ID(Env.getCtx()); + int AD_Role_ID = Env.getAD_Role_ID(Env.getCtx()); + int AD_Org_ID = 0; + String attribute = AD_Role_ID+"|RecentMenuItems"; + Query query = new Query(Env.getCtx(), MPreference.Table_Name, "PreferenceFor=? AND Attribute=? AND AD_Org_ID=? AND AD_User_ID=? AND AD_Window_ID=?", null); + MPreference preference = query.setClient_ID().setParameters("W", attribute, AD_Org_ID, AD_User_ID, SystemIDs.WINDOW_MENU).first(); + if (preference == null) { + preference = new MPreference(Env.getCtx(), 0, null); + preference.setAD_Org_ID(AD_Org_ID); + preference.setPreferenceFor("W"); + preference.setAttribute(attribute); + preference.setAD_User_ID(AD_User_ID); + preference.setValue(Integer.toString(menuId)); + preference.setAD_Window_ID(SystemIDs.WINDOW_MENU); + preference.saveEx(); + } else { + String recentItemValue = preference.getValue(); + List itemList = new ArrayList(); + String[] recentItemValues = recentItemValue.split("[,]"); + String menuIdValue = Integer.toString(menuId); + itemList.add(menuIdValue); + for (int i = 0; itemList.size() < 7 && i < recentItemValues.length; i++) { + if (!recentItemValues[i].equals(menuIdValue)) + itemList.add(recentItemValues[i]); + } + recentItemValue = itemList.stream().collect(Collectors.joining(",")); + preference.setValue(recentItemValue); + preference.saveEx(); + } + }; + Executions.schedule(getComponent().getDesktop(), e -> { + runnable.run(); + }, new Event("onUpdateRecentMenuItem")); + } + /** * @return {@link ClientInfo} */ diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/IDesktop.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/IDesktop.java index e31c4f38b3..8bb70feddb 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/IDesktop.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/IDesktop.java @@ -49,11 +49,17 @@ public interface IDesktop extends UIPart { public ClientInfo getClientInfo(); /** - * + * Launch menu item * @param nodeId */ public void onMenuSelected(int nodeId); + /** + * Launch AD Window in new record mode + * @param menuId + */ + public void onNewRecord(int menuId); + /** * * @param window diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/AbstractMenuPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/AbstractMenuPanel.java index 96e4ac5123..16bf448a69 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/AbstractMenuPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/AbstractMenuPanel.java @@ -20,18 +20,12 @@ import java.util.Collection; import java.util.Enumeration; import java.util.Properties; -import org.adempiere.util.Callback; -import org.adempiere.webui.adwindow.ADTabpanel; -import org.adempiere.webui.adwindow.ADWindow; import org.adempiere.webui.apps.MenuSearchController; -import org.adempiere.webui.desktop.AbstractDesktop; -import org.adempiere.webui.desktop.IDesktop; import org.adempiere.webui.exception.ApplicationException; import org.adempiere.webui.session.SessionManager; import org.adempiere.webui.theme.ThemeManager; import org.adempiere.webui.util.ZKUpdateUtil; import org.compiere.model.MMenu; -import org.compiere.model.MQuery; import org.compiere.model.MToolBarButtonRestrict; import org.compiere.model.MTree; import org.compiere.model.MTreeNode; @@ -291,7 +285,8 @@ public abstract class AbstractMenuPanel extends Panel implements EventListener + * The event from global search and application menu tree will be routed to here. * @param comp * @param eventData */ @@ -361,31 +356,11 @@ public abstract class AbstractMenuPanel extends Panel implements EventListener() { - @Override - public void onCallback(ADWindow result) { - if(result == null) - return; - - result.getADWindowContent().onNew(); - ADTabpanel adtabpanel = (ADTabpanel) result.getADWindowContent().getADTab().getSelectedTabpanel(); - adtabpanel.focusToFirstEditor(false); - } - }); + SessionManager.getAppDesktop().onNewRecord(menuId); } catch (Exception e) { From e577f4577097fa865c6039ddef8b95ba48fbfd62 Mon Sep 17 00:00:00 2001 From: hengsin Date: Mon, 22 Apr 2024 11:44:05 +0800 Subject: [PATCH 004/169] IDEMPIERE-6114 Eclipse 2024-03 create false changes for org.adempiere.base.AnnotationBasedModelFactory.xml (#2321) trivial fix --- .../OSGI-INF/org.adempiere.base.AnnotationBasedModelFactory.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.adempiere.base/OSGI-INF/org.adempiere.base.AnnotationBasedModelFactory.xml b/org.adempiere.base/OSGI-INF/org.adempiere.base.AnnotationBasedModelFactory.xml index ef1b4a950d..05aaa4a340 100644 --- a/org.adempiere.base/OSGI-INF/org.adempiere.base.AnnotationBasedModelFactory.xml +++ b/org.adempiere.base/OSGI-INF/org.adempiere.base.AnnotationBasedModelFactory.xml @@ -1,5 +1,5 @@ - + From 611a863707d98e11f87113b6f7896825d8e8b308 Mon Sep 17 00:00:00 2001 From: Nicolas Micoud <58596990+nmicoud@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:28:52 +0200 Subject: [PATCH 005/169] IDEMPIERE-6115 : Fix NPE in ADTabpanel while selecting a tree item (#2323) --- .../WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java index 7d4e471be1..55111a4247 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java @@ -1536,7 +1536,7 @@ DataStatusListener, IADTabpanel, IdSpace, IFieldEditorContainer } else if (treePanel != null && event.getTarget() == treePanel.getTree()) { Treeitem item = treePanel.getTree().getSelectedItem(); - if (item.getValue() != null) + if (item != null && item.getValue() != null) navigateTo((DefaultTreeNode)item.getValue()); } else if (ON_DEFER_SET_SELECTED_NODE.equals(event.getName())) { From 13bf6bf51d5c106fd7677df2097123e1786fa56e Mon Sep 17 00:00:00 2001 From: Nicolas Micoud <58596990+nmicoud@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:31:34 +0200 Subject: [PATCH 006/169] IDEMPIERE-5136: Set Messages at tenant level - missing Oracle DROP INDEX (#2322) --- .../202404220830_IDEMPIERE-5136_MissingDropIndex.sql | 8 ++++++++ .../202404220830_IDEMPIERE-5136_MissingDropIndex.sql | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 migration/iD11/oracle/202404220830_IDEMPIERE-5136_MissingDropIndex.sql create mode 100644 migration/iD11/postgresql/202404220830_IDEMPIERE-5136_MissingDropIndex.sql diff --git a/migration/iD11/oracle/202404220830_IDEMPIERE-5136_MissingDropIndex.sql b/migration/iD11/oracle/202404220830_IDEMPIERE-5136_MissingDropIndex.sql new file mode 100644 index 0000000000..f87d9fb8b7 --- /dev/null +++ b/migration/iD11/oracle/202404220830_IDEMPIERE-5136_MissingDropIndex.sql @@ -0,0 +1,8 @@ +-- IDEMPIERE-5136 +SELECT register_migration_script('202404220830_IDEMPIERE-5136_MissingDropIndex.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +DROP INDEX AD_MESSAGE_TRL_KEY +; diff --git a/migration/iD11/postgresql/202404220830_IDEMPIERE-5136_MissingDropIndex.sql b/migration/iD11/postgresql/202404220830_IDEMPIERE-5136_MissingDropIndex.sql new file mode 100644 index 0000000000..ceb792daea --- /dev/null +++ b/migration/iD11/postgresql/202404220830_IDEMPIERE-5136_MissingDropIndex.sql @@ -0,0 +1,4 @@ +-- IDEMPIERE-5136 +SELECT register_migration_script('202404220830_IDEMPIERE-5136_MissingDropIndex.sql') FROM dual; + +-- only for Oracle as there was a missing DROP INDEX instruction From ac5f84f48d969601aa2d413f15a1e68a1cc21b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Tak=C3=A1cs?= <93127072+PeterTakacs300@users.noreply.github.com> Date: Tue, 23 Apr 2024 13:05:31 +0200 Subject: [PATCH 007/169] IDEMPIERE-6107 Improve Table Partitioning API (#2315) * IDEMPIERE-6107 - Improve Table Partitioning API --- .../src/org/compiere/model/MTable.java | 43 +++-- .../org/compiere/model/MTablePartition.java | 164 ++++++++++++++++++ .../partition/TablePartitionService.java | 20 ++- 3 files changed, 214 insertions(+), 13 deletions(-) create mode 100644 org.adempiere.base/src/org/compiere/model/MTablePartition.java diff --git a/org.adempiere.base/src/org/compiere/model/MTable.java b/org.adempiere.base/src/org/compiere/model/MTable.java index d924232831..ef40b27e0d 100644 --- a/org.adempiere.base/src/org/compiere/model/MTable.java +++ b/org.adempiere.base/src/org/compiere/model/MTable.java @@ -247,18 +247,18 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport return null; } // getClass - /** - * UUID based Constructor - * @param ctx Context - * @param AD_Table_UU UUID key - * @param trxName Transaction - */ - public MTable(Properties ctx, String AD_Table_UU, String trxName) { - super(ctx, AD_Table_UU, trxName); + /** + * UUID based Constructor + * @param ctx Context + * @param AD_Table_UU UUID key + * @param trxName Transaction + */ + public MTable(Properties ctx, String AD_Table_UU, String trxName) { + super(ctx, AD_Table_UU, trxName); if (Util.isEmpty(AD_Table_UU)) setInitialDefaults(); - } - + } + /** * Standard Constructor * @param ctx context @@ -1098,4 +1098,27 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport return indexName.toString(); } + /** + * Get Partition Name of the table of the given level + * @param tableName + * @param primaryLevelOnly - if true, ignore the sub-partition, if exists + * @return table partition name, or empty + */ + public static String getPartitionName(Properties ctx, String tableName, boolean primaryLevelOnly, String trxName) { + if(Util.isEmpty(tableName)) + return ""; + + String[] partitionColsAll = MTablePartition.getPartitionKeyColumns(ctx, tableName, trxName); + + if(partitionColsAll.length == 0) + return tableName; + + int level = primaryLevelOnly ? 1 : partitionColsAll.length; + StringBuilder partitionName = new StringBuilder(); + partitionName.append(tableName); + for(int i = 0; i < level; i++) { + partitionName.append("_").append(partitionColsAll[i]); + } + return partitionName.toString(); + } } // MTable diff --git a/org.adempiere.base/src/org/compiere/model/MTablePartition.java b/org.adempiere.base/src/org/compiere/model/MTablePartition.java new file mode 100644 index 0000000000..3a1a55de52 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/MTablePartition.java @@ -0,0 +1,164 @@ +/********************************************************************** +* 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: * +* - Peter Takacs, Cloudempiere * +**********************************************************************/ +package org.compiere.model; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; + +import org.compiere.util.DB; + +public class MTablePartition extends X_AD_TablePartition { + + /** Serial Version ID */ + private static final long serialVersionUID = 1L; + + /** + * @param ctx + * @param AD_TablePartition_ID + * @param trxName + */ + public MTablePartition(Properties ctx, int AD_TablePartition_ID, String trxName) { + super(ctx, AD_TablePartition_ID, trxName); + } + + /** + * @param ctx + * @param AD_TablePartition_ID + * @param trxName + * @param virtualColumns + */ + public MTablePartition(Properties ctx, int AD_TablePartition_ID, String trxName, String[] virtualColumns) { + super(ctx, AD_TablePartition_ID, trxName, virtualColumns); + } + + /** + * @param ctx + * @param rs + * @param trxName + */ + public MTablePartition(Properties ctx, ResultSet rs, String trxName) { + super(ctx, rs, trxName); + } + + /** + * UUID based Constructor + * @param ctx + * @param AD_TablePartition_UU + * @param trxName + */ + public MTablePartition(Properties ctx, String AD_TablePartition_UU, String trxName) { + super(ctx, AD_TablePartition_UU, trxName); + } + + /** + * UUID based Constructor + * @param ctx + * @param AD_TablePartition_UU + * @param trxName + * @param virtualColumns + */ + public MTablePartition(Properties ctx, String AD_TablePartition_UU, String trxName, String[] virtualColumns) { + super(ctx, AD_TablePartition_UU, trxName, virtualColumns); + } + + /** + * Check if the given table partition exists for the table + * @param tableName + * @param partitionName + * @return true if partition already exists + */ + public static boolean partitionExists(Properties ctx, String tableName, String partitionName, String trxName) { + return partitionExists(ctx, MTable.getTable_ID(tableName), partitionName, trxName); + } + + /** + * Check if the given table partition exists for the table + * @param tableId + * @param partitionName + * @return true if partition already exists + */ + public static boolean partitionExists(Properties ctx, int tableId, String partitionName, String trxName) { + boolean returnVal = false; + StringBuilder sqlSelect = new StringBuilder(); + sqlSelect.append("SELECT 1 FROM ") + .append(X_AD_TablePartition.Table_Name) + .append(" WHERE AD_Table_ID = ? AND UPPER(Name) LIKE UPPER(?) "); + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = DB.prepareStatement(sqlSelect.toString(), trxName); + ps.setInt(1,tableId); + ps.setString(2, partitionName); + rs = ps.executeQuery(); + if (rs.next()) { + returnVal = true; + } + } catch (SQLException e) { + e.printStackTrace(); + } finally { + DB.close(rs, ps); + rs = null; ps = null; + } + + return returnVal; + } + + /** + * Get list of partition key columns for the given table + * @param ctx + * @param tableName + * @param trxName + * @return array of column names + */ + public static String[] getPartitionKeyColumns(Properties ctx, String tableName, String trxName) { + return getPartitionKeyColumns(ctx, MTable.getTable_ID(tableName), trxName); + } + + /** + * Get list of partition key columns for the given table + * @param ctx + * @param tableId + * @param trxName + * @return array of column names + */ + public static String[] getPartitionKeyColumns(Properties ctx, int tableId, String trxName) { + String whereClause = "AD_Table_ID=? AND IsPartitionKey='Y'"; + List keyCols = new Query(ctx, MColumn.Table_Name, whereClause, trxName) + .setParameters(tableId) + .setOnlyActiveRecords(true) + .setOrderBy("SeqNoPartition ASC") + .list(); + + List keyColNames = keyCols.stream() + .map(MColumn::getColumnName) + .collect(Collectors.toList()); + + return keyColNames.toArray(new String[0]); + } +} diff --git a/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java b/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java index 8b7c1799cf..f3a20ab9e5 100644 --- a/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java +++ b/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java @@ -63,10 +63,24 @@ public class TablePartitionService implements ITablePartitionService { """; return DB.getSQLValueEx(trxName, sql, table.getTableName()) == 1; } - - private String getDefaultPartitionName(MTable table) + + /** + * Get default partition name for table + * @param table + * @return String default partition name for table + */ + public String getDefaultPartitionName(MTable table) { - return table.getTableName() + "_default_partition"; + return getDefaultPartitionName(table.getTableName()); + } + + /** + * Get default partition name for table + * @param tableName + * @return String default partition name for table + */ + public String getDefaultPartitionName(String tableName) { + return tableName + "_default_partition"; } /** From 67948cbad2d3f0e18d6ab480cc9a7500173d6aa4 Mon Sep 17 00:00:00 2001 From: Zuhri Utama Date: Tue, 23 Apr 2024 21:28:52 +0700 Subject: [PATCH 008/169] IDEMPIERE-6113 : Date Range boxes on Info Window are too small (#2320) * IDEMPIERE-6113 : Date Range boxes on Info Window are too small * Update InfoWindow.java Update class based on the ideas from Zuhri and Heng Sin * - Fix issue getting the number of columns used on a row * - no need for extra column as the parameter grid has already a size of 95% * - getRowSize must also exclude DateRangeButton on Order Info * - increase size as suggested by Zuhri --------- Co-authored-by: Carlos Ruiz Co-authored-by: hengsin --- .../org/adempiere/webui/info/InfoWindow.java | 85 ++++++++++++------- 1 file changed, 52 insertions(+), 33 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java index 477ac312ff..79410e7273 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java @@ -164,7 +164,7 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL /** * generated serial id */ - private static final long serialVersionUID = 4004251745919433247L; + private static final long serialVersionUID = 615852605072547785L; private static final String ON_QUERY_AFTER_CHANGE = "onQueryAfterChange"; @@ -1797,13 +1797,16 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL Columns columns = new Columns(); parameterGrid.appendChild(columns); noOfParameterColumn = getNoOfParameterColumns(); - for(int i = 0; i < noOfParameterColumn; i++) - columns.appendChild(new Column()); - - Column column = new Column(); - ZKUpdateUtil.setWidth(column, "100px"); - column.setAlign("right"); - columns.appendChild(column); + String labelWidth = ( 100 / ( 3 * ( getNoOfParameterColumns() / 2 ) ) ) + "%"; + String fieldWidth = ( 100 * 2 / ( 3 * ( getNoOfParameterColumns() / 2 ) ) ) + "%"; + for(int i = 0; i < noOfParameterColumn; i++) { + Column column = new Column(); + if (i%2 == 0) + column.setWidth(labelWidth); + else + column.setWidth(fieldWidth); + columns.appendChild(column); + } if (parameterGrid.getRows() != null) parameterGrid.getRows().detach(); @@ -1844,9 +1847,14 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL if (checkAND == null) { if (parameterGrid.getRows() != null && parameterGrid.getRows().getFirstChild() != null) { - Row row = (Row) parameterGrid.getRows().getFirstChild(); - int col = row.getChildren().size(); - while (col < 6) { + Row row = (Row) parameterGrid.getRows().getLastChild(); + int col = getRowSize(row); + if (col == getNoOfParameterColumns()) { + row = new Row(); + parameterGrid.getRows().appendChild(row); + col = 0; + } + while (col < getNoOfParameterColumns()-1) { row.appendChild(new Space()); col++; } @@ -1895,7 +1903,21 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL identifiers = list; } } - + + /** + * Get number of children from the row except Menupopup + * @param row + * @return + */ + private int getRowSize(Row row) { + int cnt = 0; + for (Component comp : row.getChildren()) { + if (! (comp instanceof Menupopup || comp instanceof DateRangeButton) ) + cnt++; + } + return cnt; + } + /** * evaluate display logic for input parameters */ @@ -1950,6 +1972,7 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL editor.dynamicDisplay(); editor.addValueChangeListener(this); editor.fillHorizontal(); + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor.getComponent(), "100%"); if (editor instanceof WTableDirEditor) { ((WTableDirEditor) editor).setRetainSelectedValueAfterRefresh(false); @@ -1960,6 +1983,14 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL editor2.dynamicDisplay(); editor2.addValueChangeListener(this); editor2.fillHorizontal(); + if (DisplayType.isDate(mField.getDisplayType())) { + // give space for the Date Range button + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor.getComponent(), "44%"); + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor2.getComponent(), "44%"); + } else { + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor.getComponent(), "50%"); + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor2.getComponent(), "50%"); + } } } Label label = editor.getLabel(); @@ -2034,17 +2065,8 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL else { panel = (Row) parameterGrid.getRows().getLastChild(); - if (panel.getChildren().size() == getNoOfParameterColumns()) + if (getRowSize(panel) == getNoOfParameterColumns()) { - if (parameterGrid.getRows().getChildren().size() == 1) - { - createAndCheckbox(); - panel.appendChild(checkAND); - } - else - { - panel.appendChild(new Space()); - } panel = new Row(); parameterGrid.getRows().appendChild(panel); } @@ -2067,21 +2089,18 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL outerParent.setStyle("display: flex;"); outerParent.appendChild(fieldEditor); if(fieldEditor2 != null) { - Label dash = new Label("-"); - dash.setStyle("padding-left:3px;padding-right:3px;display:flex;align-items:center;"); - outerParent.appendChild(dash); + outerParent.setStyle("display: flex; flex-wrap: wrap;"); outerParent.appendChild(fieldEditor2); if(editor.getGridField() != null && DisplayType.isDate(editor.getGridField().getDisplayType())) { DateRangeButton drb = (new DateRangeButton(editor, editor2)); outerParent.appendChild(drb); - drb.setDateButtonVisible(false); - } - if (fieldEditor instanceof InputElement && fieldEditor2 instanceof InputElement) { - ((InputElement)fieldEditor).setPlaceholder(Msg.getMsg(Env.getCtx(), "From")); - ((InputElement)fieldEditor2).setPlaceholder(Msg.getMsg(Env.getCtx(), "To")); - } else if (fieldEditor instanceof NumberBox && fieldEditor2 instanceof NumberBox) { - ((NumberBox)fieldEditor).getDecimalbox().setPlaceholder(Msg.getMsg(Env.getCtx(), "From")); - ((NumberBox)fieldEditor2).getDecimalbox().setPlaceholder(Msg.getMsg(Env.getCtx(), "To")); + } + if (fieldEditor instanceof InputElement && fieldEditor2 instanceof InputElement) { + ((InputElement)fieldEditor).setPlaceholder(Msg.getMsg(Env.getCtx(), "From")); + ((InputElement)fieldEditor2).setPlaceholder(Msg.getMsg(Env.getCtx(), "To")); + } else if (fieldEditor instanceof NumberBox && fieldEditor2 instanceof NumberBox) { + ((NumberBox)fieldEditor).getDecimalbox().setPlaceholder(Msg.getMsg(Env.getCtx(), "From")); + ((NumberBox)fieldEditor2).getDecimalbox().setPlaceholder(Msg.getMsg(Env.getCtx(), "To")); } } panel.appendChild(outerParent); From bd26ce7b3798be1603b5e2741acc957773a43afc Mon Sep 17 00:00:00 2001 From: hieplq Date: Thu, 25 Apr 2024 10:37:33 +0700 Subject: [PATCH 009/169] IDEMPIERE-6121:github action fail by tycho update => setup maven to 3.9 (#2330) * IDEMPIERE-6121:github action fail by tycho update => setup maven to 3.9 1. update maven to 9.3 2. step setup java generate 2 file .m2/toolchains.xml .m2/settings.xml bellow case make conflict 1. setup java for java-17 => create toolchains.xml point to java-17 home 2. cache save toolchains.xml 3. update java to java-17.1 create toolchains.xml point to java-17.1 home 4. cache restore old toolchains.xml make conflict because it point to java-17 home so change to cache only .m2/repository * IDEMPIERE-6121:github action fail by tycho update => setup maven to 3.9 update some action, add comment --- .github/workflows/codeql-analysis.yml | 72 +++++++++++++++++---------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bf8f8bc231..6709a52b42 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,33 +35,42 @@ jobs: # Install Java - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '${{ env.java }}' distribution: ${{ env.java_distribution }} + check-latest: true + + # setup maven to 3.9 for tycho + - name: Set up Maven + uses: stCarolas/setup-maven@v5 + with: + maven-version: 3.9.6 # on case PR it check out to commit is merger of PR to base (master) - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # fetch all commit so sornar can know who change a line, it's resolved Warning: Shallow clone detected, no blame information will be provided. You can convert to non-shallow with 'git fetch --unshallow'. fetch-depth: 0 + # restore sonar cache - name: Cache sonar material restore id: cache-sonar-material-restore - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | ~/.sonar/cache key: ${{ runner.os }}-sonar-${{ env.branch_name }} + # restore maven cache - name: Cache maven material restore id: cache-maven-material-restore - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | - ~/.m2 - key: ${{ runner.os }}-maven-${{ env.branch_name }} + ~/.m2/repository + key: ${{ runner.os }}-maven-${{ env.branch_name }}-repository # run sonar on master only because sonar for PR come from other repository isn't support at moment (already on develop) # https://stackoverflow.com/a/39720346 @@ -78,19 +87,21 @@ jobs: codeql="-Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Denforcer.skip -Dmaven.javadoc.skip -DskipTests -Dmaven.test.skip.exec -Dlicense.skip=true -Drat.skip=true" mvn -B -V -e $codeql $sonar $sonarProject $sonarExclusions package org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + # save sonar cache - name: Cache sonar material save - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: | ~/.sonar/cache key: ${{ steps.cache-sonar-material-restore.outputs.cache-primary-key }} - + + # save maven cache - name: Cache maven material save - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: | - ~/.m2 - key: ${{ steps.cache-maven-material-restore.outputs.cache-primary-key }} + ~/.m2/repository + key: ${{ steps.cache-maven-material-restore.outputs.cache-primary-key }}-repository analyze_java_codeQL: name: Analyze java by code QL @@ -103,26 +114,34 @@ jobs: # Install Java - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '${{ env.java }}' distribution: ${{ env.java_distribution }} + check-latest: true + + # setup maven to 3.9 for tycho + - name: Set up Maven + uses: stCarolas/setup-maven@v5 + with: + maven-version: 3.9.6 # on case PR it check out to commit is merger of PR to base (master) - name: Checkout repository - uses: actions/checkout@v3 - + uses: actions/checkout@v4 + + # restore maven cache - name: Cache maven material restore id: cache-maven-material-restore - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | - ~/.m2 - key: ${{ runner.os }}-maven-${{ env.branch_name }} + ~/.m2/repository + key: ${{ runner.os }}-maven-${{ env.branch_name }}-repository # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: java # If you wish to specify custom queries, you can do so here or in a config file. @@ -131,17 +150,18 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 + # save maven cache - name: Cache maven material save - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: | - ~/.m2 - key: ${{ steps.cache-maven-material-restore.outputs.cache-primary-key }} + ~/.m2/repository + key: ${{ steps.cache-maven-material-restore.outputs.cache-primary-key }}-repository analyze_javascript_codeQL: @@ -155,12 +175,12 @@ jobs: # on case PR it check out to commit is merger of PR to base (master) - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: javascript - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 8e08cb5cc8d7f8377669a1086b714937f2ff7a47 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 26 Apr 2024 04:46:44 +0200 Subject: [PATCH 010/169] IDEMPIERE-6125 Allow monitoring null transactions on idempiereMonitor (#2332) * IDEMPIERE-6125 Allow monitoring null transactions on idempiereMonitor * - add warning --- .../src/org/compiere/db/StatementProxy.java | 6 +++ .../org/compiere/model/SystemProperties.java | 11 ++++++ .../src/org/compiere/util/Trx.java | 37 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/org.adempiere.base/src/org/compiere/db/StatementProxy.java b/org.adempiere.base/src/org/compiere/db/StatementProxy.java index 9fd61a7d8e..2da803be93 100644 --- a/org.adempiere.base/src/org/compiere/db/StatementProxy.java +++ b/org.adempiere.base/src/org/compiere/db/StatementProxy.java @@ -25,6 +25,7 @@ import java.util.logging.Level; import javax.sql.RowSet; import org.adempiere.exceptions.DBException; +import org.compiere.model.SystemProperties; import org.compiere.util.CCachedRowSet; import org.compiere.util.CLogger; import org.compiere.util.CStatementVO; @@ -133,8 +134,11 @@ public class StatementProxy implements InvocationHandler { } } Method m = p_stmt.getClass().getMethod(name, method.getParameterTypes()); + String nullTrxName = null; try { + if (SystemProperties.isTraceNullTrxConnection() && p_vo.getTrxName() == null) + nullTrxName = Trx.registerNullTrx(); return m.invoke(p_stmt, args); } catch (InvocationTargetException e) @@ -143,6 +147,8 @@ public class StatementProxy implements InvocationHandler { } finally { + if (nullTrxName != null && p_vo.getTrxName() == null) + Trx.unregisterNullTrx(nullTrxName); if (log.isLoggable(Level.FINE) && logSql != null && logOperation != null) { log.fine((DisplayType.getDateFormat(DisplayType.DateTime)).format(new Date(System.currentTimeMillis()))+","+logOperation+","+logSql+","+(p_vo.getTrxName() != null ? p_vo.getTrxName() : "")+" (end)"); diff --git a/org.adempiere.base/src/org/compiere/model/SystemProperties.java b/org.adempiere.base/src/org/compiere/model/SystemProperties.java index d9d368d27f..db44544c79 100644 --- a/org.adempiere.base/src/org/compiere/model/SystemProperties.java +++ b/org.adempiere.base/src/org/compiere/model/SystemProperties.java @@ -55,6 +55,7 @@ public class SystemProperties { private static final String PropertyFile = "PropertyFile"; private static final String PropertyHomeFile = "PropertyHomeFile"; private static final String TestOCI = "TestOCI"; + private static final String TRACE_NULL_TRX_CONNECTION = "TRACE_NULL_TRX_CONNECTION"; private static final String ZK_THEME = MSysConfig.ZK_THEME; private static final String ZkUnitTest = "ZkUnitTest"; @@ -258,4 +259,14 @@ public class SystemProperties { return "true".equals(System.getProperty(ZkUnitTest)); } + /** + * TRACE_NULL_TRX_CONNECTION=true to allow tracing null transactions on idempiereMonitor + * WARNING! this setting can have a big performance impact, it is disabled by default + * use it with care in production just temporarily to trace problematic connection slowness or leaks + * @return + */ + public static boolean isTraceNullTrxConnection() { + return "true".equals(System.getProperty(TRACE_NULL_TRX_CONNECTION)); + } + } diff --git a/org.adempiere.base/src/org/compiere/util/Trx.java b/org.adempiere.base/src/org/compiere/util/Trx.java index 8dd3302b41..0ae8b2d17c 100644 --- a/org.adempiere.base/src/org/compiere/util/Trx.java +++ b/org.adempiere.base/src/org/compiere/util/Trx.java @@ -34,6 +34,7 @@ import java.util.logging.Level; import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.DBException; import org.compiere.Adempiere; +import org.compiere.db.StatementProxy; import org.compiere.model.MSysConfig; import org.compiere.model.PO; @@ -876,4 +877,40 @@ public class Trx }, 2, TimeUnit.SECONDS); } } + + /** + * Register a null trx + * @return + */ + public static String registerNullTrx() { + String nullTrxName = "NullTrx_" + UUID.randomUUID().toString(); + Trx nullTrx = new Trx(nullTrxName); + nullTrx.trace = new Exception(); + nullTrx.m_startTime = System.currentTimeMillis(); + String displayName = null; + StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + Optional stackName = walker.walk(frames -> frames.map( + stackFrame -> stackFrame.getClassName() + "." + + stackFrame.getMethodName() + ":" + + stackFrame.getLineNumber()) + .filter(f -> ! (f.startsWith(Trx.class.getName() + ".") || f.startsWith(StatementProxy.class.getName() + ".") || f.startsWith("jdk.proxy") || f.startsWith("org.compiere.util.DB."))) + .findFirst()); + displayName = (stackName.orElse(null)); + if (displayName != null) + nullTrx.setDisplayName(displayName); + s_cache.put(nullTrxName, nullTrx); + return nullTrxName; + } + + /** + * Unregister a null trx + * @param nullTrxName + */ + public static void unregisterNullTrx(String nullTrxName) { + Trx nullTrx = s_cache.get(nullTrxName); + nullTrx.setDisplayName(null); + nullTrx.trace = null; + s_cache.remove(nullTrxName); + } + } // Trx From 7d70e4ef0282f273dbd8c94e9d9e388c2f705322 Mon Sep 17 00:00:00 2001 From: Nicolas Micoud <58596990+nmicoud@users.noreply.github.com> Date: Fri, 26 Apr 2024 04:47:33 +0200 Subject: [PATCH 011/169] IDEMPIERE-6126: ProcessParameterPanel - method to get ProcessInfo (#2333) --- .../org/adempiere/webui/apps/ProcessParameterPanel.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java index d144f70c1a..e4471a02ad 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java @@ -104,7 +104,7 @@ public class ProcessParameterPanel extends Panel implements /** * generated serial id */ - private static final long serialVersionUID = -8847249274740131848L; + private static final long serialVersionUID = -8476698839617674953L; /** Event post from {@link #valueChange(ValueChangeEvent)} **/ private static final String ON_POST_EDITOR_VALUE_CHANGE_EVENT = "onPostEditorValueChange"; @@ -1327,6 +1327,11 @@ public class ProcessParameterPanel extends Panel implements m_processInfo = processInfo; } + + public ProcessInfo getProcessInfo() { + return m_processInfo; + } + /** * focus to first visible field editor. * @return true if there is at least one visible field editor. From 44688e37a1aaed0f097c394953e20a80a0f69804 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 26 Apr 2024 04:49:10 +0200 Subject: [PATCH 012/169] IDEMPIERE-6044 CLogMgt.enable not working properly after InitialClientSetup (#2328) * IDEMPIERE-6044 CLogMgt.enable not working properly after InitialClientSetup * - add deprecation comment --- .../src/org/compiere/print/PrintUtil.java | 31 +++++-------------- .../src/org/compiere/util/CLogMgt.java | 15 ++++++++- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/print/PrintUtil.java b/org.adempiere.base/src/org/compiere/print/PrintUtil.java index de8480dcb2..18c0175fe3 100644 --- a/org.adempiere.base/src/org/compiere/print/PrintUtil.java +++ b/org.adempiere.base/src/org/compiere/print/PrintUtil.java @@ -47,11 +47,6 @@ import javax.print.attribute.standard.JobPriority; import javax.print.attribute.standard.OrientationRequested; import javax.swing.JDialog; -import org.adempiere.process.UUIDGenerator; -import org.compiere.model.MColumn; -import org.compiere.model.PO; -import org.compiere.model.X_AD_PrintForm; -import org.compiere.util.CLogMgt; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.Env; @@ -453,8 +448,7 @@ public class PrintUtil { if (log.isLoggable(Level.CONFIG)) log.config("AD_Client_ID=" + AD_Client_ID); Properties ctx = Env.getCtx(); - CLogMgt.enable(false); - // + // Order Template int Order_PrintFormat_ID = MPrintFormat.copyToClient(ctx, PRINTFORMAT_ORDER_HEADER_TEMPLATE, AD_Client_ID, trxName).get_ID(); int OrderLine_PrintFormat_ID = MPrintFormat.copyToClient(ctx, PRINTFORMAT_ORDER_LINETAX_TEMPLATE, AD_Client_ID, trxName).get_ID(); @@ -475,27 +469,18 @@ public class PrintUtil int Remittance_PrintFormat_ID = MPrintFormat.copyToClient(ctx, PRINTFORMAT_PAYSELECTION_REMITTANCE__TEMPLATE, AD_Client_ID, trxName).get_ID(); updatePrintFormatHeader(Remittance_PrintFormat_ID, RemittanceLine_PrintFormat_ID, trxName); - // TODO: MPrintForm - int AD_PrintForm_ID = DB.getNextID (AD_Client_ID, "AD_PrintForm", null); - String sql = "INSERT INTO AD_PrintForm(AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_PrintForm_ID," + StringBuilder sql = new StringBuilder("INSERT INTO AD_PrintForm(AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_PrintForm_ID,AD_PrintForm_UU," + "Name,Order_PrintFormat_ID,Invoice_PrintFormat_ID,Remittance_PrintFormat_ID,Shipment_PrintFormat_ID)" - // - + " VALUES (" + AD_Client_ID + ",0,'Y',getDate(),0,getDate(),0," + AD_PrintForm_ID + "," - + "'" + Msg.translate(ctx, "Standard") + "'," - + Order_PrintFormat_ID + "," + Invoice_PrintFormat_ID + "," - + Remittance_PrintFormat_ID + "," + Shipment_PrintFormat_ID + ")"; - int no = DB.executeUpdate(sql, trxName); + + " VALUES (") + .append(AD_Client_ID).append(",0,'Y',getDate(),0,getDate(),0,").append(AD_PrintForm_ID).append(",generate_uuid(),") + .append(DB.TO_STRING(Msg.translate(ctx, "Standard"))).append(",") + .append(Order_PrintFormat_ID).append(",").append(Invoice_PrintFormat_ID).append(",") + .append(Remittance_PrintFormat_ID).append(",").append(Shipment_PrintFormat_ID).append(")"); + int no = DB.executeUpdateEx(sql.toString(), trxName); if (no != 1) log.log(Level.SEVERE, "PrintForm NOT inserted"); - if (DB.isGenerateUUIDSupported()) - DB.executeUpdateEx("UPDATE AD_PrintForm SET AD_PrintForm_UU=generate_uuid() WHERE AD_PrintForm_UU IS NULL", trxName); - else - UUIDGenerator.updateUUID(MColumn.get(ctx, X_AD_PrintForm.Table_Name, PO.getUUIDColumnName(X_AD_PrintForm.Table_Name)), trxName); - - // - CLogMgt.enable(true); } // createDocuments /** diff --git a/org.adempiere.base/src/org/compiere/util/CLogMgt.java b/org.adempiere.base/src/org/compiere/util/CLogMgt.java index af154d0188..8ebbd4e9ac 100644 --- a/org.adempiere.base/src/org/compiere/util/CLogMgt.java +++ b/org.adempiere.base/src/org/compiere/util/CLogMgt.java @@ -450,18 +450,31 @@ public class CLogMgt return Level.INFO.intValue() >= getLevelAsInt(); } // isLevelFine + /** + * Save the current level when disabling log + */ + private static Level previousLevel = null; /** * Enable/Disable logging (of handlers) * @param enableLogging true if logging enabled + * @deprecated not recommended to use, problematic method to enable/disable the log globally */ public static void enable (boolean enableLogging) { Logger rootLogger = getRootLogger(); if (enableLogging) - setLevel(rootLogger.getLevel()); + { + if (previousLevel != null) + setLevel(previousLevel); + else + setLevel(rootLogger.getLevel()); + reInit(); + previousLevel = null; + } else { + previousLevel = rootLogger.getLevel(); setLevel(Level.OFF); } } // enable From f82c6a9468a0cd4e5bddf141105429e174ae52f8 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 26 Apr 2024 14:47:32 +0200 Subject: [PATCH 013/169] IDEMPIERE-6128 make org.idempiere.db.debug show just native Postgresql by default (#2335) --- .../src/org/compiere/model/SystemProperties.java | 9 +++++++++ .../src/org/compiere/dbPort/Convert_PostgreSQL.java | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/SystemProperties.java b/org.adempiere.base/src/org/compiere/model/SystemProperties.java index db44544c79..0f77fc12f6 100644 --- a/org.adempiere.base/src/org/compiere/model/SystemProperties.java +++ b/org.adempiere.base/src/org/compiere/model/SystemProperties.java @@ -47,6 +47,7 @@ public class SystemProperties { private static final String org_adempiere_po_useTimeoutForUpdate = "org.adempiere.po.useTimeoutForUpdate"; private static final String org_compiere_report_path = "org.compiere.report.path"; private static final String org_idempiere_db_debug = "org.idempiere.db.debug"; + private static final String org_idempiere_db_debug_convert = "org.idempiere.db.debug.convert"; private static final String org_idempiere_db_debug_filter = "org.idempiere.db.debug.filter"; private static final String org_idempiere_FileLogPrefix = "org.idempiere.FileLogPrefix"; private static final String org_idempiere_postgresql_URLParameters = "org.idempiere.postgresql.URLParameters"; @@ -178,6 +179,14 @@ public class SystemProperties { return System.getProperty(org_idempiere_db_debug_filter); } + /** + * org.idempiere.db.convert=true to print also Oracle SQL Statements being converted + * @return + */ + public static boolean isDBDebugConvert() { + return "true".equals(System.getProperty(org_idempiere_db_debug_convert)); + } + /** * org.idempiere.FileLogPrefix defines the template prefix to write logs * @return diff --git a/org.compiere.db.postgresql.provider/src/org/compiere/dbPort/Convert_PostgreSQL.java b/org.compiere.db.postgresql.provider/src/org/compiere/dbPort/Convert_PostgreSQL.java index d40ff07f88..9d05187b3a 100644 --- a/org.compiere.db.postgresql.provider/src/org/compiere/dbPort/Convert_PostgreSQL.java +++ b/org.compiere.db.postgresql.provider/src/org/compiere/dbPort/Convert_PostgreSQL.java @@ -148,9 +148,9 @@ public class Convert_PostgreSQL extends Convert_SQL92 { boolean print = true; if (filterPgDebug != null) print = statement.matches(filterPgDebug); - // log.warning("Oracle -> " + oraStatement); if (print) { - log.warning("Oracle -> " + sqlStatement); + if (SystemProperties.isDBDebugConvert()) + log.warning("Oracle -> " + sqlStatement); log.warning("PgSQL -> " + statement); } } From 439b158224c64fb769ddfd2445e89545e19edad4 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 26 Apr 2024 23:47:55 +0200 Subject: [PATCH 014/169] IDEMPIERE-6130 Show gc information (#2339) --- .../src/org/adempiere/webui/window/AboutWindow.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java index 18717dc075..d478607021 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java @@ -120,9 +120,15 @@ public class AboutWindow extends Window implements EventListener { */ private void init() { + Runtime runtime = Runtime.getRuntime(); + long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory(); System.runFinalization(); System.gc(); - + try {Thread.sleep(100);} catch (InterruptedException e) {} // Give some time for GC to complete + long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory(); + long freedMemory = usedMemoryAfter - usedMemoryBefore; + log.warning(String.format("Memory: total %,d, before gc: %,d, after gc %,d, freed by gc %,d bytes%n", runtime.totalMemory(), usedMemoryBefore, usedMemoryAfter, freedMemory)); + this.setPosition("center"); this.setTitle(ThemeManager.getBrowserTitle()); this.setSclass("popup-dialog about-window"); From 4c7a1b124fbb9f35e0d358d47136b0f7a8144adb Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Sat, 27 Apr 2024 12:57:53 +0200 Subject: [PATCH 015/169] IDEMPIERE-6130 Move garbage collection to a button in About Window (#2341) --- .../adempiere/webui/window/AboutWindow.java | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java index d478607021..4b066a3483 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java @@ -85,9 +85,9 @@ import org.zkoss.zul.Vbox; */ public class AboutWindow extends Window implements EventListener { /** - * generated serial id + * */ - private static final long serialVersionUID = -4235323239552159150L; + private static final long serialVersionUID = -5590393631865037228L; /** Logger */ private static final CLogger log = CLogger.getCLogger(AboutWindow.class); @@ -104,6 +104,7 @@ public class AboutWindow extends Window implements EventListener { protected Button btnAdempiereLog; protected Button btnReloadLogProps; + protected Button btnGC; private Listbox levelListBox; @@ -120,15 +121,6 @@ public class AboutWindow extends Window implements EventListener { */ private void init() { - Runtime runtime = Runtime.getRuntime(); - long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory(); - System.runFinalization(); - System.gc(); - try {Thread.sleep(100);} catch (InterruptedException e) {} // Give some time for GC to complete - long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory(); - long freedMemory = usedMemoryAfter - usedMemoryBefore; - log.warning(String.format("Memory: total %,d, before gc: %,d, after gc %,d, freed by gc %,d bytes%n", runtime.totalMemory(), usedMemoryBefore, usedMemoryAfter, freedMemory)); - this.setPosition("center"); this.setTitle(ThemeManager.getBrowserTitle()); this.setSclass("popup-dialog about-window"); @@ -257,11 +249,11 @@ public class AboutWindow extends Window implements EventListener { } } + MUser user = MUser.get(Env.getCtx()); levelListBox.setEnabled(false); - if (Env.getAD_Client_ID(Env.getCtx()) == 0) + if (user.isAdministrator()) { - MUser user = MUser.get(Env.getCtx()); - if (user.isAdministrator()) + if (Env.getAD_Client_ID(Env.getCtx()) == 0) { levelListBox.setEnabled(true); levelListBox.setTooltiptext("Set trace level. Warning: this will effect all session not just the current session"); @@ -274,6 +266,13 @@ public class AboutWindow extends Window implements EventListener { hbox.appendChild(new Space()); hbox.appendChild(btnAdempiereLog); + ZKUpdateUtil.setHflex(hbox, "1"); + ZKUpdateUtil.setVflex(hbox, "0"); + vbox.appendChild(hbox); + hbox = new Hbox(); + hbox.setAlign("center"); + hbox.setPack("start"); + btnReloadLogProps = new Button("Reload Log Props"); btnReloadLogProps.setTooltiptext("Reload the configuration of log levels from idempiere.properties file"); LayoutUtils.addSclass("txt-btn", btnReloadLogProps); @@ -281,6 +280,12 @@ public class AboutWindow extends Window implements EventListener { hbox.appendChild(new Space()); hbox.appendChild(btnReloadLogProps); } + btnGC = new Button("Garbage Collect"); + btnGC.setTooltiptext("Perform a Garbage Collection on the JVM"); + LayoutUtils.addSclass("txt-btn", btnGC); + btnGC.addEventListener(Events.ON_CLICK, this); + hbox.appendChild(new Space()); + hbox.appendChild(btnGC); } ZKUpdateUtil.setHflex(hbox, "1"); @@ -506,6 +511,8 @@ public class AboutWindow extends Window implements EventListener { downloadAdempiereLogFile(); else if (event.getTarget() == btnReloadLogProps) reloadLogProps(); + else if (event.getTarget() == btnGC) + garbageCollection(); else if (event.getTarget() == levelListBox) setTraceLevel(); else if (Events.ON_CLICK.equals(event.getName())) @@ -562,6 +569,29 @@ public class AboutWindow extends Window implements EventListener { } } + /** + * Call JVM GC + */ + private void garbageCollection() { + Runtime runtime = Runtime.getRuntime(); + long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory(); + System.runFinalization(); + System.gc(); + try {Thread.sleep(1000);} catch (InterruptedException e) {} // Wait 1 second for GC to complete + long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory(); + long freedMemory = usedMemoryAfter - usedMemoryBefore; + String msg = String.format("Memory: total %,d, before gc: %,d, after gc %,d, freed by gc %,d bytes%n", runtime.totalMemory(), usedMemoryBefore, usedMemoryAfter, freedMemory); + log.warning(msg); + msg = String.format("Memory in bytes:
    " + + "
  • Total = %,d
  • " + + "
  • Used before gc = %,d
  • " + + "
  • Used after gc = %,d
  • " + + "
  • Freed by gc = %,d
  • " + + "
", + runtime.totalMemory(), usedMemoryBefore, usedMemoryAfter, freedMemory); + Dialog.info(0, "", msg, "JVM Garbage Collection"); + } + /** * Change trace/log level */ From 0e17a03d37a8ca9b90e0c03ca254249a5cd9bfb7 Mon Sep 17 00:00:00 2001 From: Zuhri Utama Date: Tue, 30 Apr 2024 18:33:19 +0700 Subject: [PATCH 016/169] IDEMPIERE-6127 : Improve Report Process Parameter for Mobile Screen (#2334) * IDEMPIERE-6127 : Improve Report Process Parameter for Mobile Screen * apply patch from hengsin - Combine summary checkbox and label into one component - update default theme version --- .../WEB-INF/src/metainfo/zk/lang-addon.xml | 2 +- .../org/adempiere/webui/apps/AbstractProcessDialog.java | 4 +--- .../theme/default/css/fragment/parameter-process.css.dsp | 9 ++++++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml b/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml index b9f6181713..548caa52f6 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml +++ b/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml @@ -58,6 +58,6 @@ Copyright (C) 2007 Ashley G Ramdass (ADempiere WebUI). - + diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AbstractProcessDialog.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AbstractProcessDialog.java index eb388180f6..44e0abaa94 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AbstractProcessDialog.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AbstractProcessDialog.java @@ -511,10 +511,9 @@ public abstract class AbstractProcessDialog extends Window implements IProcessUI //summary option chbIsSummary = new Checkbox(); chbIsSummary.setSclass("option-input-parameter"); + chbIsSummary.setLabel(Msg.translate(Env.getCtx(), "Summary")); Label lPrintFormat = new Label(Msg.translate(Env.getCtx(), "AD_PrintFormat_ID")); lPrintFormat.setSclass("option-input-parameter print-format-label"); - Label lIsSummary = new Label(Msg.translate(Env.getCtx(), "Summary")); - lIsSummary.setSclass("option-input-parameter"); //print formats MClient client = MClient.get(m_ctx); @@ -531,7 +530,6 @@ public abstract class AbstractProcessDialog extends Window implements IProcessUI } fPrintFormat.getComponent().setSclass("option-input-parameter print-format-list"); fPrintFormat.getComponent().setPlaceholder(lPrintFormat.getValue()); - reportOptionLayout.appendChild(lIsSummary); reportOptionLayout.appendChild(chbIsSummary); } diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/theme/default/css/fragment/parameter-process.css.dsp b/org.adempiere.ui.zk/WEB-INF/src/web/theme/default/css/fragment/parameter-process.css.dsp index 75f20d8916..1848678028 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/web/theme/default/css/fragment/parameter-process.css.dsp +++ b/org.adempiere.ui.zk/WEB-INF/src/web/theme/default/css/fragment/parameter-process.css.dsp @@ -31,7 +31,14 @@ when detect side effect, fix to only apply for parameter window*/ } .report-option-container { - overflow-x: auto; + display: flex; + flex-wrap: wrap; + flex-direction: row; + align-items: center; +} + +.report-option-container div { + padding: 2px; } /* Chromium based browsers + Safari */ From 95fee2623b20d0339ed79ba9b6138e0d1c38c9c8 Mon Sep 17 00:00:00 2001 From: Zuhri Utama Date: Thu, 2 May 2024 16:51:17 +0700 Subject: [PATCH 017/169] IDEMPIERE-6118 : Missing resources on idempiere server monitor (#2326) * IDEMPIERE-6118 : Missing resources on idempiere server monitor * remove unused class and add table style * rename css file and remove javaclient condition --- .../src/org/compiere/util/WebDoc.java | 14 +- .../resources/css/idempiereMonitor.css | 46 ++++++ org.adempiere.server/standard.css | 151 ------------------ 3 files changed, 48 insertions(+), 163 deletions(-) create mode 100644 org.adempiere.server/resources/css/idempiereMonitor.css delete mode 100644 org.adempiere.server/standard.css diff --git a/org.adempiere.base/src/org/compiere/util/WebDoc.java b/org.adempiere.base/src/org/compiere/util/WebDoc.java index b735db112e..23ab5f17ad 100644 --- a/org.adempiere.base/src/org/compiere/util/WebDoc.java +++ b/org.adempiere.base/src/org/compiere/util/WebDoc.java @@ -163,12 +163,7 @@ public class WebDoc return; // css, js - if (javaClient) { - m_head.addElement(new StoredHtmlSrc("STYLE", "org/compiere/images/standard.css")); - } else { - m_head.addElement(new link(WebEnv.getStylesheetURL(), link.REL_STYLESHEET, link.TYPE_CSS)); - m_head.addElement(new script((Element)null, WebEnv.getBaseDirectory("/js/standard.js"))); - } + m_head.addElement(new link("/resources/css/idempiereMonitor.css", link.REL_STYLESHEET, link.TYPE_CSS)); m_head.addElement(new meta().setHttpEquiv("Content-Type", "text/html; charset=UTF-8")); m_head.addElement(new meta().setName("description", "iDempiere HTML UI")); @@ -185,13 +180,8 @@ public class WebDoc // Logo m_topRight = new td().setAlign("right"); - if (javaClient) { - m_topRight.addElement(new img("res:org/compiere/images/iD10030.png") + m_topRight.addElement(new img("/webui/images/header-logo.png") .setAlign(AlignType.RIGHT).setAlt("iDempiere")); - } else { - m_topRight.addElement(new img("/webui/images/header-logo.png") - .setAlign(AlignType.RIGHT).setAlt("iDempiere")); - } m_topRow.addElement(m_topRight); m_table.addElement(m_topRow); // diff --git a/org.adempiere.server/resources/css/idempiereMonitor.css b/org.adempiere.server/resources/css/idempiereMonitor.css new file mode 100644 index 0000000000..6fea17dde7 --- /dev/null +++ b/org.adempiere.server/resources/css/idempiereMonitor.css @@ -0,0 +1,46 @@ +/* Adempiere Root (c) Jorg Janke */ +/* $Id: standard.css,v 1.1 2006/04/21 18:04:14 jjanke Exp $ */ +body{ + background-color: #FFFFFF; + color: #000000; + font-size: 0.8em; + font-family: Verdana, Arial, sans-serif; + line-height: 1.3em; +} + +a{ + color: #3465a4; + text-decoration: none; +} + +a:hover{ + color: #24272c; +} + +h1{ + color: #FF0000; + font-size: x-large; + margin-bottom: 10px; + margin-top: 0; +} + +hr{ + color: #000099; + padding-bottom: 0; + padding-top: 0; +} + +table { + font-size: 1em; + column-span: none; + border-collapse: collapse; +} + +p{ + text-align: justify; +} + +th{ + background-color: #E6E6FA; + text-align: left; +} \ No newline at end of file diff --git a/org.adempiere.server/standard.css b/org.adempiere.server/standard.css deleted file mode 100644 index abf0eea4da..0000000000 --- a/org.adempiere.server/standard.css +++ /dev/null @@ -1,151 +0,0 @@ -/* Adempiere Root (c) Jorg Janke */ -/* $Id: standard.css,v 1.1 2006/04/21 18:04:14 jjanke Exp $ */ -body{ - background-color: #FFFFFF; - color: #000000; - font-size: 76%; - font-family: Verdana, Arial, sans-serif; - line-height: 1.3em; -} - -a{ - color: #3465a4; - text-decoration: none; -} - -a:hover{ - text-decoration: none; -} - -h1{ - color: #FF0000; - font-size: x-large; - margin-bottom: 10px; - margin-top: 0; -} - -h2{ - color: #000066; - font-size: large; -} - -h3{ - color: #0000CC; - font-size: medium; - font-style: normal; - font-weight: bold; -} - -h4{ - color: #6600CC; - font-size: medium; - font-style: italic; -} - -h5{ - color: #660099; - font-size: medium; - font-weight: normal; -} - -h6{ - font-size: larger; - font-weight: bold; -} - -hr{ - color: #000099; - padding-bottom: 0; - padding-top: 0; -} - -p{ - text-align: justify; -} - -th{ - background-color: #E6E6FA; - text-align: left; -} - -caption{ - color: #660099; - text-align: left; - font-style: italic; - font-weight: bolder; -} - - -.menuDetail{ - color: #660099; - font-family: Arial,Helvetica,sans-serif; - font-size: 12px; - padding-bottom: 0; - padding-left: 20px; - padding-top: 0; - text-decoration: none; -} - -.menuDetail:hover{ - color: #660099; - font-family: Arial,Helvetica,sans-serif; - font-size: 12px; - padding-bottom: 0; - padding-left: 20px; - padding-top: 0; - text-decoration: none; - background-color: #99FFFF; -} - -.menuMain{ - color: #000066; - font-family: Arial,Helvetica,sans-serif; - font-size: 16px; - text-align: left; - text-decoration: none; -} - -.menuMain:hover{ - color: #000066; - font-family: Arial,Helvetica,sans-serif; - font-size: 16px; - text-align: left; - text-decoration: none; - background-color: #99FFFF; -} - -.menuSub{ - color: #000066; - font-family: Arial,Helvetica,sans-serif; - font-size: 14px; - padding-left: 10px; - text-align: left; - text-decoration: none; -} - -.menuSub:hover{ - color: #000066; - font-family: Arial,Helvetica,sans-serif; - font-size: 14px; - padding-left: 10px; - text-align: left; - text-decoration: none; - background-color: #99FFFF; -} - -.Cerror{ -background:#FF4A4A; -} -.Cmandatory{ -background:#9DFFFF; -} -.Cbasket{ - font-size: 9px; - display: inline; -} -#imgButton{ -border-style:outset; -} -#imgButtonPressed{ -border-style:inset; -} From 766ca60cd44499f8c4cd6ce818162fe3e1a30fd6 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 2 May 2024 13:20:17 +0200 Subject: [PATCH 018/169] IDEMPIERE-6118 fix broken build (#2344) --- org.adempiere.server/build.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/org.adempiere.server/build.properties b/org.adempiere.server/build.properties index 10ee4c5eb4..349f899d96 100644 --- a/org.adempiere.server/build.properties +++ b/org.adempiere.server/build.properties @@ -13,7 +13,6 @@ bin.includes = META-INF/,\ header_bar.jpg,\ index.html,\ robots.txt,\ - standard.css,\ webstart.jpg,\ zip.gif,\ plugin.xml,\ From 42e998ff7d9473017acfedae87e53dae42966ee7 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 3 May 2024 12:51:47 +0200 Subject: [PATCH 019/169] IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) (#2340) * IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) * - create SysConfig - add Dialog when reaching max query records * - minor fix comment - add timeout management to MLookup query * - Fix for the GridTable.Loader.Open issue * - add SysConfig and Messages - add showing error message when the number of records loaded in background exceed the allowed - add timeout to GridTable.fillBuffer --------- Co-authored-by: hengsin --- .../oracle/202404302320_IDEMPIERE-6123.sql | 22 +++++ .../202404302320_IDEMPIERE-6123.sql | 19 +++++ .../org/compiere/model/DataStatusEvent.java | 8 +- .../src/org/compiere/model/GridTab.java | 16 +++- .../src/org/compiere/model/GridTable.java | 84 ++++++++++++++----- .../src/org/compiere/model/MLookup.java | 51 +++++++---- .../src/org/compiere/model/MSysConfig.java | 4 +- .../adempiere/webui/adwindow/ADTabpanel.java | 21 +---- .../adwindow/AbstractADWindowContent.java | 52 ++++++++++-- .../adempiere/webui/window/FindWindow.java | 28 +++---- 10 files changed, 215 insertions(+), 90 deletions(-) create mode 100644 migration/iD11/oracle/202404302320_IDEMPIERE-6123.sql create mode 100644 migration/iD11/postgresql/202404302320_IDEMPIERE-6123.sql diff --git a/migration/iD11/oracle/202404302320_IDEMPIERE-6123.sql b/migration/iD11/oracle/202404302320_IDEMPIERE-6123.sql new file mode 100644 index 0000000000..2d3aacce8e --- /dev/null +++ b/migration/iD11/oracle/202404302320_IDEMPIERE-6123.sql @@ -0,0 +1,22 @@ +-- IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) +SELECT register_migration_script('202404302320_IDEMPIERE-6123.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Apr 30, 2024, 11:20:08 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200244,0,0,TO_TIMESTAMP('2024-04-30 23:20:08','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-04-30 23:20:08','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS','1','Timeout for the initial count on windows','D','C','5fae1af7-74ca-41d8-bbd3-d506c6c23b6a') +; + +-- Apr 30, 2024, 11:22:16 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200245,0,0,TO_TIMESTAMP('2024-04-30 23:22:16','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-04-30 23:22:16','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GLOBAL_MAX_QUERY_RECORDS','100000','Maximum number of records allowed to search in a window, can be overriden per Role or Tab','D','C','840fb67c-4609-41f2-9e20-e0ea9d839065') +; + +-- Apr 30, 2024, 11:23:28 PM CEST +UPDATE AD_Message SET MsgText='The query returned more records than allowed, consider adding more filters.',Updated=TO_TIMESTAMP('2024-04-30 23:23:28','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Message_ID=852 +; + +-- Apr 30, 2024, 11:24:06 PM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The initial count query timed out, loading records ...',0,0,'Y',TO_TIMESTAMP('2024-04-30 23:24:06','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-30 23:24:06','YYYY-MM-DD HH24:MI:SS'),100,200887,'CountQueryTimeoutLoadBackground','D','988292d7-175f-41c2-b560-43d62b8326a9') +; + diff --git a/migration/iD11/postgresql/202404302320_IDEMPIERE-6123.sql b/migration/iD11/postgresql/202404302320_IDEMPIERE-6123.sql new file mode 100644 index 0000000000..ad27fcaac4 --- /dev/null +++ b/migration/iD11/postgresql/202404302320_IDEMPIERE-6123.sql @@ -0,0 +1,19 @@ +-- IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) +SELECT register_migration_script('202404302320_IDEMPIERE-6123.sql') FROM dual; + +-- Apr 30, 2024, 11:20:08 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200244,0,0,TO_TIMESTAMP('2024-04-30 23:20:08','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-04-30 23:20:08','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS','1','Timeout for the initial count on windows','D','C','5fae1af7-74ca-41d8-bbd3-d506c6c23b6a') +; + +-- Apr 30, 2024, 11:22:16 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200245,0,0,TO_TIMESTAMP('2024-04-30 23:22:16','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-04-30 23:22:16','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GLOBAL_MAX_QUERY_RECORDS','100000','Maximum number of records allowed to search in a window, can be overriden per Role or Tab','D','C','840fb67c-4609-41f2-9e20-e0ea9d839065') +; + +-- Apr 30, 2024, 11:23:28 PM CEST +UPDATE AD_Message SET MsgText='The query returned more records than allowed, consider adding more filters.',Updated=TO_TIMESTAMP('2024-04-30 23:23:28','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Message_ID=852 +; + +-- Apr 30, 2024, 11:24:06 PM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The initial count query timed out, loading records ...',0,0,'Y',TO_TIMESTAMP('2024-04-30 23:24:06','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-30 23:24:06','YYYY-MM-DD HH24:MI:SS'),100,200887,'CountQueryTimeoutLoadBackground','D','988292d7-175f-41c2-b560-43d62b8326a9') +; + diff --git a/org.adempiere.base/src/org/compiere/model/DataStatusEvent.java b/org.adempiere.base/src/org/compiere/model/DataStatusEvent.java index 5fbbb1e161..313c44331c 100644 --- a/org.adempiere.base/src/org/compiere/model/DataStatusEvent.java +++ b/org.adempiere.base/src/org/compiere/model/DataStatusEvent.java @@ -257,10 +257,9 @@ public final class DataStatusEvent extends EventObject implements Serializable retValue.append(m_currentRow+1); // of retValue.append("/"); - if (m_allLoaded) - retValue.append(m_totalRows); - else - retValue.append(m_loadedRows).append("->").append(m_totalRows); + if (! m_allLoaded) + retValue.append(m_loadedRows).append("->"); + retValue.append(m_totalRows); // return retValue.toString(); } // getMessage @@ -358,6 +357,7 @@ public final class DataStatusEvent extends EventObject implements Serializable e.m_changedColumn == m_changedColumn && Util.equals(e.m_columnName, m_columnName) && e.m_currentRow == m_currentRow && + e.m_loadedRows == m_loadedRows && e.isInitEdit == isInitEdit; } diff --git a/org.adempiere.base/src/org/compiere/model/GridTab.java b/org.adempiere.base/src/org/compiere/model/GridTab.java index 29297e0e24..359186de4a 100644 --- a/org.adempiere.base/src/org/compiere/model/GridTab.java +++ b/org.adempiere.base/src/org/compiere/model/GridTab.java @@ -235,6 +235,8 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable public static final String CTX_IsLookupOnlySelection = "_TabInfo_IsLookupOnlySelection"; public static final String CTX_IsAllowAdvancedLookup = "_TabInfo_IsAllowAdvancedLookup"; + public static final int DEFAULT_GLOBAL_MAX_QUERY_RECORDS = 100000; + /** * Tab loader for Tabs > 0 */ @@ -2544,20 +2546,23 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable } // Row Count int rows = getRowCount(); - if (rows == 0) + if (rows == 0 && !m_mTable.isLoading()) { log.fine("No Rows"); return -1; } if (newRow >= rows) { - newRow = rows-1; - if (log.isLoggable(Level.FINE)) log.fine("Set to max Row: " + newRow); + if (!m_mTable.isLoading()) + { + newRow = rows-1; + if (log.isLoggable(Level.FINE)) log.fine("Set to max Row: " + newRow); + } } else if (newRow < 0) { newRow = 0; - log.fine("Set to first Row"); + if (log.isLoggable(Level.FINE)) log.fine("Set to first Row"); } m_mTable.waitLoadingForRow(newRow); @@ -3556,6 +3561,9 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable int tabMaxQueryRecords = m_vo.MaxQueryRecords; if (roleMaxQueryRecords > 0 && (roleMaxQueryRecords < tabMaxQueryRecords || tabMaxQueryRecords == 0)) tabMaxQueryRecords = roleMaxQueryRecords; + if (tabMaxQueryRecords == 0) + tabMaxQueryRecords = MSysConfig.getIntValue(MSysConfig.GLOBAL_MAX_QUERY_RECORDS, + DEFAULT_GLOBAL_MAX_QUERY_RECORDS, Env.getAD_Client_ID(Env.getCtx())); return tabMaxQueryRecords; } diff --git a/org.adempiere.base/src/org/compiere/model/GridTable.java b/org.adempiere.base/src/org/compiere/model/GridTable.java index 7893bc8355..a460c96e18 100644 --- a/org.adempiere.base/src/org/compiere/model/GridTable.java +++ b/org.adempiere.base/src/org/compiere/model/GridTable.java @@ -46,6 +46,7 @@ import java.util.logging.Level; import javax.swing.event.TableModelListener; import javax.swing.table.AbstractTableModel; +import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.DBException; import org.adempiere.util.ServerContext; import org.compiere.Adempiere; @@ -95,14 +96,15 @@ public class GridTable extends AbstractTableModel implements Serializable { /** - * generated serial id + * */ - private static final long serialVersionUID = -5564364545827057092L; + private static final long serialVersionUID = -2602189278069194311L; protected static final String SORTED_DSE_EVENT = "Sorted"; public static final int DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS = 30; - + public static final int DEFAULT_GRIDTABLE_COUNT_TIMEOUT_IN_SECONDS = 1; + public static final String LOAD_TIMEOUT_ERROR_MESSAGE = "GridTabLoadTimeoutError"; public static final String DATA_REFRESH_MESSAGE = "Refreshed"; @@ -169,6 +171,7 @@ public class GridTable extends AbstractTableModel /** Rowcount */ private int m_rowCount = 0; + private boolean m_rowCountTimeout = false; /** Has Data changed? */ private boolean m_changed = false; /** Index of changed row via SetValueAt */ @@ -426,7 +429,8 @@ public class GridTable extends AbstractTableModel //IDEMPIERE-5193 Add Limit to Query if(m_maxRows > 0 && DB.getDatabase().isPagingSupported()) { - m_SQL = DB.getDatabase().addPagingSQL(m_SQL, 1, m_maxRows); + // set to maxRows plus one to trigger FindOverMax on overflow + m_SQL = DB.getDatabase().addPagingSQL(m_SQL, 1, m_maxRows+1); } // @@ -645,7 +649,8 @@ public class GridTable extends AbstractTableModel m_buffer = new ArrayList(m_rowCount+10); } m_sort = new ArrayList(m_rowCount+10); - if (m_rowCount > 0) + // actual row count or timeout + if (m_rowCount > 0 || m_rowCountTimeout) { m_loader.setContext(ServerContext.getCurrentInstance()); m_loaderFuture = Adempiere.getThreadPoolExecutor().submit(m_loader); @@ -1159,7 +1164,7 @@ public class GridTable extends AbstractTableModel log.warning("Reached " + timeout + " seconds timeout loading row " + (row+1) + " for SQL=" + m_SQL); //adjust row count m_rowCount = m_sort.size(); - throw new DBException("GridTabLoadTimeoutError"); + throw new AdempiereException(Msg.getMsg(Env.getCtx(), LOAD_TIMEOUT_ERROR_MESSAGE)); } } @@ -1266,6 +1271,9 @@ public class GridTable extends AbstractTableModel try { stmt = DB.prepareStatement(sql.toString(), null); + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + if (timeout > 0) + stmt.setQueryTimeout(timeout); rs = stmt.executeQuery(); while(rs.next()) { @@ -3014,12 +3022,14 @@ public class GridTable extends AbstractTableModel // Get Number of Rows rows = 0; PreparedStatement pstmt = null; - ResultSet rs = null; + ResultSet rs = null; + m_rowCountTimeout = false; try { pstmt = DB.prepareStatement(m_SQL_Count, get_TrxName()); setParameter (pstmt, true); - int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS, + DEFAULT_GRIDTABLE_COUNT_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); if (timeout > 0) pstmt.setQueryTimeout(timeout); rs = pstmt.executeQuery(); @@ -3028,7 +3038,13 @@ public class GridTable extends AbstractTableModel } catch (SQLException e0) { - throw new DBException(e0); + if (DB.getDatabase().isQueryTimeout(e0)) + { + m_rowCountTimeout = true; + return 0; + } + else + throw new DBException(e0); } finally { @@ -3064,11 +3080,7 @@ public class GridTable extends AbstractTableModel try { m_pstmt = DB.prepareStatement(m_SQL, trxName); - if (this.maxRows > 0 && rows == this.maxRows) - { - m_pstmt.setMaxRows(this.maxRows); - } - //ensure not all row is fectch into memory for virtual table + //ensure not all rows are fetch into memory for virtual table if (m_virtual) m_pstmt.setFetchSize(100); setParameter (m_pstmt, false); @@ -3079,8 +3091,12 @@ public class GridTable extends AbstractTableModel } catch (SQLException e) { - log.saveError(e.getLocalizedMessage(), e); - throw new DBException(e); + if (DB.getDatabase().isQueryTimeout(e)) { + throw new AdempiereException(Msg.getMsg(Env.getCtx(), LOAD_TIMEOUT_ERROR_MESSAGE), e); + } else { + log.saveError(e.getLocalizedMessage(), e); + throw new DBException(e); + } } } @@ -3118,6 +3134,7 @@ public class GridTable extends AbstractTableModel * Fill buffer from result set */ private void doRun() { + boolean isFindOverMax = false; try { openResultSet(); @@ -3126,6 +3143,11 @@ public class GridTable extends AbstractTableModel while (m_rs.next()) { + if (maxRows > 0 && m_sort.size() == maxRows) { + isFindOverMax = true; + break; + } + if (Thread.interrupted()) { if (log.isLoggable(Level.FINE)) log.fine("Interrupted"); @@ -3149,9 +3171,25 @@ public class GridTable extends AbstractTableModel } m_sort.add(sort); + // Start with rowCount=0, inform loading of first row + if (m_rowCountTimeout) + { + m_rowCount++; + if (m_rowCount == 1) + { + DataStatusEvent evt = createDSE(); + evt.setLoading(m_sort.size()); + evt.setInfo("CountQueryTimeoutLoadBackground", null, false, true); + fireDataStatusChanged(evt); + } + } + // Statement all 1000 rows & sleep if (m_sort.size() % 1000 == 0) { + DataStatusEvent evt = createDSE(); + evt.setLoading(m_sort.size()); + fireDataStatusChanged(evt); // give the other processes a chance try { @@ -3164,9 +3202,6 @@ public class GridTable extends AbstractTableModel close(); return; } - DataStatusEvent evt = createDSE(); - evt.setLoading(m_sort.size()); - fireDataStatusChanged(evt); } } // while(rs.next()) } @@ -3178,6 +3213,17 @@ public class GridTable extends AbstractTableModel { close(); } + + // Background loading without initial rowCount, inform final loaded rows + if (m_rowCountTimeout && m_sort.size() > 0) + { + DataStatusEvent evt = createDSE(); + evt.setLoading(m_sort.size()); + if (isFindOverMax) + evt.setInfo("FindOverMax", " > " + m_sort.size(), false, true); + fireDataStatusChanged(evt); + } + fireDataStatusIEvent("", ""); } diff --git a/org.adempiere.base/src/org/compiere/model/MLookup.java b/org.adempiere.base/src/org/compiere/model/MLookup.java index 65731d521b..756cc922fe 100644 --- a/org.adempiere.base/src/org/compiere/model/MLookup.java +++ b/org.adempiere.base/src/org/compiere/model/MLookup.java @@ -58,9 +58,9 @@ import org.compiere.util.ValueNamePair; public final class MLookup extends Lookup implements Serializable { /** - * generated serial id + * */ - private static final long serialVersionUID = 2288661955135689187L; + private static final long serialVersionUID = 3339750658316918418L; /** * MLookup Constructor @@ -1083,9 +1083,9 @@ public final class MLookup extends Lookup implements Serializable protected class MLoader extends ContextRunnable implements Serializable { /** - * generated serial id + * */ - private static final long serialVersionUID = -7868426685745727939L; + private static final long serialVersionUID = -5752931726580011885L; /** * MLoader Constructor @@ -1228,7 +1228,11 @@ public final class MLookup extends Lookup implements Serializable try { // SELECT Key, Value, Name, IsActive FROM ... - pstmt = DB.prepareStatement(sql.toString(), null); + String sqlFirstRows = DB.getDatabase().addPagingSQL(sql.toString(), 1, MAX_ROWS+1); + pstmt = DB.prepareStatement(sqlFirstRows, null); + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, GridTable.DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + if (timeout > 0) + pstmt.setQueryTimeout(timeout); rs = pstmt.executeQuery(); // Get first ... rows @@ -1237,19 +1241,10 @@ public final class MLookup extends Lookup implements Serializable { if (rows++ > MAX_ROWS) { - StringBuilder s = new StringBuilder().append(m_info.KeyColumn).append(": Loader - Too many records"); - if (m_info.Column_ID > 0) - { - MColumn mColumn = MColumn.get(m_info.ctx, m_info.Column_ID); - String column = mColumn.getColumnName(); - s.append(", Column=").append(column); - String tableName = MTable.getTableName(m_info.ctx, mColumn.getAD_Table_ID()); - s.append(", Table=").append(tableName); - } - log.warning(s.toString()); + logLookup(Level.WARNING, "Too many records"); break; } - // check for interrupted every 10 rows + // check for interrupted every 20 rows if (rows % 20 == 0 && Thread.interrupted()) break; @@ -1294,8 +1289,11 @@ public final class MLookup extends Lookup implements Serializable } catch (SQLException e) { - log.log(Level.SEVERE, m_info.KeyColumn + ", " + m_info.Column_ID + " : Loader - " + sql, e); m_allLoaded = false; + if (DB.getDatabase().isQueryTimeout(e)) + logLookup(Level.WARNING, "Too slow query"); + else + logLookup(Level.SEVERE, e.getLocalizedMessage()); } finally { DB.close(rs, pstmt); @@ -1307,6 +1305,25 @@ public final class MLookup extends Lookup implements Serializable + " - ms=" + String.valueOf(System.currentTimeMillis()-m_startTime) + " (" + String.valueOf(System.currentTimeMillis()-startTime) + ")"); } // run + + /** + * Log a warning for the lookup problem found + * @param problem + */ + private void logLookup(Level level, String problem) { + if (log.isLoggable(level)) { + StringBuilder msg = new StringBuilder().append(m_info.KeyColumn).append(": Loader - ").append(problem); + if (m_info.Column_ID > 0) { + MColumn mColumn = MColumn.get(m_info.ctx, m_info.Column_ID); + String column = mColumn.getColumnName(); + msg.append(", Column=").append(column); + String tableName = MTable.getTableName(m_info.ctx, mColumn.getAD_Table_ID()); + msg.append(", Table=").append(tableName); + } + log.log(level, msg.toString()); + } + } + } // Loader } // MLookup diff --git a/org.adempiere.base/src/org/compiere/model/MSysConfig.java b/org.adempiere.base/src/org/compiere/model/MSysConfig.java index 4b3277f1e2..eabb6da52d 100644 --- a/org.adempiere.base/src/org/compiere/model/MSysConfig.java +++ b/org.adempiere.base/src/org/compiere/model/MSysConfig.java @@ -46,7 +46,7 @@ public class MSysConfig extends X_AD_SysConfig /** * */ - private static final long serialVersionUID = -4149262106340017798L; + private static final long serialVersionUID = 8121897973805635995L; /** Constant for Predefine System Configuration Names (in alphabetical order) */ @@ -119,7 +119,9 @@ public class MSysConfig extends X_AD_SysConfig public static final String FORM_SQL_QUERY_LOG_ISSUE = "FORM_SQL_QUERY_LOG_ISSUE"; public static final String FORM_SQL_QUERY_MAX_RECORDS = "FORM_SQL_QUERY_MAX_RECORDS"; public static final String FORM_SQL_QUERY_TIMEOUT_IN_SECONDS = "FORM_SQL_QUERY_TIMEOUT_IN_SECONDS"; + public static final String GLOBAL_MAX_QUERY_RECORDS = "GLOBAL_MAX_QUERY_RECORDS"; public static final String GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS = "GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS"; + public static final String GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS = "GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS"; public static final String HTML_REPORT_MINIFY = "HTML_REPORT_MINIFY"; public static final String HTML_REPORT_THEME = "HTML_REPORT_THEME"; public static final String IBAN_VALIDATION = "IBAN_VALIDATION"; diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java index 55111a4247..c27de5daac 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java @@ -29,7 +29,6 @@ import java.util.logging.Level; import org.adempiere.base.Core; import org.adempiere.exceptions.AdempiereException; -import org.adempiere.exceptions.DBException; import org.adempiere.util.Callback; import org.adempiere.webui.AdempiereIdGenerator; import org.adempiere.webui.AdempiereWebUI; @@ -1380,23 +1379,9 @@ DataStatusListener, IADTabpanel, IdSpace, IFieldEditorContainer public void query (boolean onlyCurrentRows, int onlyCurrentDays, int maxRows) { boolean open = gridTab.isOpen(); - try - { - gridTab.query(onlyCurrentRows, onlyCurrentDays, maxRows); - if (listPanel.isVisible() && !open) - gridTab.getTableModel().fireTableDataChanged(); - } - catch (Exception e) - { - if (DBException.isTimeout(e)) - { - throw e; - } - else - { - Dialog.error(windowNo, e.getMessage()); - } - } + gridTab.query(onlyCurrentRows, onlyCurrentDays, maxRows); + if (listPanel.isVisible() && !open) + gridTab.getTableModel().fireTableDataChanged(); } /** diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/AbstractADWindowContent.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/AbstractADWindowContent.java index 13dd8c1929..62dafd98de 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/AbstractADWindowContent.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/AbstractADWindowContent.java @@ -21,6 +21,8 @@ import static org.compiere.model.MSysConfig.ZK_GRID_AFTER_FIND; import static org.compiere.model.SystemIDs.PROCESS_AD_CHANGELOG_REDO; import static org.compiere.model.SystemIDs.PROCESS_AD_CHANGELOG_UNDO; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.text.MessageFormat; import java.util.ArrayList; @@ -853,15 +855,9 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements } } // - StringBuffer sql = new StringBuffer("SELECT COUNT(*) FROM ") - .append(mTab.getTableName()); - if (where.length() > 0) - sql.append(" WHERE ").append(where); - String finalSQL = MRole.getDefault().addAccessSQL(sql.toString(), - mTab.getTableName(), MRole.SQL_NOTQUALIFIED, MRole.SQL_RO); - int no = DB.getSQLValue(null, finalSQL.toString()); - // - require = mTab.isQueryRequire(no); + int no = getRecordCount(mTab, where); + // show find dialog if count timeout/exception + require = no == -1 ? true : mTab.isQueryRequire(no); } // Show find window (if required) @@ -916,6 +912,35 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements } } // initialQuery + /** + * Get record count + * @param mTab + * @param where + * @return record count + */ + private int getRecordCount(GridTab mTab, StringBuffer where) { + StringBuffer sql = new StringBuffer("SELECT COUNT(*) FROM ") + .append(mTab.getTableName()); + if (where.length() > 0) + sql.append(" WHERE ").append(where); + String finalSQL = MRole.getDefault().addAccessSQL(sql.toString(), + mTab.getTableName(), MRole.SQL_NOTQUALIFIED, MRole.SQL_RO); + int no = -1; + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS, + GridTable.DEFAULT_GRIDTABLE_COUNT_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + try (PreparedStatement stmt = DB.prepareStatement(finalSQL, null)) { + if (timeout > 0) + stmt.setQueryTimeout(timeout); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) + no = rs.getInt(1); + } catch (SQLException e) { + logger.log(Level.WARNING, e.getMessage(), e); + no = -1; + } + return no; + } + /** * Setup find window UI properties * @param findWindow @@ -1742,7 +1767,16 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements { //ignore non-ui thread event. if (Executions.getCurrent() == null) + { + // Re-post incremental loading event to UI thread + if (e.isLoading() && e.getSource() != null && e.getSource().equals(adTabbox.getSelectedGridTab().getTableModel())) + { + Executions.schedule(getComponent().getDesktop(), evt -> { + this.dataStatusChanged(e); + }, new Event("onAsynchronousDataStatusChanged")); + } return; + } boolean detailTab = false; if (e.getSource() instanceof GridTable) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java index 0fa9af9f01..74de5c0da7 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java @@ -3020,14 +3020,10 @@ public class FindWindow extends Window implements EventListener, ValueCha // Test for no records if (getNoOfRecords(m_query, true) != 0) { - if (m_total == COUNTING_RECORDS_TIMED_OUT) { - Dialog.error(m_targetWindowNo, "InfoQueryTimeOutError"); - } else { - if (advancedPanel != null) { - advancedPanel.getItems().clear(); - } - dispose(); + if (advancedPanel != null) { + advancedPanel.getItems().clear(); } + dispose(); } } // cmd_ok_Simple @@ -3098,11 +3094,7 @@ public class FindWindow extends Window implements EventListener, ValueCha } if (getNoOfRecords(m_query, true) != 0) { - if (m_total == COUNTING_RECORDS_TIMED_OUT) { - Dialog.error(m_targetWindowNo, "InfoQueryTimeOutError"); - } else { - dispose(); - } + dispose(); } } // cmd_ok_Advanced @@ -3150,8 +3142,8 @@ public class FindWindow extends Window implements EventListener, ValueCha Env.setContext(Env.getCtx(), m_targetWindowNo, TABNO, GridTab.CTX_FindSQL, finalSQL); // Execute Query - int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, - GridTable.DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS, + GridTable.DEFAULT_GRIDTABLE_COUNT_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); m_total = 999999; Statement stmt = null; ResultSet rs = null; @@ -3187,12 +3179,12 @@ public class FindWindow extends Window implements EventListener, ValueCha // No Records if (m_total == 0 && alertRecords) Dialog.warn(m_targetWindowNo, "FindZeroRecords", null); - // More then allowed + // Load not more than max allow if (m_gridTab != null && alertRecords && m_total != COUNTING_RECORDS_TIMED_OUT && m_gridTab.isQueryMax(m_total)) { - Dialog.error(m_targetWindowNo, "FindOverMax", - m_total + " > " + m_gridTab.getMaxQueryRecords()); - m_total = 0; // return 0 if more then allowed - teo_sarca [ 1708717 ] + Dialog.info(m_targetWindowNo, "FindOverMax", + m_total + " > " + m_gridTab.getMaxQueryRecords()); + m_total = m_gridTab.getMaxQueryRecords(); } else if (log.isLoggable(Level.CONFIG)) log.config("#" + m_total); From 39fff47de699d39305308e9269ab3d21568ce637 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Mon, 6 May 2024 15:25:17 +0200 Subject: [PATCH 020/169] IDEMPIERE-6137 Payment Rule does not appear in reports from Sales Order (#2347) --- org.adempiere.base/src/org/compiere/print/DataEngine.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/org.adempiere.base/src/org/compiere/print/DataEngine.java b/org.adempiere.base/src/org/compiere/print/DataEngine.java index 416ea7c7d4..040319ab15 100644 --- a/org.adempiere.base/src/org/compiere/print/DataEngine.java +++ b/org.adempiere.base/src/org/compiere/print/DataEngine.java @@ -16,6 +16,8 @@ *****************************************************************************/ package org.compiere.print; +import static org.compiere.model.SystemIDs.REFERENCE_PAYMENTRULE; + import java.io.Serializable; import java.math.BigDecimal; import java.sql.Clob; @@ -513,6 +515,8 @@ public class DataEngine else if (DisplayType.isList(AD_Reference_ID) || (AD_Reference_ID == DisplayType.Button && AD_Reference_Value_ID != 0)) { + if (AD_Reference_ID == DisplayType.Payment) + AD_Reference_Value_ID = REFERENCE_PAYMENTRULE; if (ColumnSQL.length() > 0) { lookupSQL = ColumnSQL; From 47f1336df4f0cad595901ce9ca1373a01149cb55 Mon Sep 17 00:00:00 2001 From: hengsin Date: Mon, 6 May 2024 21:58:34 +0800 Subject: [PATCH 021/169] IDEMPIERE-6136 ModelEventDelegate doesn't works with DocAction, PostCreate, PostUpdate and PostDelete annotation (#2346) --- .../src/org/adempiere/base/event/EventManager.java | 6 +++++- .../src/org/compiere/process/DocActionEventData.java | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/org.adempiere.base/src/org/adempiere/base/event/EventManager.java b/org.adempiere.base/src/org/adempiere/base/event/EventManager.java index e6d9838162..61a6a28a59 100644 --- a/org.adempiere.base/src/org/adempiere/base/event/EventManager.java +++ b/org.adempiere.base/src/org/adempiere/base/event/EventManager.java @@ -31,6 +31,7 @@ import java.util.stream.Collectors; import org.adempiere.base.BaseActivator; import org.adempiere.base.event.annotations.BaseEventHandler; +import org.compiere.model.PO; import org.compiere.util.CLogger; import org.compiere.util.Env; import org.compiere.util.Ini; @@ -332,8 +333,11 @@ public class EventManager implements IEventManager { } else { Map map = new HashMap(3); map.put(EventConstants.EVENT_TOPIC, topic); - if (data != null) + if (data != null) { map.put(EVENT_DATA, data); + if (data instanceof PO po) + map.put(TABLE_NAME_PROPERTY, po.get_TableName()); + } map.put(EVENT_ERROR_MESSAGES, new ArrayList()); if (copySessionContext) map.put(EVENT_CONTEXT, getCurrentSessionContext()); diff --git a/org.adempiere.base/src/org/compiere/process/DocActionEventData.java b/org.adempiere.base/src/org/compiere/process/DocActionEventData.java index 04ec90d2a1..4724a1531b 100644 --- a/org.adempiere.base/src/org/compiere/process/DocActionEventData.java +++ b/org.adempiere.base/src/org/compiere/process/DocActionEventData.java @@ -28,12 +28,13 @@ import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; import org.adempiere.base.event.IEventTopics; +import org.adempiere.base.event.POEventData; import org.compiere.model.PO; /** * Event data for {@link IEventTopics#DOCACTION}. */ -public class DocActionEventData { +public class DocActionEventData implements POEventData { public String docStatus; public Object processing; @@ -70,4 +71,9 @@ public class DocActionEventData { this.po = po; } + @Override + public PO getPo() { + return po; + } + } From 6cb578865194beb0abcd80bf943953a7e37182dd Mon Sep 17 00:00:00 2001 From: hengsin Date: Tue, 7 May 2024 11:05:26 +0800 Subject: [PATCH 022/169] IDEMPIERE-6119 Show dashboard content layout on column when open on mobile screen size (#2350) --- .../adempiere/webui/desktop/DashboardController.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/DashboardController.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/DashboardController.java index 3b6a460cc7..321afb8f70 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/DashboardController.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/DashboardController.java @@ -543,7 +543,7 @@ public class DashboardController implements EventListener { // Dashboard content Hlayout dashboardLineLayout = null; int currentLineNo = 0; - int noOfLines = 0; + int maxPerLine = 0; int width = 100; try { @@ -562,13 +562,12 @@ public class DashboardController implements EventListener { } } - noOfLines = MDashboardPreference.getForSessionRowCount(isShowInDashboard, AD_User_ID, AD_Role_ID); if (ClientInfo.isMobile() && isShowInDashboard) { if (ClientInfo.maxWidth(ClientInfo.MEDIUM_WIDTH-1)) { if (ClientInfo.maxWidth(ClientInfo.SMALL_WIDTH-1)) { - noOfLines = 1; - } else if (noOfLines > 2) { - noOfLines = 2; + maxPerLine = 1; + } else { + maxPerLine = 2; } } } @@ -586,7 +585,7 @@ public class DashboardController implements EventListener { int lineNo = dp.getLine().intValue(); int flexGrow = (flexGrow = dp.getFlexGrow()) > 0 ? flexGrow : DEFAULT_FLEX_GROW; - if(dashboardLineLayout == null || currentLineNo != lineNo) + if(dashboardLineLayout == null || currentLineNo != lineNo || (maxPerLine > 0 && dashboardLineLayout.getChildren().size() == maxPerLine)) { dashboardLineLayout = new Hlayout(); dashboardLineLayout.setAttribute(LINE_ATTRIBUTE, lineNo); From a86a73c391c73d8816ffb709582dfbec2576c1d3 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Tue, 7 May 2024 15:26:40 +0200 Subject: [PATCH 023/169] IDEMPIERE-6137 Payment Rule does not appear in reports from Sales Order (#2351) - safer approach --- migration/iD11/oracle/202405071219_IDEMPIERE-6137.sql | 10 ++++++++++ .../iD11/postgresql/202405071219_IDEMPIERE-6137.sql | 7 +++++++ org.adempiere.base/src/org/compiere/model/MColumn.java | 3 +++ .../src/org/compiere/print/DataEngine.java | 4 ---- .../src/org/compiere/util/DisplayType.java | 2 +- 5 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 migration/iD11/oracle/202405071219_IDEMPIERE-6137.sql create mode 100644 migration/iD11/postgresql/202405071219_IDEMPIERE-6137.sql diff --git a/migration/iD11/oracle/202405071219_IDEMPIERE-6137.sql b/migration/iD11/oracle/202405071219_IDEMPIERE-6137.sql new file mode 100644 index 0000000000..8344ff4658 --- /dev/null +++ b/migration/iD11/oracle/202405071219_IDEMPIERE-6137.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-6137 Payment Rule does not appear in reports from Sales Order +SELECT register_migration_script('202405071219_IDEMPIERE-6137.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- May 7, 2024, 12:19:00 PM CEST +UPDATE AD_Column SET AD_Reference_Value_ID=195,Updated=TO_TIMESTAMP('2024-05-07 12:19:00','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Reference_ID=200012 AND COALESCE(AD_Reference_Value_ID,0)!=195 +; + diff --git a/migration/iD11/postgresql/202405071219_IDEMPIERE-6137.sql b/migration/iD11/postgresql/202405071219_IDEMPIERE-6137.sql new file mode 100644 index 0000000000..8ea63cd938 --- /dev/null +++ b/migration/iD11/postgresql/202405071219_IDEMPIERE-6137.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-6137 Payment Rule does not appear in reports from Sales Order +SELECT register_migration_script('202405071219_IDEMPIERE-6137.sql') FROM dual; + +-- May 7, 2024, 12:19:00 PM CEST +UPDATE AD_Column SET AD_Reference_Value_ID=195,Updated=TO_TIMESTAMP('2024-05-07 12:19:00','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Reference_ID=200012 AND COALESCE(AD_Reference_Value_ID,0)!=195 +; + diff --git a/org.adempiere.base/src/org/compiere/model/MColumn.java b/org.adempiere.base/src/org/compiere/model/MColumn.java index 0c3234581c..98241913cb 100644 --- a/org.adempiere.base/src/org/compiere/model/MColumn.java +++ b/org.adempiere.base/src/org/compiere/model/MColumn.java @@ -609,6 +609,9 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport } } + if (getAD_Reference_ID() == DisplayType.Payment) + setAD_Reference_Value_ID(SystemIDs.REFERENCE_PAYMENTRULE); + return true; } // beforeSave diff --git a/org.adempiere.base/src/org/compiere/print/DataEngine.java b/org.adempiere.base/src/org/compiere/print/DataEngine.java index 040319ab15..416ea7c7d4 100644 --- a/org.adempiere.base/src/org/compiere/print/DataEngine.java +++ b/org.adempiere.base/src/org/compiere/print/DataEngine.java @@ -16,8 +16,6 @@ *****************************************************************************/ package org.compiere.print; -import static org.compiere.model.SystemIDs.REFERENCE_PAYMENTRULE; - import java.io.Serializable; import java.math.BigDecimal; import java.sql.Clob; @@ -515,8 +513,6 @@ public class DataEngine else if (DisplayType.isList(AD_Reference_ID) || (AD_Reference_ID == DisplayType.Button && AD_Reference_Value_ID != 0)) { - if (AD_Reference_ID == DisplayType.Payment) - AD_Reference_Value_ID = REFERENCE_PAYMENTRULE; if (ColumnSQL.length() > 0) { lookupSQL = ColumnSQL; diff --git a/org.adempiere.base/src/org/compiere/util/DisplayType.java b/org.adempiere.base/src/org/compiere/util/DisplayType.java index db4121f6c3..2a02579987 100644 --- a/org.adempiere.base/src/org/compiere/util/DisplayType.java +++ b/org.adempiere.base/src/org/compiere/util/DisplayType.java @@ -542,7 +542,7 @@ public final class DisplayType */ public static boolean isLookup(int displayType) { - if (displayType == List + if (displayType == List || displayType == Payment || displayType == Table || displayType == TableUU || displayType == TableDir || displayType == TableDirUU || displayType == Search || displayType == SearchUU From e1ed11f224301aca728812a2e772cf56bbc7f4df Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Wed, 8 May 2024 05:01:39 +0200 Subject: [PATCH 024/169] IDEMPIERE-6140 Chosen Multiple Selection Table column bug in Info window (#2353) --- .../src/org/adempiere/webui/panel/InfoGeneralPanel.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoGeneralPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoGeneralPanel.java index 486266db4c..8440b08b37 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoGeneralPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoGeneralPanel.java @@ -582,7 +582,7 @@ public class InfoGeneralPanel extends InfoPanel implements EventListener else if (DisplayType.isDate(displayType)) colClass = Timestamp.class; // ignore Binary, Button, ID, RowID - else if (displayType == DisplayType.List) + else if (displayType == DisplayType.List || displayType == DisplayType.RadiogroupList || displayType == DisplayType.Payment) { if (Env.isBaseLanguage(Env.getCtx(), "AD_Ref_List")) colSql = new StringBuffer("(SELECT l.Name FROM AD_Ref_List l WHERE l.AD_Reference_ID=") @@ -602,7 +602,8 @@ public class InfoGeneralPanel extends InfoPanel implements EventListener list.add(new ColumnInfo(Msg.translate(Env.getCtx(), columnName), colSql.toString(), colClass, true, columnName )); if (log.isLoggable(Level.FINEST)) log.finest("Added Column=" + columnName); } - else if (isDisplayed && DisplayType.isLookup(displayType)) + else if (isDisplayed && DisplayType.isLookup(displayType) + && !(displayType == DisplayType.ChosenMultipleSelectionTable || displayType == DisplayType.ChosenMultipleSelectionSearch || displayType == DisplayType.ChosenMultipleSelectionList)) { ColumnInfo colInfo = createLookupColumnInfo(Msg.translate(Env.getCtx(), columnName), columnName, displayType, AD_Reference_Value_ID, AD_Column_ID, colSql.toString()); if (colInfo != null) From 4409415d5501fe954c61a053b726a6940c6962d2 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Wed, 8 May 2024 14:16:12 +0200 Subject: [PATCH 025/169] IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) (#2354) * IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) - make also configurable the timeout and number of records from windows - improve timing of reports avoiding unncessary load of array when not jasper * - implement suggestions from Heng Sin --- .../oracle/202405080101_IDEMPIERE-6123.sql | 22 +++++++++++++++ .../202405080101_IDEMPIERE-6123.sql | 19 +++++++++++++ .../org/compiere/db/AdempiereDatabase.java | 2 +- .../src/org/compiere/model/MAcctSchema.java | 2 +- .../src/org/compiere/model/MLookup.java | 2 ++ .../compiere/model/MProductCategoryAcct.java | 2 +- .../src/org/compiere/model/MSysConfig.java | 4 ++- .../src/org/compiere/print/DataEngine.java | 26 +++++++++++++++-- .../webui/panel/action/ReportAction.java | 28 ++++++++++--------- 9 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 migration/iD11/oracle/202405080101_IDEMPIERE-6123.sql create mode 100644 migration/iD11/postgresql/202405080101_IDEMPIERE-6123.sql diff --git a/migration/iD11/oracle/202405080101_IDEMPIERE-6123.sql b/migration/iD11/oracle/202405080101_IDEMPIERE-6123.sql new file mode 100644 index 0000000000..ffd792aa44 --- /dev/null +++ b/migration/iD11/oracle/202405080101_IDEMPIERE-6123.sql @@ -0,0 +1,22 @@ +-- IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) +SELECT register_migration_script('202405080101_IDEMPIERE-6123.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- May 8, 2024, 1:01:16 AM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200247,0,0,TO_TIMESTAMP('2024-05-08 01:01:16','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 01:01:16','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','REPORT_LOAD_TIMEOUT_IN_SECONDS','120','Timeout in seconds when loading a report','D','C','14e838b1-c25c-400e-b39c-61da9bf92099') +; + +-- May 8, 2024, 1:01:42 AM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200248,0,0,TO_TIMESTAMP('2024-05-08 01:01:41','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 01:01:41','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GLOBAL_MAX_REPORT_RECORDS','100000','Max number of records allowed in a report','D','C','7030640a-1aa7-4ac7-a894-b4fe0dfde530') +; + +-- May 8, 2024, 1:06:19 AM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The data query for the report took too much time to execute (over {0} seconds) exceeding the allowed limit',0,0,'Y',TO_TIMESTAMP('2024-05-08 01:06:18','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-05-08 01:06:18','YYYY-MM-DD HH24:MI:SS'),100,200893,'ReportQueryTimeout','D','5f17f55f-adbe-4d97-bf83-9447983b4946') +; + +-- May 8, 2024, 1:07:10 AM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The report data exceeds the maximum limit of {0} rows',0,0,'Y',TO_TIMESTAMP('2024-05-08 01:07:09','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-05-08 01:07:09','YYYY-MM-DD HH24:MI:SS'),100,200894,'ReportMaxRowsReached','D','a4b55c31-0df0-4302-a62a-91cb7e79be0d') +; + diff --git a/migration/iD11/postgresql/202405080101_IDEMPIERE-6123.sql b/migration/iD11/postgresql/202405080101_IDEMPIERE-6123.sql new file mode 100644 index 0000000000..be168edcfe --- /dev/null +++ b/migration/iD11/postgresql/202405080101_IDEMPIERE-6123.sql @@ -0,0 +1,19 @@ +-- IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) +SELECT register_migration_script('202405080101_IDEMPIERE-6123.sql') FROM dual; + +-- May 8, 2024, 1:01:16 AM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200247,0,0,TO_TIMESTAMP('2024-05-08 01:01:16','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 01:01:16','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','REPORT_LOAD_TIMEOUT_IN_SECONDS','120','Timeout in seconds when loading a report','D','C','14e838b1-c25c-400e-b39c-61da9bf92099') +; + +-- May 8, 2024, 1:01:42 AM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200248,0,0,TO_TIMESTAMP('2024-05-08 01:01:41','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 01:01:41','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GLOBAL_MAX_REPORT_RECORDS','100000','Max number of records allowed in a report','D','C','7030640a-1aa7-4ac7-a894-b4fe0dfde530') +; + +-- May 8, 2024, 1:06:19 AM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The data query for the report took too much time to execute (over {0} seconds) exceeding the allowed limit',0,0,'Y',TO_TIMESTAMP('2024-05-08 01:06:18','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-05-08 01:06:18','YYYY-MM-DD HH24:MI:SS'),100,200893,'ReportQueryTimeout','D','5f17f55f-adbe-4d97-bf83-9447983b4946') +; + +-- May 8, 2024, 1:07:10 AM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The report data exceeds the maximum limit of {0} rows',0,0,'Y',TO_TIMESTAMP('2024-05-08 01:07:09','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-05-08 01:07:09','YYYY-MM-DD HH24:MI:SS'),100,200894,'ReportMaxRowsReached','D','a4b55c31-0df0-4302-a62a-91cb7e79be0d') +; + diff --git a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java index 9b4e0afc18..84d1034f25 100644 --- a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java +++ b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java @@ -312,7 +312,7 @@ public interface AdempiereDatabase public boolean isPagingSupported(); /** - * modify sql to return a subset of the query result + * modify sql to return a subset of the query result. use 1 base index for start and end parameter * @param sql * @param start * @param end diff --git a/org.adempiere.base/src/org/compiere/model/MAcctSchema.java b/org.adempiere.base/src/org/compiere/model/MAcctSchema.java index 8976261c95..7ec3cf820f 100644 --- a/org.adempiere.base/src/org/compiere/model/MAcctSchema.java +++ b/org.adempiere.base/src/org/compiere/model/MAcctSchema.java @@ -744,7 +744,7 @@ public class MAcctSchema extends X_C_AcctSchema implements ImmutablePOSupport StringBuilder sql = new StringBuilder("SELECT DISTINCT p.Value FROM M_Product p JOIN M_CostDetail d ON p.M_Product_ID=d.M_Product_ID"); sql.append(" JOIN M_Product_Category_Acct pc ON p.M_Product_Category_ID=pc.M_Product_Category_ID AND d.C_AcctSchema_ID=pc.C_AcctSchema_ID"); sql.append(" WHERE p.IsActive='Y' AND pc.IsActive='Y' AND pc.CostingLevel IS NULL AND d.C_AcctSchema_ID=?"); - String query = DB.getDatabase().addPagingSQL(sql.toString(), 0, 50); + String query = DB.getDatabase().addPagingSQL(sql.toString(), 1, 50); List> list = DB.getSQLArrayObjectsEx(get_TrxName(), query, getC_AcctSchema_ID()); if (list != null) { for(List entry : list) { diff --git a/org.adempiere.base/src/org/compiere/model/MLookup.java b/org.adempiere.base/src/org/compiere/model/MLookup.java index 756cc922fe..4747f754f0 100644 --- a/org.adempiere.base/src/org/compiere/model/MLookup.java +++ b/org.adempiere.base/src/org/compiere/model/MLookup.java @@ -1230,6 +1230,8 @@ public final class MLookup extends Lookup implements Serializable // SELECT Key, Value, Name, IsActive FROM ... String sqlFirstRows = DB.getDatabase().addPagingSQL(sql.toString(), 1, MAX_ROWS+1); pstmt = DB.prepareStatement(sqlFirstRows, null); + if (! DB.getDatabase().isPagingSupported()) + pstmt.setMaxRows(MAX_ROWS+1); int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, GridTable.DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); if (timeout > 0) pstmt.setQueryTimeout(timeout); diff --git a/org.adempiere.base/src/org/compiere/model/MProductCategoryAcct.java b/org.adempiere.base/src/org/compiere/model/MProductCategoryAcct.java index 63497f8d39..653e9c41a9 100644 --- a/org.adempiere.base/src/org/compiere/model/MProductCategoryAcct.java +++ b/org.adempiere.base/src/org/compiere/model/MProductCategoryAcct.java @@ -236,7 +236,7 @@ public class MProductCategoryAcct extends X_M_Product_Category_Acct implements I StringBuilder products = new StringBuilder(); StringBuilder sql = new StringBuilder("SELECT DISTINCT p.Value FROM M_Product p JOIN M_CostDetail d ON p.M_Product_ID=d.M_Product_ID"); sql.append(" WHERE p.IsActive='Y' AND p.M_Product_Category_ID=? AND d.C_AcctSchema_ID=?"); - String query = DB.getDatabase().addPagingSQL(sql.toString(), 0, 50); + String query = DB.getDatabase().addPagingSQL(sql.toString(), 1, 50); List> list = DB.getSQLArrayObjectsEx(get_TrxName(), query, getM_Product_Category_ID(), getC_AcctSchema_ID()); if (list != null) { for(List entry : list) { diff --git a/org.adempiere.base/src/org/compiere/model/MSysConfig.java b/org.adempiere.base/src/org/compiere/model/MSysConfig.java index eabb6da52d..9dea5550dd 100644 --- a/org.adempiere.base/src/org/compiere/model/MSysConfig.java +++ b/org.adempiere.base/src/org/compiere/model/MSysConfig.java @@ -46,7 +46,7 @@ public class MSysConfig extends X_AD_SysConfig /** * */ - private static final long serialVersionUID = 8121897973805635995L; + private static final long serialVersionUID = 5739824752288579881L; /** Constant for Predefine System Configuration Names (in alphabetical order) */ @@ -120,6 +120,7 @@ public class MSysConfig extends X_AD_SysConfig public static final String FORM_SQL_QUERY_MAX_RECORDS = "FORM_SQL_QUERY_MAX_RECORDS"; public static final String FORM_SQL_QUERY_TIMEOUT_IN_SECONDS = "FORM_SQL_QUERY_TIMEOUT_IN_SECONDS"; public static final String GLOBAL_MAX_QUERY_RECORDS = "GLOBAL_MAX_QUERY_RECORDS"; + public static final String GLOBAL_MAX_REPORT_RECORDS = "GLOBAL_MAX_REPORT_RECORDS"; public static final String GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS = "GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS"; public static final String GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS = "GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS"; public static final String HTML_REPORT_MINIFY = "HTML_REPORT_MINIFY"; @@ -185,6 +186,7 @@ public class MSysConfig extends X_AD_SysConfig public static final String REAL_TIME_POS = "REAL_TIME_POS"; public static final String RecentItems_MaxSaved = "RecentItems_MaxSaved"; public static final String RecentItems_MaxShown = "RecentItems_MaxShown"; + public static final String REPORT_LOAD_TIMEOUT_IN_SECONDS = "REPORT_LOAD_TIMEOUT_IN_SECONDS"; public static final String REPORT_SWAP_MAX_ROWS = "REPORT_SWAP_MAX_ROWS"; public static final String SHIPPING_DEFAULT_WEIGHT_PER_PACKAGE = "SHIPPING_DEFAULT_WEIGHT_PER_PACKAGE"; public static final String STANDARD_REPORT_FOOTER_TRADEMARK_TEXT = "STANDARD_REPORT_FOOTER_TRADEMARK_TEXT"; diff --git a/org.adempiere.base/src/org/compiere/print/DataEngine.java b/org.adempiere.base/src/org/compiere/print/DataEngine.java index 416ea7c7d4..50a2569590 100644 --- a/org.adempiere.base/src/org/compiere/print/DataEngine.java +++ b/org.adempiere.base/src/org/compiere/print/DataEngine.java @@ -41,6 +41,7 @@ import org.compiere.model.MLookupFactory; import org.compiere.model.MQuery; import org.compiere.model.MReportView; import org.compiere.model.MRole; +import org.compiere.model.MSysConfig; import org.compiere.model.MTable; import org.compiere.model.SystemIDs; import org.compiere.util.CLogMgt; @@ -142,6 +143,10 @@ public class DataEngine private Map m_summarized = new HashMap(); + public static final int DEFAULT_REPORT_LOAD_TIMEOUT_IN_SECONDS = 120; + + public static final int DEFAULT_GLOBAL_MAX_REPORT_RECORDS = 100000; + /************************************************************************** * Load Data * @@ -927,11 +932,20 @@ public class DataEngine int reportLineID = 0; ArrayList scriptColumns = new ArrayList(); // + int timeout = MSysConfig.getIntValue(MSysConfig.REPORT_LOAD_TIMEOUT_IN_SECONDS, DEFAULT_REPORT_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); PreparedStatement pstmt = null; ResultSet rs = null; + String sql = pd.getSQL(); try { - pstmt = DB.prepareNormalReadReplicaStatement(pd.getSQL(), m_trxName); + int maxRows = MSysConfig.getIntValue(MSysConfig.GLOBAL_MAX_REPORT_RECORDS, DEFAULT_GLOBAL_MAX_REPORT_RECORDS, Env.getAD_Client_ID(Env.getCtx())); + if (maxRows > 0 && DB.getDatabase().isPagingSupported()) + sql = DB.getDatabase().addPagingSQL(sql, 1, maxRows+1); + pstmt = DB.prepareNormalReadReplicaStatement(sql, m_trxName); + if (maxRows > 0 && ! DB.getDatabase().isPagingSupported()) + pstmt.setMaxRows(maxRows+1); + if (timeout > 0) + pstmt.setQueryTimeout(timeout); rs = pstmt.executeQuery(); boolean isExistsT_Report_PA_ReportLine_ID = false; @@ -948,9 +962,13 @@ public class DataEngine } } + int cnt = 0; // Row Loop while (rs.next()) { + cnt++; + if (maxRows > 0 && cnt > maxRows) + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "ReportMaxRowsReached", new Object[] {maxRows})); if (hasLevelNo) { levelNo = rs.getInt("LevelNo"); @@ -1184,7 +1202,9 @@ public class DataEngine } catch (SQLException e) { - log.log(Level.SEVERE, pdc + " - " + e.getMessage() + "\nSQL=" + pd.getSQL()); + if (DB.getDatabase().isQueryTimeout(e)) + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "ReportQueryTimeout", new Object[] {timeout})); + log.log(Level.SEVERE, pdc + " - " + e.getMessage() + "\nSQL=" + sql); throw new AdempiereException(e); } finally @@ -1303,7 +1323,7 @@ public class DataEngine { if (CLogMgt.isLevelFiner()) log.finer("NO Rows - ms=" + (System.currentTimeMillis()-m_startTime) - + " - " + pd.getSQL()); + + " - " + sql); else log.info("NO Rows - ms=" + (System.currentTimeMillis()-m_startTime)); } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ReportAction.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ReportAction.java index 42c00a9a87..4479a2711e 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ReportAction.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ReportAction.java @@ -291,8 +291,8 @@ public class ReportAction implements EventListener boolean currentRowOnly = chkCurrentRowOnly.isChecked(); int Record_ID = 0; String Record_UU = null; - List RecordIDs = null; - List RecordUUs = null; + List jasperRecordIDs = null; + List jasperRecordUUs = null; MQuery query = new MQuery(gridTab.getTableName()); MTable table = MTable.get(gridTab.getAD_Table_ID()); StringBuilder whereClause = new StringBuilder(""); @@ -309,15 +309,17 @@ public class ReportAction implements EventListener else { whereClause.append(gridTab.getTableModel().getSelectWhereClause()); - if (table.isUUIDKeyTable()) { - RecordUUs = new ArrayList(); - for(int i = 0; i < gridTab.getRowCount(); i++) { - RecordUUs.add(gridTab.getKeyUUID(i)); - } - } else { - RecordIDs = new ArrayList(); - for(int i = 0; i < gridTab.getRowCount(); i++) { - RecordIDs.add(gridTab.getKeyID(i)); + if (pf != null && pf.getJasperProcess_ID() > 0) { + if (table.isUUIDKeyTable()) { + jasperRecordUUs = new ArrayList(); + for(int i = 0; i < gridTab.getRowCount(); i++) { + jasperRecordUUs.add(gridTab.getKeyUUID(i)); + } + } else { + jasperRecordIDs = new ArrayList(); + for(int i = 0; i < gridTab.getRowCount(); i++) { + jasperRecordIDs.add(gridTab.getKeyID(i)); + } } } } @@ -358,8 +360,8 @@ public class ReportAction implements EventListener { // It's a report using the JasperReports engine ProcessInfo pi = new ProcessInfo ("", pf.getJasperProcess_ID(), pf.getAD_Table_ID(), Record_ID, Record_UU); - pi.setRecord_IDs(RecordIDs); - pi.setRecord_UUs(RecordUUs); + pi.setRecord_IDs(jasperRecordIDs); + pi.setRecord_UUs(jasperRecordUUs); //pi.setIsBatch(true); if (export) From 4091c7409346d94d78380e33f090363cde88be2c Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Mon, 13 May 2024 18:27:36 +0200 Subject: [PATCH 026/169] IDEMPIERE-6110 New Error Message To Indicate When Sales Order has just Freight Product or Charge Freight - fix migration script (#2360) --- migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql | 2 +- migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql b/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql index dbeb7bfc22..a603f62ddc 100644 --- a/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql +++ b/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql @@ -5,6 +5,6 @@ SET SQLBLANKLINES ON SET DEFINE OFF -- Apr 17, 2024, 11:25:53 AM BRT -INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Products or charges configured as ''Freight Product'' or ''Freight Charge'' cannot be added to this order due to the combination of delivery via rule and freight cost rule',0,0,'Y',TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,nextidfunc(9,'N'),'FreightOrderLineNotAllowed','D','204c8bb9-d002-4beb-bbc3-1d1a11d7471d') +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Products or charges configured as ''Freight Product'' or ''Freight Charge'' cannot be added to this order due to the combination of delivery via rule and freight cost rule',0,0,'Y',TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,200897,'FreightOrderLineNotAllowed','D','204c8bb9-d002-4beb-bbc3-1d1a11d7471d') ; diff --git a/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql b/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql index 387e855eca..8680472e4f 100644 --- a/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql +++ b/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql @@ -2,6 +2,6 @@ SELECT register_migration_script('202404171125_IDEMPIERE-6110.sql') FROM dual; -- Apr 17, 2024, 11:25:53 AM BRT -INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Products or charges configured as ''Freight Product'' or ''Freight Charge'' cannot be added to this order due to the combination of delivery via rule and freight cost rule',0,0,'Y',TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,nextidfunc(9,'N'),'FreightOrderLineNotAllowed','D','204c8bb9-d002-4beb-bbc3-1d1a11d7471d') +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Products or charges configured as ''Freight Product'' or ''Freight Charge'' cannot be added to this order due to the combination of delivery via rule and freight cost rule',0,0,'Y',TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,200897,'FreightOrderLineNotAllowed','D','204c8bb9-d002-4beb-bbc3-1d1a11d7471d') ; From c47db344e8c4e4520d2560a9091d8dbe7b573ebd Mon Sep 17 00:00:00 2001 From: hengsin Date: Tue, 14 May 2024 19:04:30 +0800 Subject: [PATCH 027/169] IDEMPIERE-6142 Can't add new column to partitioned table (#2358) * IDEMPIERE-6142 Can't add new column to partitioned table * IDEMPIERE-6142 Can't add new column to partitioned table - Fix issue with foreign key * IDEMPIERE-6142 Can't add new column to partitioned table - Fix issue with foreign key --- org.adempiere.base/src/org/compiere/model/MColumn.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/MColumn.java b/org.adempiere.base/src/org/compiere/model/MColumn.java index 98241913cb..7757e91dd2 100644 --- a/org.adempiere.base/src/org/compiere/model/MColumn.java +++ b/org.adempiere.base/src/org/compiere/model/MColumn.java @@ -592,11 +592,12 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport setSeqNoPartition(ii); } - if (is_ValueChanged(COLUMNNAME_IsPartitionKey) + // Validate partition column changes + if (!newRecord && (is_ValueChanged(COLUMNNAME_IsPartitionKey) || is_ValueChanged(COLUMNNAME_PartitioningMethod) || (isPartitionKey() && is_ValueChanged(COLUMNNAME_IsActive)) || (isPartitionKey() && is_ValueChanged(COLUMNNAME_SeqNoPartition)) - || (isPartitionKey() && is_ValueChanged(COLUMNNAME_RangePartitionInterval))) { + || (isPartitionKey() && is_ValueChanged(COLUMNNAME_RangePartitionInterval)))) { ITablePartitionService service = DB.getDatabase().getTablePartitionService(); if (service == null) { log.saveError("Error", Msg.getMsg(getCtx(), "DBAdapterNoTablePartitionSupport")); @@ -1034,6 +1035,10 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport String referenceTableName = column.getReferenceTableName(); if (referenceTableName != null) { + // Fk doesn't work for partitioned PostgreSQL table + if (DB.isPostgreSQL() && MTable.get(Env.getCtx(), referenceTableName) != null && MTable.get(Env.getCtx(), referenceTableName).isPartition()) + return null; + Hashtable htForeignKeys = new Hashtable(); if (md.storesUpperCaseIdentifiers()) { From ecfb71bd7d8f60c4daeeac90b94f5653b5a087dc Mon Sep 17 00:00:00 2001 From: Nicolas Micoud <58596990+nmicoud@users.noreply.github.com> Date: Thu, 16 May 2024 20:55:22 +0200 Subject: [PATCH 028/169] IDEMPIERE-5796: Generate Model Template for several tables in a row (#2244) * IDEMPIERE-5796: for several tables in a row https://idempiere.atlassian.net/browse/IDEMPIERE-5796 * IDEMPIERE-5796: for several tables in a row - changes done in ModelInterfaceGenerator / ModelClassGenerator Implement suggesion from @hengsin * IDEMPIERE-5796: for several tables in a row - changes done in ModelInterfaceGenerator / ModelClassGenerator Implement suggesion from @hengsin * IDEMPIERE-5796: for several tables in a row - changes done in ModelInterfaceGenerator / ModelClassGenerator Implement suggestion from @CarlosRuiz-globalqss removing duplicated code * IDEMPIERE-5796: for several tables in a row - changes done in ModelInterfaceGenerator / ModelClassGenerator fix error --- .../adempiere/util/ModelClassGenerator.java | 118 +----------------- .../util/ModelInterfaceGenerator.java | 44 +++++-- 2 files changed, 36 insertions(+), 126 deletions(-) diff --git a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java index 87a71cb5f6..760aa3b4cc 100644 --- a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java +++ b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java @@ -31,7 +31,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Collection; -import java.util.StringTokenizer; import java.util.TreeSet; import java.util.logging.Level; @@ -914,121 +913,6 @@ public class ModelClassGenerator */ public static void generateSource(String sourceFolder, String packageName, String entityType, String tableName, String columnEntityType) { - if (sourceFolder == null || sourceFolder.trim().length() == 0) - throw new IllegalArgumentException("Must specify source folder"); - - File file = new File(sourceFolder); - if (!file.exists()) - throw new IllegalArgumentException("Source folder doesn't exists. sourceFolder="+sourceFolder); - - if (packageName == null || packageName.trim().length() == 0) - throw new IllegalArgumentException("Must specify package name"); - - if (tableName == null || tableName.trim().length() == 0) - throw new IllegalArgumentException("Must specify table name"); - - StringBuilder tableLike = new StringBuilder().append(tableName.trim()); - if (!tableLike.toString().startsWith("'") || !tableLike.toString().endsWith("'")) - tableLike = new StringBuilder("'").append(tableLike).append("'"); - - StringBuilder entityTypeFilter = new StringBuilder(); - if (entityType != null && entityType.trim().length() > 0) - { - entityTypeFilter.append("EntityType IN ("); - StringTokenizer tokenizer = new StringTokenizer(entityType, ","); - int i = 0; - while(tokenizer.hasMoreTokens()) { - StringBuilder token = new StringBuilder().append(tokenizer.nextToken().trim()); - if (!token.toString().startsWith("'") || !token.toString().endsWith("'")) - token = new StringBuilder("'").append(token).append("'"); - if (i > 0) - entityTypeFilter.append(","); - entityTypeFilter.append(token); - i++; - } - entityTypeFilter.append(")"); - } - else - { - entityTypeFilter.append("EntityType IN ('U','A')"); - } - - StringBuilder directory = new StringBuilder().append(sourceFolder.trim()); - String packagePath = packageName.replace(".", File.separator); - if (!(directory.toString().endsWith("/") || directory.toString().endsWith("\\"))) - { - directory.append(File.separator); - } - if (File.separator.equals("/")) - directory = new StringBuilder(directory.toString().replaceAll("[\\\\]", File.separator)); - else - directory = new StringBuilder(directory.toString().replaceAll("[/]", File.separator)); - directory.append(packagePath); - file = new File(directory.toString()); - if (!file.exists()) - file.mkdirs(); - - // complete sql - String filterViews = null; - if (tableLike.toString().contains("%")) { - filterViews = " AND (TableName IN ('RV_WarehousePrice','RV_BPartner') OR IsView='N')"; // special views - } - if (tableLike.toString().equals("'%'")) { - filterViews += " AND TableName NOT LIKE 'W|_%' ESCAPE '|'"; // exclude webstore from general model generator - } - StringBuilder sql = new StringBuilder(); - sql.append("SELECT AD_Table_ID ") - .append("FROM AD_Table ") - .append("WHERE IsActive = 'Y' AND TableName NOT LIKE '%_Trl' "); - // Autodetect if we need to use IN or LIKE clause - teo_sarca [ 3020640 ] - if (tableLike.indexOf(",") == -1) - sql.append(" AND TableName LIKE ").append(tableLike); - else - sql.append(" AND TableName IN (").append(tableLike).append(")"); // only specific tables - sql.append(" AND ").append(entityTypeFilter.toString()); - if (filterViews != null) { - sql.append(filterViews); - } - sql.append(" ORDER BY TableName"); - // - StringBuilder columnFilterBuilder = new StringBuilder(); - if (!Util.isEmpty(columnEntityType, true)) - { - columnFilterBuilder.append("EntityType IN ("); - StringTokenizer tokenizer = new StringTokenizer(columnEntityType, ","); - int i = 0; - while(tokenizer.hasMoreTokens()) { - StringBuilder token = new StringBuilder().append(tokenizer.nextToken().trim()); - if (!token.toString().startsWith("'") || !token.toString().endsWith("'")) - token = new StringBuilder("'").append(token).append("'"); - if (i > 0) - columnFilterBuilder.append(","); - columnFilterBuilder.append(token); - i++; - } - columnFilterBuilder.append(")"); - } - String columnFilter = columnFilterBuilder.length() > 0 ? columnFilterBuilder.toString() : null; - - PreparedStatement pstmt = null; - ResultSet rs = null; - try - { - pstmt = DB.prepareStatement(sql.toString(), null); - rs = pstmt.executeQuery(); - while (rs.next()) - { - new ModelClassGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter); - } - } - catch (SQLException e) - { - throw new DBException(e, sql.toString()); - } - finally - { - DB.close(rs, pstmt); - rs = null; pstmt = null; - } + ModelInterfaceGenerator.generateSource(ModelInterfaceGenerator.GEN_SOURCE_CLASS, sourceFolder, packageName, entityType, tableName, columnEntityType); } } diff --git a/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java b/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java index 832ffdc4d9..6b7a47da2c 100644 --- a/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java +++ b/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java @@ -106,6 +106,9 @@ public class ModelInterfaceGenerator /** Logger */ private static final CLogger log = CLogger.getCLogger(ModelInterfaceGenerator.class); + public final static String GEN_SOURCE_INTERFACE = "I"; + public final static String GEN_SOURCE_CLASS = "C"; + /** * @param AD_Table_ID * @param directory @@ -778,6 +781,18 @@ public class ModelInterfaceGenerator * @param columnEntityType */ public static void generateSource(String sourceFolder, String packageName, String entityType, String tableName, String columnEntityType) + { + generateSource(GEN_SOURCE_INTERFACE, sourceFolder, packageName, entityType, tableName, columnEntityType); + } + + /** + * @param sourceFolder + * @param packageName + * @param entityType + * @param tableName table Like + * @param columnEntityType + */ + public static void generateSource(String type, String sourceFolder, String packageName, String entityType, String tableName, String columnEntityType) { if (sourceFolder == null || sourceFolder.trim().length() == 0) throw new IllegalArgumentException("Must specify source folder"); @@ -792,9 +807,7 @@ public class ModelInterfaceGenerator if (tableName == null || tableName.trim().length() == 0) throw new IllegalArgumentException("Must specify table name"); - StringBuilder tableLike = new StringBuilder().append(tableName.trim()); - if (!tableLike.toString().startsWith("'") || !tableLike.toString().endsWith("'")) - tableLike = new StringBuilder("'").append(tableLike).append("'"); + StringBuilder tableLike = new StringBuilder().append(tableName.trim().toUpperCase().replaceAll("'", "")); StringBuilder entityTypeFilter = new StringBuilder(); if (entityType != null && entityType.trim().length() > 0) @@ -803,7 +816,7 @@ public class ModelInterfaceGenerator StringTokenizer tokenizer = new StringTokenizer(entityType, ","); int i = 0; while(tokenizer.hasMoreTokens()) { - StringBuilder token = new StringBuilder(tokenizer.nextToken().trim()); + StringBuilder token = new StringBuilder().append(tokenizer.nextToken().trim()); if (!token.toString().startsWith("'") || !token.toString().endsWith("'")) token = new StringBuilder("'").append(token).append("'"); if (i > 0) @@ -828,7 +841,7 @@ public class ModelInterfaceGenerator directory = new StringBuilder(directory.toString().replaceAll("[\\\\]", File.separator)); else directory = new StringBuilder(directory.toString().replaceAll("[/]", File.separator)); - directory = new StringBuilder(directory).append(packagePath); + directory.append(packagePath); file = new File(directory.toString()); if (!file.exists()) file.mkdirs(); @@ -847,9 +860,19 @@ public class ModelInterfaceGenerator .append("WHERE IsActive = 'Y' AND TableName NOT LIKE '%_Trl' "); // Autodetect if we need to use IN or LIKE clause - teo_sarca [ 3020640 ] if (tableLike.indexOf(",") == -1) - sql.append(" AND TableName LIKE ").append(tableLike); - else - sql.append(" AND TableName IN (").append(tableLike).append(")"); // only specific tables + sql.append(" AND UPPER(TableName) LIKE ").append(DB.TO_STRING(tableLike.toString())); + else { // only specific tables + StringBuilder finalTableLike = new StringBuilder(""); + for (String table : tableLike.toString().split(",")) { + if (finalTableLike.length() > 0) + finalTableLike.append(", "); + + finalTableLike.append(DB.TO_STRING(table.replaceAll("'", "").trim())); + } + + sql.append(" AND UPPER(TableName) IN (").append(finalTableLike).append(")"); + } + sql.append(" AND ").append(entityTypeFilter.toString()); if (filterViews != null) { sql.append(filterViews); @@ -884,7 +907,10 @@ public class ModelInterfaceGenerator rs = pstmt.executeQuery(); while (rs.next()) { - new ModelInterfaceGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter); + if (type.equals(GEN_SOURCE_INTERFACE)) + new ModelInterfaceGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter); + else if (type.equals(GEN_SOURCE_CLASS)) + new ModelClassGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter); } } catch (SQLException e) From a3fb6f5b9c53caf96f0020e9350237f3b0b2726d Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Sat, 18 May 2024 04:14:09 +0200 Subject: [PATCH 029/169] IDEMPIERE-6149 Model[Class|Interface]Generator must have a predictable order independent of the developer language (collation) (#2368) --- .../src/org/adempiere/util/ModelClassGenerator.java | 8 ++++++++ .../src/org/adempiere/util/ModelInterfaceGenerator.java | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java index 760aa3b4cc..5221aa6e9e 100644 --- a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java +++ b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java @@ -318,6 +318,10 @@ public class ModelClassGenerator + " AND c.IsActive='Y' AND (c.ColumnSQL IS NULL OR c.ColumnSQL NOT LIKE '@SQL%') " + (!Util.isEmpty(entityTypeFilter) ? " AND c." + entityTypeFilter : "") + " ORDER BY c.ColumnName"; + if (DB.isOracle()) + sql += " COLLATE \"BINARY\""; + else if (DB.isPostgreSQL()) + sql += " COLLATE \"C\""; boolean isKeyNamePairCreated = false; // true if the method "getKeyNamePair" is already generated PreparedStatement pstmt = null; ResultSet rs = null; @@ -669,6 +673,10 @@ public class ModelClassGenerator StringBuilder statement = new StringBuilder(); // String sql = "SELECT Value, Name FROM AD_Ref_List WHERE AD_Reference_ID=? ORDER BY Value"; // even inactive, see IDEMPIERE-4979 + if (DB.isOracle()) + sql += " COLLATE \"BINARY\""; + else if (DB.isPostgreSQL()) + sql += " COLLATE \"C\""; PreparedStatement pstmt = null; ResultSet rs = null; try diff --git a/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java b/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java index 6b7a47da2c..97736811c0 100644 --- a/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java +++ b/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java @@ -252,6 +252,10 @@ public class ModelInterfaceGenerator + " AND c.IsActive='Y' AND (c.ColumnSQL IS NULL OR c.ColumnSQL NOT LIKE '@SQL%') " + (!Util.isEmpty(entityTypeFilter) ? " AND c." + entityTypeFilter : "") + " ORDER BY c.ColumnName"; + if (DB.isOracle()) + sql += " COLLATE \"BINARY\""; + else if (DB.isPostgreSQL()) + sql += " COLLATE \"C\""; PreparedStatement pstmt = null; ResultSet rs = null; try { From 2aa7215e2c546361e5b2e1c2f749c3d68afb9774 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Sat, 18 May 2024 04:22:03 +0200 Subject: [PATCH 030/169] IDEMPIERE-5567 Support of UUID as Key (FHCA-4195) + IDEMPIERE-6143 (#2367) - fix export and import for UUID based tables - fix ExportAction creating temporary files with the wrong extension - fix export and import of multi-selection fields IDEMPIERE-6143 --- .../adempiere/impexp/GridTabCSVExporter.java | 17 +++-- .../adempiere/impexp/GridTabCSVImporter.java | 68 ++++++++++--------- .../webui/panel/action/ExportAction.java | 19 +++--- 3 files changed, 55 insertions(+), 49 deletions(-) diff --git a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVExporter.java b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVExporter.java index 87a874a8aa..ea5d7c4a70 100644 --- a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVExporter.java +++ b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVExporter.java @@ -106,6 +106,8 @@ public class GridTabCSVExporter implements IGridTabExporter specialHDispayType = DisplayType.Location; continue; } else if (! (field.isDisplayed() || field.isDisplayedGrid())) { + continue; + } else if (DisplayType.Binary == field.getDisplayType()) { continue; } String headName = resolveColumnName(table, column); @@ -438,15 +440,16 @@ public class GridTabCSVExporter implements IGridTabExporter String ref = (String) idO; value = MRefList.getListName(Env.getCtx(), column.getAD_Reference_Value_ID(), ref); } else { - int id = (Integer) idO; + MTable forTab = MTable.get(Env.getCtx(), foreignTable); + String foreignKeyCol = forTab.getKeyColumns()[0]; int start = headName.indexOf("[")+1; int end = headName.length()-1; String foreignColumn = headName.substring(start, end); StringBuilder select = new StringBuilder("SELECT ") .append(foreignColumn).append(" FROM ") .append(foreignTable).append(" WHERE ") - .append(foreignTable).append("_ID=?"); - value = DB.getSQLValueStringEx(null, select.toString(), id); + .append(foreignKeyCol).append("=?"); + value = DB.getSQLValueStringEx(null, select.toString(), idO); } } } else { @@ -478,7 +481,7 @@ public class GridTabCSVExporter implements IGridTabExporter */ private String resolveColumnName(MTable table, MColumn column) { StringBuilder name = new StringBuilder(column.getColumnName()); - if (DisplayType.isLookup(column.getAD_Reference_ID())) { + if (DisplayType.isLookup(column.getAD_Reference_ID()) && !DisplayType.isMultiID(column.getAD_Reference_ID())) { // resolve to identifier - search for value first, if not search for name - if not use the ID String foreignTable = column.getReferenceTableName(); if ("AD_EntityType".equals(foreignTable) && I_AD_EntityType.COLUMNNAME_AD_EntityType_ID.equals(column.getColumnName())){ @@ -579,7 +582,8 @@ public class GridTabCSVExporter implements IGridTabExporter || gridField.isEncryptedColumn() || !(gridField.isDisplayed() || gridField.isDisplayedGrid()) || gridField.isReadOnly() - || (DisplayType.Button == MColumn.get(Env.getCtx(),gridField.getAD_Column_ID()).getAD_Reference_ID()) + || DisplayType.Button == gridField.getDisplayType() + || DisplayType.Binary == gridField.getDisplayType() ) continue; @@ -598,7 +602,8 @@ public class GridTabCSVExporter implements IGridTabExporter { if ("AD_Client_ID".equals(field.getColumnName())) continue; - if (DisplayType.Button == MColumn.get(Env.getCtx(),field.getAD_Column_ID()).getAD_Reference_ID()) + if ( DisplayType.Button == field.getDisplayType() + || DisplayType.Binary == field.getDisplayType()) continue; if ( field.isVirtualColumn() || field.isEncrypted() diff --git a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java index df5cd9deac..2b4e28bddc 100644 --- a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java +++ b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java @@ -1022,17 +1022,16 @@ public class GridTabCSVImporter implements IGridTabImporter if (isForeing && value != null && !"(null)".equals(value)){ String foreignTable = column.getReferenceTableName(); - String idS = null; - int id = -1; + Object idS = null; if("AD_Ref_List".equals(foreignTable)) - idS= resolveForeignList(column,foreignColumn,value,null); + idS = resolveForeignList(column,foreignColumn,value,null); else - id = resolveForeign(foreignTable,foreignColumn,value,field,null); + idS = resolveForeign(foreignTable,foreignColumn,value,field,null); - if(idS == null && id < 0){ + if(idS == null){ //it could be that record still doesn't exist if import mode is inserting or merging if(isUpdateMode()) - return new StringBuilder(Msg.getMsg(Env.getCtx(),id==-2?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value})); + return new StringBuilder(Msg.getMsg(Env.getCtx(),(idS instanceof Integer && (int)idS==-2)?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value})); } } else { // TODO: we could validate length of string or min/max @@ -1095,9 +1094,9 @@ public class GridTabCSVImporter implements IGridTabImporter if(isForeing && !"(null)".equals(value)){ String foreignTable = columnName.substring(0,columnName.length()-3); - int id = resolveForeign(foreignTable,foreignColumn,value,field,null); - if (id < 0) - return new StringBuilder(Msg.getMsg(Env.getCtx(), id==-2?"ForeignMultipleResolved":"ForeignNotResolved" ,new Object[]{header.get(j),value})); + Object id = resolveForeign(foreignTable,foreignColumn,value,field,null); + if (id == null || (id instanceof Integer && (int)id < 0)) + return new StringBuilder(Msg.getMsg(Env.getCtx(),(id instanceof Integer && (int)id==-2)?"ForeignMultipleResolved":"ForeignNotResolved" ,new Object[]{header.get(j),value})); } isEmptyRow=false; } @@ -1205,19 +1204,18 @@ public class GridTabCSVImporter implements IGridTabImporter }else if (masterRecord==null && isDetail){ MColumn column = MColumn.get(Env.getCtx(),field.getAD_Column_ID()); String foreignTable = column.getReferenceTableName(); - String idS = null; - int id = -1; + Object idS = null; if ("AD_Ref_List".equals(foreignTable)) - idS= resolveForeignList(column, foreignColumn, value,trx); + idS = resolveForeignList(column, foreignColumn, value,trx); else - id = resolveForeign(foreignTable,foreignColumn,value, field, trx); + idS = resolveForeign(foreignTable,foreignColumn,value, field, trx); - if(idS == null && id < 0) - return Msg.getMsg(Env.getCtx(),id==-2?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value}); + if (idS == null || (idS instanceof Integer && (int)idS < 0)) + return Msg.getMsg(Env.getCtx(),(idS instanceof Integer && (int)idS==-2)?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value}); - if(id >= 0) - logMsg = gridTab.setValue(field,id); + if (idS instanceof Integer && (int)idS >= 0) + logMsg = gridTab.setValue(field,idS); else if (idS != null) logMsg = gridTab.setValue(field,idS); @@ -1251,14 +1249,14 @@ public class GridTabCSVImporter implements IGridTabImporter isThereRow =true; } else { - int id = resolveForeign(foreignTable, foreignColumn, value,field,trx); - if(id < 0) - return Msg.getMsg(Env.getCtx(),id==-2?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value}); + Object id = resolveForeign(foreignTable, foreignColumn, value,field,trx); + if (id == null || (id instanceof Integer && (int)id < 0)) + return Msg.getMsg(Env.getCtx(),(id instanceof Integer && (int)id==-2)?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value}); setValue = id; if (field.isParentValue()) { - int actualId = (Integer) field.getValue(); - if (actualId != id) { + Object actualId = field.getValue(); + if (actualId != null && ! actualId.equals(id)) { logMsg = Msg.getMsg(Env.getCtx(), "ParentCannotChange",new Object[]{header.get(i)}); break; } @@ -1380,9 +1378,9 @@ public class GridTabCSVImporter implements IGridTabImporter setValue = idS; } else { - int id = resolveForeign(foreignTable, foreignColumn, setValue, field, trx); - if (id < 0) - return Msg.getMsg(Env.getCtx(),id==-2?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{columnName,setValue}); + Object id = resolveForeign(foreignTable, foreignColumn, setValue, field, trx); + if (id == null || (id instanceof Integer && (int)id < 0)) + return Msg.getMsg(Env.getCtx(),(id instanceof Integer && (int)id==-2)?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{columnName,setValue}); setValue = id; } @@ -1478,7 +1476,7 @@ public class GridTabCSVImporter implements IGridTabImporter String idS = resolveForeignList(column, foreignColumn, tmpValue,trx); setValue = idS; }else { - int id = resolveForeign(foreignTable, foreignColumn, tmpValue,field,trx); + Object id = resolveForeign(foreignTable, foreignColumn, tmpValue,field,trx); setValue = id; } }else{ @@ -1519,8 +1517,7 @@ public class GridTabCSVImporter implements IGridTabImporter String idS = resolveForeignList(column,foreignColumn,value,trx); value = idS; }else { - int id = resolveForeign(foreignTable,foreignColumn,value,field,trx); - value = id; + value = resolveForeign(foreignTable,foreignColumn,value,field,trx); } } }else{ //mandatory key not found @@ -1587,7 +1584,7 @@ public class GridTabCSVImporter implements IGridTabImporter * @param trx * @return -3 for not found, -2 for more than 1 match and > 0 for foreign id */ - private int resolveForeign(String foreignTable, String foreignColumn, Object value, GridField field, Trx trx) { + private Object resolveForeign(String foreignTable, String foreignColumn, Object value, GridField field, Trx trx) { boolean systemAccess = false; if (!"AD_Client".equals(foreignTable)) { MTable ft = MTable.get(Env.getCtx(), foreignTable); @@ -1632,10 +1629,14 @@ public class GridTabCSVImporter implements IGridTabImporter } } StringBuilder selectCount = new StringBuilder("SELECT COUNT(*)").append(postSelect); - StringBuilder selectId = new StringBuilder("SELECT ").append(foreignTable).append("_ID").append(postSelect); + MTable forTab = MTable.get(Env.getCtx(), foreignTable); + StringBuilder selectId = new StringBuilder("SELECT ").append(forTab.getKeyColumns()[0]).append(postSelect); int count = DB.getSQLValueEx(trxName, selectCount.toString(), value, thisClientId); if (count == 1) { // single value found, OK - return DB.getSQLValueEx(trxName, selectId.toString(), value, thisClientId); + if (forTab.isUUIDKeyTable()) + return DB.getSQLValueStringEx(trxName, selectId.toString(), value, thisClientId); + else + return DB.getSQLValueEx(trxName, selectId.toString(), value, thisClientId); } else if (count > 1) { // multiple values found, error ForeignMultipleResolved return -2; } else if (count == 0) { // no values found, error ForeignNotResolved @@ -1643,7 +1644,10 @@ public class GridTabCSVImporter implements IGridTabImporter // not found in client, try with System count = DB.getSQLValueEx(trxName, selectCount.toString(), value, 0 /* System */); if (count == 1) { // single value found, OK - return DB.getSQLValueEx(trxName, selectId.toString(), value, 0 /* System */); + if (forTab.isUUIDKeyTable()) + return DB.getSQLValueStringEx(trxName, selectId.toString(), value, 0 /* System */); + else + return DB.getSQLValueEx(trxName, selectId.toString(), value, 0 /* System */); } else if (count > 1) { // multiple values found, error ForeignMultipleResolved return -2; } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ExportAction.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ExportAction.java index 4c0e9865dc..0842d581e2 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ExportAction.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ExportAction.java @@ -15,12 +15,12 @@ package org.adempiere.webui.panel.action; import java.io.File; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.TreeMap; import org.adempiere.base.IGridTabExporter; import org.adempiere.base.equinox.EquinoxExtensionLocator; @@ -99,8 +99,8 @@ public class ExportAction implements EventListener */ public void export() { - exporterMap = new HashMap(); - extensionMap = new HashMap(); + exporterMap = new TreeMap(); + extensionMap = new TreeMap(); List exporterList = EquinoxExtensionLocator.instance().list(IGridTabExporter.class).getExtensions(); MRole role = MRole.getDefault(); for(IGridTabExporter exporter : exporterList) @@ -130,11 +130,8 @@ public class ExportAction implements EventListener cboType.setMold("select"); cboType.getItems().clear(); - List keys = new ArrayList<>(extensionMap.keySet()); - Collections.sort(keys); - for(String key : keys) - { - cboType.appendItem(key, key); + for (Entry extension : extensionMap.entrySet()) { + cboType.appendItem(extension.getKey(), extension.getValue()); } cboType.setSelectedIndex(0); @@ -352,12 +349,12 @@ public class ExportAction implements EventListener */ protected IGridTabExporter getExporter() { ListItem li = cboType.getSelectedItem(); - if(li == null || li.getValue() == null) + if(li == null || li.getLabel() == null) { return null; } - String ext = li.getValue().toString(); + String ext = li.getLabel().toString(); IGridTabExporter exporter = exporterMap.get(ext); return exporter; } From 88a9d3eb3a1e7a71431257d5e01ffcd2b3997b1b Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Sat, 18 May 2024 04:22:59 +0200 Subject: [PATCH 031/169] IDEMPIERE-6137 Payment Rule does not appear in reports from Sales Order (#2364) --- .../src/org/adempiere/util/ModelClassGenerator.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java index 5221aa6e9e..3ec85f8da1 100644 --- a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java +++ b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java @@ -19,8 +19,6 @@ *****************************************************************************/ package org.adempiere.util; -import static org.compiere.model.SystemIDs.REFERENCE_PAYMENTRULE; - import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; @@ -475,13 +473,6 @@ public class ModelClassGenerator String staticVar = addListValidation (sb, AD_Reference_ID, columnName); sb.insert(0, staticVar); } - - // Payment Validation - if (displayType == DisplayType.Payment) - { - String staticVar = addListValidation (sb, REFERENCE_PAYMENTRULE, columnName); - sb.insert(0, staticVar); - } // setValue ("ColumnName", xx); if (virtualColumn) From a5a1ce3050960ed51718c64c3307c0f8a55bce71 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Sat, 18 May 2024 04:24:55 +0200 Subject: [PATCH 032/169] IDEMPIERE-6144 Can't export a report if the name has less than 3 characters (#2363) --- org.adempiere.base/src/org/compiere/tools/FileUtil.java | 6 ++---- .../org/idempiere/ui/zk/report/CSVReportViewerRenderer.java | 5 +---- .../idempiere/ui/zk/report/HTMLReportViewerRenderer.java | 2 -- .../org/idempiere/ui/zk/report/PDFReportViewerRenderer.java | 2 -- .../org/idempiere/ui/zk/report/XLSReportViewerRenderer.java | 2 -- .../idempiere/ui/zk/report/XLSXReportViewerRenderer.java | 5 +---- .../zk/datatable/DatatableReportViewerRenderer.java | 2 -- .../org/idempiere/zk/pivot/PivotReportVieweRenderer.java | 2 -- 8 files changed, 4 insertions(+), 22 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/tools/FileUtil.java b/org.adempiere.base/src/org/compiere/tools/FileUtil.java index 2188602115..f60aceeafd 100644 --- a/org.adempiere.base/src/org/compiere/tools/FileUtil.java +++ b/org.adempiere.base/src/org/compiere/tools/FileUtil.java @@ -467,10 +467,8 @@ public class FileUtil public static File createTempFile(String prefix, String suffix, File directory) throws IOException { - if (prefix.length() < 3) { - throw new IllegalArgumentException("Prefix string \"" + prefix + - "\" too short: length must be at least 3"); - } + if (Util.isEmpty(prefix)) + throw new IllegalArgumentException("Prefix is required"); prefix = Util.setFilenameCorrect(prefix); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/CSVReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/CSVReportViewerRenderer.java index a00f763ace..2b95c5c5c2 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/CSVReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/CSVReportViewerRenderer.java @@ -87,10 +87,7 @@ public class CSVReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (log.isLoggable(Level.FINE)) - { - log.log(Level.FINE, "Path="+path + " Prefix="+prefix); - } + if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); IReportRenderer renderer = Core.getReportRenderer(getId()); CSVReportRendererConfiguration config = new CSVReportRendererConfiguration() diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/HTMLReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/HTMLReportViewerRenderer.java index 58060493d9..9563304f06 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/HTMLReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/HTMLReportViewerRenderer.java @@ -88,8 +88,6 @@ public class HTMLReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (prefix.length() < 3) - prefix += "_".repeat(3-prefix.length()); if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); String contextPath = Executions.getCurrent().getContextPath(); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/PDFReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/PDFReportViewerRenderer.java index 354c0228ea..535036221f 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/PDFReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/PDFReportViewerRenderer.java @@ -86,8 +86,6 @@ public class PDFReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (prefix.length() < 3) - prefix += "_".repeat(3-prefix.length()); if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); IReportRenderer renderer = Core.getReportRenderer(getId()); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSReportViewerRenderer.java index 56e1464964..4e50f2f83d 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSReportViewerRenderer.java @@ -86,8 +86,6 @@ public class XLSReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (prefix.length() < 3) - prefix += "_".repeat(3-prefix.length()); if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); IReportRenderer renderer = Core.getReportRenderer(getId()); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSXReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSXReportViewerRenderer.java index 9254e2e855..510e950d93 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSXReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSXReportViewerRenderer.java @@ -86,10 +86,7 @@ public class XLSXReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (log.isLoggable(Level.FINE)) - { - log.log(Level.FINE, "Path=" + path + " Prefix=" + prefix); - } + if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path=" + path + " Prefix=" + prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); IReportRenderer renderer = Core.getReportRenderer(getId()); XLSXReportRendererConfiguration config = new XLSXReportRendererConfiguration() diff --git a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportViewerRenderer.java b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportViewerRenderer.java index dbc7812510..c0c2f8552d 100644 --- a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportViewerRenderer.java +++ b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportViewerRenderer.java @@ -83,8 +83,6 @@ public class DatatableReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (prefix.length() < 3) - prefix += "_".repeat(3-prefix.length()); if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); String contextPath = Executions.getCurrent().getContextPath(); diff --git a/org.idempiere.zk.pivot/src/org/idempiere/zk/pivot/PivotReportVieweRenderer.java b/org.idempiere.zk.pivot/src/org/idempiere/zk/pivot/PivotReportVieweRenderer.java index ba1c85102f..6be2e6160e 100644 --- a/org.idempiere.zk.pivot/src/org/idempiere/zk/pivot/PivotReportVieweRenderer.java +++ b/org.idempiere.zk.pivot/src/org/idempiere/zk/pivot/PivotReportVieweRenderer.java @@ -81,8 +81,6 @@ public class PivotReportVieweRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (prefix.length() < 3) - prefix += "_".repeat(3-prefix.length()); if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); IReportRenderer renderer = Core.getReportRenderer(getId()); From 9071ae38d6733c4a59f02c7d26f6fd25ecb70d6a Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Sat, 18 May 2024 04:26:53 +0200 Subject: [PATCH 033/169] IDEMPIERE-5728 Fix RV_UnPosted view definition in dictionary (#2361) --- migration/iD11/oracle/202405151228_IDEMPIERE-5728.sql | 10 ++++++++++ .../iD11/postgresql/202405151228_IDEMPIERE-5728.sql | 7 +++++++ 2 files changed, 17 insertions(+) create mode 100644 migration/iD11/oracle/202405151228_IDEMPIERE-5728.sql create mode 100644 migration/iD11/postgresql/202405151228_IDEMPIERE-5728.sql diff --git a/migration/iD11/oracle/202405151228_IDEMPIERE-5728.sql b/migration/iD11/oracle/202405151228_IDEMPIERE-5728.sql new file mode 100644 index 0000000000..0e88421aed --- /dev/null +++ b/migration/iD11/oracle/202405151228_IDEMPIERE-5728.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-5728 +SELECT register_migration_script('202405151228_IDEMPIERE-5728.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- May 15, 2024, 12:28:30 PM CEST +UPDATE AD_ViewColumn SET ColumnName='RV_UnPosted_UU',Updated=TO_TIMESTAMP('2024-05-15 12:28:30','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_ViewColumn_ID=217690 +; + diff --git a/migration/iD11/postgresql/202405151228_IDEMPIERE-5728.sql b/migration/iD11/postgresql/202405151228_IDEMPIERE-5728.sql new file mode 100644 index 0000000000..e5da84f8db --- /dev/null +++ b/migration/iD11/postgresql/202405151228_IDEMPIERE-5728.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-5728 +SELECT register_migration_script('202405151228_IDEMPIERE-5728.sql') FROM dual; + +-- May 15, 2024, 12:28:30 PM CEST +UPDATE AD_ViewColumn SET ColumnName='RV_UnPosted_UU',Updated=TO_TIMESTAMP('2024-05-15 12:28:30','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_ViewColumn_ID=217690 +; + From 7cd0c88e3ae97e755307a10783dd5c600ca7efbb Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Sun, 19 May 2024 13:29:21 +0200 Subject: [PATCH 034/169] IDEMPIERE-5567 Support of UUID as Key (FHCA-4195) (#2371) - avoid calling delete cascade for ID when is UUID table --- org.adempiere.base/src/org/compiere/model/PO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.adempiere.base/src/org/compiere/model/PO.java b/org.adempiere.base/src/org/compiere/model/PO.java index b674852e8a..be645b0902 100644 --- a/org.adempiere.base/src/org/compiere/model/PO.java +++ b/org.adempiere.base/src/org/compiere/model/PO.java @@ -4067,7 +4067,7 @@ public abstract class PO delete_Tree(MTree_Base.TREETYPE_CustomTable); } - if (m_KeyColumns != null && m_KeyColumns.length == 1) { + if (m_KeyColumns != null && m_KeyColumns.length == 1 && !getTable().isUUIDKeyTable()) { //delete cascade only for single key column record PO_Record.deleteModelCascade(p_info.getTableName(), Record_ID, localTrxName); // Delete Cascade AD_Table_ID/Record_ID (Attachments, ..) From fb594a5f565c72c4c1c2bbbec8364a1b8de9d7b0 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 11 Apr 2024 05:53:44 +0200 Subject: [PATCH 035/169] IDEMPIERE-6102 Performance: avoid SQL on AD_TreeNode when the table doesn't have a custom tree (#2309) --- .../src/org/compiere/model/MTable.java | 20 ++++++++++++++++++- .../src/org/compiere/model/PO.java | 18 ++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/MTable.java b/org.adempiere.base/src/org/compiere/model/MTable.java index ef40b27e0d..00500f8172 100644 --- a/org.adempiere.base/src/org/compiere/model/MTable.java +++ b/org.adempiere.base/src/org/compiere/model/MTable.java @@ -67,7 +67,11 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport /** * */ - private static final long serialVersionUID = -167824144142429242L; + private static final long serialVersionUID = -2459194178797758731L; + + /** + * + */ public final static int MAX_OFFICIAL_ID = 999999; @@ -1098,6 +1102,20 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport return indexName.toString(); } + private Boolean hasCustomTree = null; + + /** + * If the table has a custom tree defined + * @return + */ + public boolean hasCustomTree() { + if (hasCustomTree == null) { + int exists = DB.getSQLValueEx(get_TrxName(), "SELECT 1 FROM AD_Tree WHERE TreeType=? AND AD_Table_ID=? AND IsActive='Y'", MTree_Base.TREETYPE_CustomTable, getAD_Table_ID()); + hasCustomTree = Boolean.valueOf(exists == 1); + } + return hasCustomTree.booleanValue(); + } + /** * Get Partition Name of the table of the given level * @param tableName diff --git a/org.adempiere.base/src/org/compiere/model/PO.java b/org.adempiere.base/src/org/compiere/model/PO.java index be645b0902..47bda21f69 100644 --- a/org.adempiere.base/src/org/compiere/model/PO.java +++ b/org.adempiere.base/src/org/compiere/model/PO.java @@ -2641,10 +2641,10 @@ public abstract class PO // table with potential tree if (get_ColumnIndex("IsSummary") >= 0) { - if (newRecord) + if (newRecord && getTable().hasCustomTree()) insert_Tree(MTree_Base.TREETYPE_CustomTable); int idxValue = get_ColumnIndex("Value"); - if (newRecord || (idxValue >= 0 && is_ValueChanged(idxValue))) + if (getTable().hasCustomTree() && (newRecord || (idxValue >= 0 && is_ValueChanged(idxValue)))) update_Tree(MTree_Base.TREETYPE_CustomTable); } } @@ -2749,8 +2749,16 @@ public abstract class PO } // saveFinish /** - * Update Value or create new record. - * To reload call load() - not updated + * Get the MTable object associated to this PO + * @return MTable + */ + private MTable getTable() { + return MTable.get(getCtx(), get_TableName()); + } + + /** + * Update or insert new record.
+ * To reload call load(). * @param trxName transaction * @return true if saved */ @@ -4063,7 +4071,7 @@ public abstract class PO { // deleteTranslations(localTrxName); - if (get_ColumnIndex("IsSummary") >= 0) { + if (get_ColumnIndex("IsSummary") >= 0 && getTable().hasCustomTree()) { delete_Tree(MTree_Base.TREETYPE_CustomTable); } From 6f4686f9db80a413e79aa0b94fb5595879dd0131 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 23 May 2024 16:47:26 +0200 Subject: [PATCH 036/169] IDEMPIERE-6158 Warehouse Purchase Order cannot be completed after change of product (#2376) * IDEMPIERE-6158 Warehouse Purchase Order cannot be completed after change of product * - fix callouts to avoid setting M_Product_ID to null --- .../src/org/compiere/model/CalloutInOut.java | 2 +- .../src/org/compiere/model/CalloutInventory.java | 4 ++-- .../src/org/compiere/model/CalloutInvoice.java | 2 +- .../src/org/compiere/model/CalloutMovement.java | 2 +- .../src/org/compiere/model/CalloutOrder.java | 2 +- .../src/org/compiere/model/CalloutProduction.java | 2 +- org.adempiere.base/src/org/compiere/model/MOrderLine.java | 6 ++++-- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutInOut.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutInOut.java index 412e02f231..8ca716ff72 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutInOut.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutInOut.java @@ -525,7 +525,7 @@ public class CalloutInOut extends CalloutEngine mTab.setValue("M_Locator_ID", Integer.valueOf(M_Locator_ID)); } else - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); // int M_Warehouse_ID = Env.getContextAsInt(ctx, WindowNo, "M_Warehouse_ID"); boolean IsSOTrx = "Y".equals(Env.getContext(ctx, WindowNo, "IsSOTrx")); diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutInventory.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutInventory.java index 9e6d034539..0d9b8b2a9e 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutInventory.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutInventory.java @@ -60,7 +60,7 @@ public class CalloutInventory extends CalloutEngine if ("M_Product_ID".equals(mField.getColumnName())) { // product changed - remove old ASI - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); } // Get Book Value @@ -96,7 +96,7 @@ public class CalloutInventory extends CalloutEngine if (M_AttributeSetInstance_ID != 0) mTab.setValue(MInventoryLine.COLUMNNAME_M_AttributeSetInstance_ID, M_AttributeSetInstance_ID); else - mTab.setValue(MInventoryLine.COLUMNNAME_M_AttributeSetInstance_ID, null); + mTab.setValue(MInventoryLine.COLUMNNAME_M_AttributeSetInstance_ID, 0); } // Set QtyBook from first storage location diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java index b22f690162..e2db5081a5 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java @@ -325,7 +325,7 @@ public class CalloutInvoice extends CalloutEngine && Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID") != 0) mTab.setValue("M_AttributeSetInstance_ID", Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID")); else - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); /***** Price Calculation see also qty ****/ boolean IsSOTrx = Env.getContext(ctx, WindowNo, "IsSOTrx").equals("Y"); diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutMovement.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutMovement.java index cc2f6466bc..78f8712f23 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutMovement.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutMovement.java @@ -54,7 +54,7 @@ public class CalloutMovement extends CalloutEngine && Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID") != 0) mTab.setValue("M_AttributeSetInstance_ID", Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID")); else - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); checkQtyAvailable(ctx, mTab, WindowNo, M_Product_ID, null); return ""; diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java index 2684da6eba..43856064c1 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java @@ -759,7 +759,7 @@ public class CalloutOrder extends CalloutEngine && Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID") != 0) mTab.setValue("M_AttributeSetInstance_ID", Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID")); else - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); /***** Price Calculation see also qty ****/ int C_BPartner_ID = Env.getContextAsInt(ctx, WindowNo, "C_BPartner_ID"); diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutProduction.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutProduction.java index 8d686f27e6..f4d727d817 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutProduction.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutProduction.java @@ -55,7 +55,7 @@ public class CalloutProduction extends CalloutEngine } else { - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); } MProduct product = MProduct.get(ctx, M_Product_ID); diff --git a/org.adempiere.base/src/org/compiere/model/MOrderLine.java b/org.adempiere.base/src/org/compiere/model/MOrderLine.java index 0999811023..bbdc0946df 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrderLine.java +++ b/org.adempiere.base/src/org/compiere/model/MOrderLine.java @@ -808,8 +808,10 @@ public class MOrderLine extends X_C_OrderLine // R/O Check - Product/Warehouse Change if (!newRecord - && (is_ValueChanged("M_Product_ID") || is_ValueChanged("M_Warehouse_ID") || - (!getParent().isProcessed() && is_ValueChanged(COLUMNNAME_M_AttributeSetInstance_ID)))) + && ( is_ValueChanged("M_Product_ID") + || is_ValueChanged("M_Warehouse_ID") + || ( !getParent().isProcessed() + && getM_AttributeSetInstance_ID() != get_ValueOldAsInt(COLUMNNAME_M_AttributeSetInstance_ID)))) { if (!canChangeWarehouse()) return false; From 9740f2bf18add0fa95ec8a59e7c2d8f7457e0ec0 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Tue, 28 May 2024 16:23:25 +0200 Subject: [PATCH 037/169] IDEMPIERE-5849 Fix PackOut broken with GenericPO after Cache Reset (#2382) --- org.adempiere.base/src/org/adempiere/model/GenericPO.java | 3 ++- org.adempiere.base/src/org/compiere/model/POInfo.java | 1 + org.adempiere.pipo/src/org/adempiere/pipo2/PoExporter.java | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/org.adempiere.base/src/org/adempiere/model/GenericPO.java b/org.adempiere.base/src/org/adempiere/model/GenericPO.java index 23656aa137..50463421ea 100644 --- a/org.adempiere.base/src/org/adempiere/model/GenericPO.java +++ b/org.adempiere.base/src/org/adempiere/model/GenericPO.java @@ -26,6 +26,7 @@ import java.math.BigDecimal; import java.sql.ResultSet; import java.util.Properties; +import org.adempiere.util.ServerContextPropertiesWrapper; import org.compiere.model.MTable; import org.compiere.model.PO; import org.compiere.model.POInfo; @@ -346,7 +347,7 @@ public class GenericPO extends PO implements DocAction { * @author Low Heng Sin * */ -class PropertiesWrapper extends Properties { +class PropertiesWrapper extends ServerContextPropertiesWrapper { /** * generated serial id */ diff --git a/org.adempiere.base/src/org/compiere/model/POInfo.java b/org.adempiere.base/src/org/compiere/model/POInfo.java index ce09001f51..4e4e559351 100644 --- a/org.adempiere.base/src/org/compiere/model/POInfo.java +++ b/org.adempiere.base/src/org/compiere/model/POInfo.java @@ -584,6 +584,7 @@ public class POInfo implements Serializable } catch (Exception e) { + CLogger.get().log(Level.WARNING, "Cannot create Lookup for " + m_columns[index].ColumnName + "[" + m_columns[index].AD_Column_ID + "]", e); lookup = null; // cannot create Lookup } return lookup; diff --git a/org.adempiere.pipo/src/org/adempiere/pipo2/PoExporter.java b/org.adempiere.pipo/src/org/adempiere/pipo2/PoExporter.java index 115cad3cdb..f8dd4a3a4f 100644 --- a/org.adempiere.pipo/src/org/adempiere/pipo2/PoExporter.java +++ b/org.adempiere.pipo/src/org/adempiere/pipo2/PoExporter.java @@ -309,6 +309,8 @@ public class PoExporter { String lookupColumn = info.getColumnLookup(i).getColumnName(); tableName = lookupColumn.substring(0, lookupColumn.indexOf(".")); } + if (tableName == null) + throw new AdempiereException("Could not find the related table for column " + po.get_TableName() + "." + columnName); if ( info.getColumnDisplayType(i) == DisplayType.ChosenMultipleSelectionList || DisplayType.isMultiID(info.getColumnDisplayType(i))) { addTableReferenceMulti(columnName, tableName, new AttributesImpl()); From 69995b178b47af0437273d94f993d3af35e490d6 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Tue, 28 May 2024 16:24:22 +0200 Subject: [PATCH 038/169] IDEMPIERE-6150 NON-DB attachments are disappearing when a DB deletion fails (#2377) * IDEMPIERE-6150 NON-DB attachments are disappearing when a DB deletion fails - delete AD_Attachment and AD_Archive after commit of deletion Trx * - add patch from Heng Sin to convert string concatenation to text block --------- Co-authored-by: hengsin --- .../src/org/compiere/model/PO.java | 35 ++++++++++-- .../src/org/compiere/model/PO_Record.java | 55 ++++++++++++------- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/PO.java b/org.adempiere.base/src/org/compiere/model/PO.java index 47bda21f69..33834a22b6 100644 --- a/org.adempiere.base/src/org/compiere/model/PO.java +++ b/org.adempiere.base/src/org/compiere/model/PO.java @@ -4078,14 +4078,14 @@ public abstract class PO if (m_KeyColumns != null && m_KeyColumns.length == 1 && !getTable().isUUIDKeyTable()) { //delete cascade only for single key column record PO_Record.deleteModelCascade(p_info.getTableName(), Record_ID, localTrxName); - // Delete Cascade AD_Table_ID/Record_ID (Attachments, ..) - PO_Record.deleteRecordCascade(AD_Table_ID, Record_ID, localTrxName); + // Delete Cascade AD_Table_ID/Record_ID except Attachments/Archive (that's postponed until trx commit) + PO_Record.deleteRecordCascade(AD_Table_ID, Record_ID, "AD_Table.TableName NOT IN ('AD_Attachment','AD_Archive')", localTrxName); // Set referencing Record_ID Null AD_Table_ID/Record_ID PO_Record.setRecordNull(AD_Table_ID, Record_ID, localTrxName); } if (Record_UU != null) { PO_Record.deleteModelCascade(p_info.getTableName(), Record_UU, localTrxName); - PO_Record.deleteRecordCascade(AD_Table_ID, Record_UU, localTrxName); + PO_Record.deleteRecordCascade(AD_Table_ID, Record_UU, "AD_Table.TableName NOT IN ('AD_Attachment','AD_Archive')", localTrxName); PO_Record.setRecordNull(AD_Table_ID, Record_UU, localTrxName); } @@ -4226,9 +4226,10 @@ public abstract class PO } else { - if (CacheMgt.get().hasCache(p_info.getTableName())) { - Trx trxdel = Trx.get(get_TrxName(), false); - if (trxdel != null) { + Trx trxdel = Trx.get(get_TrxName(), false); + if (trxdel != null) { + // Schedule the reset cache for after committed the delete + if (CacheMgt.get().hasCache(p_info.getTableName())) { trxdel.addTrxEventListener(new TrxEventListener() { @Override public void afterRollback(Trx trxdel, boolean success) { @@ -4245,6 +4246,28 @@ public abstract class PO } }); } + // trigger the deletion of attachments and archives for after committed the delete + trxdel.addTrxEventListener(new TrxEventListener() { + @Override + public void afterRollback(Trx trxdel, boolean success) { + trxdel.removeTrxEventListener(this); + } + @Override + public void afterCommit(Trx trxdel, boolean success) { + if (success) { + if (m_KeyColumns != null && m_KeyColumns.length == 1 && !getTable().isUUIDKeyTable()) + // Delete Cascade AD_Table_ID/Record_ID on Attachments/Archive + // after commit because operations on external storage providers don't have rollback + PO_Record.deleteRecordCascade(AD_Table_ID, Record_ID, "AD_Table.TableName IN ('AD_Attachment','AD_Archive')", null); + if (Record_UU != null) + PO_Record.deleteRecordCascade(AD_Table_ID, Record_UU, "AD_Table.TableName IN ('AD_Attachment','AD_Archive')", null); + } + trxdel.removeTrxEventListener(this); + } + @Override + public void afterClose(Trx trxdel) { + } + }); } if (localTrx != null) { diff --git a/org.adempiere.base/src/org/compiere/model/PO_Record.java b/org.adempiere.base/src/org/compiere/model/PO_Record.java index d26daaa888..0e65126c6a 100644 --- a/org.adempiere.base/src/org/compiere/model/PO_Record.java +++ b/org.adempiere.base/src/org/compiere/model/PO_Record.java @@ -28,6 +28,7 @@ import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.compiere.util.KeyNamePair; import org.compiere.util.Msg; +import org.compiere.util.Util; /** * Maintain AD_Table_ID/Record_ID constraint @@ -47,10 +48,11 @@ public class PO_Record * Delete Cascade including (selected)parent relationships * @param AD_Table_ID table * @param Record_IDorUU record ID (int) or UUID (String) + * @param whereTables filter for the Tables * @param trxName transaction * @return false if could not be deleted */ - protected static boolean deleteRecordCascade (int AD_Table_ID, Serializable Record_IDorUU, String trxName) + protected static boolean deleteRecordCascade (int AD_Table_ID, Serializable Record_IDorUU, String whereTables, String trxName) { int refId; String columnName; @@ -63,7 +65,7 @@ public class PO_Record } else { throw new IllegalArgumentException(Record_IDorUU.getClass().getName() + " not supported for ID/UUID"); } - KeyNamePair[] cascades = getTablesWithConstraintType(refId, MColumn.FKCONSTRAINTTYPE_ModelCascade, trxName); + KeyNamePair[] cascades = getTablesWithConstraintType(refId, MColumn.FKCONSTRAINTTYPE_ModelCascade, whereTables, trxName); // Table Loop StringBuilder whereClause = new StringBuilder("AD_Table_ID=? AND ").append(columnName).append("=?"); for (KeyNamePair table : cascades) @@ -146,21 +148,21 @@ public class PO_Record KeyNamePair[] tables = s_po_record_tables_cache.get(key.toString()); if (tables != null) return tables; - final String sql = "" - + "SELECT t.AD_Table_ID, " - + " c.ColumnName " - + "FROM AD_Column c " - + " JOIN AD_Table t ON c.AD_Table_ID = t.AD_Table_ID " - + " LEFT JOIN AD_Ref_Table r ON c.AD_Reference_Value_ID = r.AD_Reference_ID " - + " LEFT JOIN AD_Table tr ON r.AD_Table_ID = tr.AD_Table_ID " - + "WHERE t.IsView = 'N' " - + " AND t.IsActive = 'Y' " - + " AND c.IsActive = 'Y' " - + " AND ( ( c.AD_Reference_ID = ? " - + " AND c.ColumnName = ? || '_ID' ) " - + " OR ( c.AD_Reference_ID IN (? , ?) " - + " AND ( tr.TableName = ? OR ( tr.TableName IS NULL AND c.ColumnName = ? || '_ID' ) ) ) ) " - + " AND c.FKConstraintType = ?"; + final String sql = """ + SELECT t.AD_Table_ID, + c.ColumnName + FROM AD_Column c + JOIN AD_Table t ON c.AD_Table_ID = t.AD_Table_ID + LEFT JOIN AD_Ref_Table r ON c.AD_Reference_Value_ID = r.AD_Reference_ID + LEFT JOIN AD_Table tr ON r.AD_Table_ID = tr.AD_Table_ID + WHERE t.IsView = 'N' + AND t.IsActive = 'Y' + AND c.IsActive = 'Y' + AND ( ( c.AD_Reference_ID = ? + AND c.ColumnName = ? || '_ID' ) + OR ( c.AD_Reference_ID IN (? , ?) + AND ( tr.TableName = ? OR ( tr.TableName IS NULL AND c.ColumnName = ? || '_ID' ) ) ) ) + AND c.FKConstraintType = ?"""; List> dependents = DB.getSQLArrayObjectsEx(trxName, sql, refTableDirId, tableName, refTableId, refTableSearchId, tableName, tableName, MColumn.FKCONSTRAINTTYPE_ModelCascade); if (dependents != null) { @@ -275,6 +277,18 @@ public class PO_Record * @return array of KeyNamePair */ private static KeyNamePair[] getTablesWithConstraintType(int refId, String constraintType, String trxName) { + return getTablesWithConstraintType(refId, constraintType, null, trxName); + } + + /** + * Get array of tables which has a refId column with the defined Constraint Type + * @param refId AD_Reference_ID - Record_ID or Record_UU + * @param constraintType - FKConstraintType of AD_Column + * @param whereTables - optional filter + * @param trxName + * @return array of KeyNamePair + */ + private static KeyNamePair[] getTablesWithConstraintType(int refId, String constraintType, String whereTables, String trxName) { String columnName; if (refId == DisplayType.RecordID) { columnName = "Record_ID"; @@ -284,11 +298,14 @@ public class PO_Record log.warning(refId + " not supported for ID/UUID"); return null; } - StringBuilder key = new StringBuilder(constraintType).append("|").append(refId); + StringBuilder key = new StringBuilder(constraintType).append("|").append(refId).append("|").append(whereTables); KeyNamePair[] tables = s_po_record_tables_cache.get(key.toString()); if (tables != null) return tables; - List listTables = new Query(Env.getCtx(), MTable.Table_Name, "c.AD_Reference_ID=? AND c.FKConstraintType=? AND AD_Table.IsView='N' AND c.ColumnName=?", trxName) + String whereClause = "c.AD_Reference_ID=? AND c.FKConstraintType=? AND AD_Table.IsView='N' AND c.ColumnName=?"; + if (! Util.isEmpty(whereTables)) + whereClause = whereClause + " AND (" + whereTables + ")"; + List listTables = new Query(Env.getCtx(), MTable.Table_Name, whereClause, trxName) .addJoinClause("JOIN AD_Column c ON (c.AD_Table_ID=AD_Table.AD_Table_ID)") .setOnlyActiveRecords(true) .setParameters(refId, constraintType, columnName) From 5994292dbb4eef2760d10eaec16f43eb91c711c2 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Wed, 29 May 2024 15:07:59 +0200 Subject: [PATCH 039/169] IDEMPIERE-6160 Remove Eclipse warnings (#2379) --- .../src/org/compiere/impexp/OFXBankStatementHandler.java | 8 ++++++-- .../org/compiere/impexp/OFXFileBankStatementLoader.java | 8 ++++++++ .../src/org/compiere/install/ConfigurationData.java | 7 +++++++ org.adempiere.ui/src/org/compiere/apps/form/PayPrint.java | 5 +++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/impexp/OFXBankStatementHandler.java b/org.adempiere.base/src/org/compiere/impexp/OFXBankStatementHandler.java index 0b8fd4a8f9..fa0c096fbd 100644 --- a/org.adempiere.base/src/org/compiere/impexp/OFXBankStatementHandler.java +++ b/org.adempiere.base/src/org/compiere/impexp/OFXBankStatementHandler.java @@ -245,7 +245,8 @@ public abstract class OFXBankStatementHandler extends DefaultHandler if (isOfx1) { - m_reader = new BufferedReader(new InputStreamReader(new OFX1ToXML(reader))); + OFX1ToXML in = new OFX1ToXML(reader); + m_reader = new BufferedReader(new InputStreamReader(in)); } else { @@ -257,9 +258,12 @@ public abstract class OFXBankStatementHandler extends DefaultHandler { m_errorMessage = new StringBuffer("ErrorReadingData"); m_errorDescription = new StringBuffer(e.getMessage()); - closeBufferedReader(); return result; } + finally + { + closeBufferedReader(); + } return result; } // attachInput diff --git a/org.adempiere.base/src/org/compiere/impexp/OFXFileBankStatementLoader.java b/org.adempiere.base/src/org/compiere/impexp/OFXFileBankStatementLoader.java index dc86eeb1f4..7b3daed703 100644 --- a/org.adempiere.base/src/org/compiere/impexp/OFXFileBankStatementLoader.java +++ b/org.adempiere.base/src/org/compiere/impexp/OFXFileBankStatementLoader.java @@ -17,6 +17,7 @@ package org.compiere.impexp; import java.io.FileInputStream; +import java.io.IOException; import org.compiere.model.MBankStatementLoader; import org.xml.sax.SAXException; @@ -70,6 +71,13 @@ public final class OFXFileBankStatementLoader extends OFXBankStatementHandler im m_errorMessage = new StringBuffer("ErrorReadingData"); m_errorDescription = new StringBuffer(); } + finally + { + if (m_stream != null) + try { + m_stream.close(); + } catch (IOException e) {} + } return result; } // init diff --git a/org.adempiere.install/src/org/compiere/install/ConfigurationData.java b/org.adempiere.install/src/org/compiere/install/ConfigurationData.java index 572f2ae8e2..b48f0f5720 100644 --- a/org.adempiere.install/src/org/compiere/install/ConfigurationData.java +++ b/org.adempiere.install/src/org/compiere/install/ConfigurationData.java @@ -831,6 +831,13 @@ public class ConfigurationData if (log.isLoggable(Level.FINE)) log.fine(host + ":" + port + " - " + e.getMessage()); return false; } + finally + { + if (pingSocket != null) + try { + pingSocket.close(); + } catch (IOException e) {} + } if (!shouldBeUsed) log.warning("Open Socket " + host + ":" + port + " - " + pingSocket); diff --git a/org.adempiere.ui/src/org/compiere/apps/form/PayPrint.java b/org.adempiere.ui/src/org/compiere/apps/form/PayPrint.java index 58aebdcee0..f11e640eaa 100644 --- a/org.adempiere.ui/src/org/compiere/apps/form/PayPrint.java +++ b/org.adempiere.ui/src/org/compiere/apps/form/PayPrint.java @@ -346,8 +346,9 @@ public class PayPrint { if (pdfFile != null) { // increase the check document no by the number of pages of the generated pdf file - PdfReader document = new PdfReader(pdfFile.getAbsolutePath()); - lastDocumentNo += document.getNumberOfPages(); + try (PdfReader document = new PdfReader(pdfFile.getAbsolutePath())) { + lastDocumentNo += document.getNumberOfPages(); + } pdfList.add(pdfFile); } } From 76adcb1f98b7da6691d271e017089d8f7ed47546 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 30 May 2024 12:25:49 +0200 Subject: [PATCH 040/169] Revert "IDEMPIERE-5923 Info Window should use key and display columns setting from lookup (#2108)" This reverts commit 6f484ee7013318d32c1278e618ecc22b2022958d. --- .../webui/factory/DefaultInfoFactory.java | 53 +++------ .../adempiere/webui/factory/IInfoFactory.java | 16 --- .../adempiere/webui/info/InfoAssetWindow.java | 21 ---- .../webui/info/InfoAssignmentWindow.java | 22 ---- .../webui/info/InfoBPartnerWindow.java | 21 ---- .../adempiere/webui/info/InfoInOutWindow.java | 22 ---- .../webui/info/InfoInvoiceWindow.java | 23 +--- .../adempiere/webui/info/InfoOrderWindow.java | 21 ---- .../webui/info/InfoPaymentWindow.java | 21 ---- .../webui/info/InfoProductWindow.java | 21 ---- .../org/adempiere/webui/info/InfoWindow.java | 106 +++++++++--------- 11 files changed, 65 insertions(+), 282 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultInfoFactory.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultInfoFactory.java index cb61611e36..62e1ac5672 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultInfoFactory.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultInfoFactory.java @@ -56,13 +56,6 @@ public class DefaultInfoFactory implements IInfoFactory { value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, null, null); } - @Override - public InfoPanel create(int WindowNo, String tableName, String keyColumn, String value, boolean multiSelection, - String whereClause, int AD_InfoWindow_ID, Lookup lookup) { - return create(WindowNo, tableName, keyColumn, - value, multiSelection, whereClause, AD_InfoWindow_ID, (lookup != null), null, null, lookup); - } - @Override public InfoPanel create(int WindowNo, String tableName, String keyColumn, String value, boolean multiSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field) { @@ -83,40 +76,20 @@ public class DefaultInfoFactory implements IInfoFactory { * @param predefinedContextVariables * @param field * @return InfoPanel - */ + */ public InfoPanel create(int WindowNo, String tableName, String keyColumn, String value, boolean multiSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, String predefinedContextVariables, GridField field) { - return create(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, predefinedContextVariables, field, - (field != null ? field.getLookup() : null)); - } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param value - * @param multiSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param predefinedContextVariables - * @param field - * @param lookupModel - * @return InfoPanel - */ - public InfoPanel create(int WindowNo, String tableName, String keyColumn, - String value, boolean multiSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, String predefinedContextVariables, GridField field, Lookup lookupModel) { InfoPanel info = null; setSOTrxBasedOnDocType(WindowNo); if (tableName.equals("C_BPartner")) { - info = new InfoBPartnerWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoBPartnerWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoBPartnerPanel (value,WindowNo, !Env.getContext(Env.getCtx(), WindowNo, "IsSOTrx").equals("N"), multiSelection, whereClause, lookup); } } else if (tableName.equals("M_Product")) { - info = new InfoProductWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoProductWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoProductPanel ( WindowNo, Env.getContextAsInt(Env.getCtx(), WindowNo, "M_Warehouse_ID"), @@ -124,31 +97,31 @@ public class DefaultInfoFactory implements IInfoFactory { multiSelection, value,whereClause, lookup); } } else if (tableName.equals("C_Invoice")) { - info = new InfoInvoiceWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoInvoiceWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoInvoicePanel ( WindowNo, value, multiSelection, whereClause, lookup); } } else if (tableName.equals("A_Asset")) { - info = new InfoAssetWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoAssetWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoAssetPanel (WindowNo, 0, value, multiSelection, whereClause, lookup); } } else if (tableName.equals("C_Order")) { - info = new InfoOrderWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoOrderWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoOrderPanel ( WindowNo, value, multiSelection, whereClause, lookup); } } else if (tableName.equals("M_InOut")) { - info = new InfoInOutWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoInOutWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoInOutPanel (WindowNo, value, multiSelection, whereClause, lookup); } } else if (tableName.equals("C_Payment")) { - info = new InfoPaymentWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoPaymentWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoPaymentPanel (WindowNo, value, multiSelection, whereClause, lookup); } @@ -156,13 +129,13 @@ public class DefaultInfoFactory implements IInfoFactory { info = new InfoCashLinePanel (WindowNo, value, multiSelection, whereClause, lookup); } else if (tableName.equals("S_ResourceAssignment")) { - info = new InfoAssignmentWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoAssignmentWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoAssignmentPanel (WindowNo, value, multiSelection, whereClause, lookup); } } else { - info = new InfoWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoGeneralPanel (value, WindowNo, tableName, keyColumn, @@ -191,7 +164,7 @@ public class DefaultInfoFactory implements IInfoFactory { if (col.equals("M_Product_ID")) { - InfoWindow infoWindow = new InfoProductWindow(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field, null, lookup); + InfoWindow infoWindow = new InfoProductWindow(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field); if (infoWindow.loadedOK()) return infoWindow; @@ -222,7 +195,7 @@ public class DefaultInfoFactory implements IInfoFactory { String tempIsSOTrx = ("Y".equals(originalIsSOTrx) ? "N" : "Y"); Env.setContext(Env.getCtx(), lookup.getWindowNo(), "IsSOTrx", tempIsSOTrx); } - InfoWindow infoWindow = new InfoBPartnerWindow(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field, null, lookup); + InfoWindow infoWindow = new InfoBPartnerWindow(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field); if (infoWindow.loadedOK()) return infoWindow; } finally { @@ -240,7 +213,7 @@ public class DefaultInfoFactory implements IInfoFactory { } else // General Info { - info = create(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, (String)null, field, lookup); + info = create(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field); } return info; } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/IInfoFactory.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/IInfoFactory.java index eaf4fbb124..c3b7433a68 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/IInfoFactory.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/IInfoFactory.java @@ -39,22 +39,6 @@ public interface IInfoFactory { public InfoPanel create (int WindowNo, String tableName, String keyColumn, String value, boolean multiSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup); - - /** - * - * @param WindowNo - * @param tableName - * @param keyColumn - * @param value - * @param multiSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @return {@link InfoPanel} - */ - public InfoPanel create (int WindowNo, - String tableName, String keyColumn, String value, - boolean multiSelection, String whereClause, int AD_InfoWindow_ID, Lookup lookup); /** * diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssetWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssetWindow.java index f4f3591760..fb9554823c 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssetWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssetWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; import org.compiere.model.MAsset; import org.compiere.util.Env; @@ -92,26 +91,6 @@ public class InfoAssetWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoAssetWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - @Override protected void saveSelectionDetail() { int row = contentPanel.getSelectedRow(); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssignmentWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssignmentWindow.java index 92288d542f..afc83d8890 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssignmentWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssignmentWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; /** * Info window for S_ResourceAssignment @@ -90,25 +89,4 @@ public class InfoAssignmentWindow extends InfoWindow { super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoAssignmentWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoBPartnerWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoBPartnerWindow.java index 2d2c9d1d3d..52b3ffb4f0 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoBPartnerWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoBPartnerWindow.java @@ -28,7 +28,6 @@ import java.util.logging.Level; import org.adempiere.webui.panel.InvoiceHistory; import org.compiere.model.GridField; -import org.compiere.model.Lookup; import org.compiere.model.MBPartner; import org.compiere.util.Env; @@ -96,26 +95,6 @@ public class InfoBPartnerWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoBPartnerWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - /** * Has History * @return true diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInOutWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInOutWindow.java index 79d055ebc8..e301f49d75 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInOutWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInOutWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; /** * Info window for M_InOut @@ -89,25 +88,4 @@ public class InfoInOutWindow extends InfoWindow { super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoInOutWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInvoiceWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInvoiceWindow.java index 2d5fc8b108..d15d43ec08 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInvoiceWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInvoiceWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; import org.compiere.model.MInvoice; import org.compiere.util.Env; @@ -91,27 +90,7 @@ public class InfoInvoiceWindow extends InfoWindow { super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoInvoiceWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - + @Override protected void saveSelectionDetail() { int row = contentPanel.getSelectedRow(); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoOrderWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoOrderWindow.java index cd1ca8282e..4276ce61c6 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoOrderWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoOrderWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; /** * Info window for C_Order @@ -90,24 +89,4 @@ public class InfoOrderWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoOrderWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoPaymentWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoPaymentWindow.java index 70263e43eb..a5141d89e1 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoPaymentWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoPaymentWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; /** * Info window for C_Payment @@ -90,24 +89,4 @@ public class InfoPaymentWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoPaymentWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoProductWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoProductWindow.java index 19f4fae4df..7ef815a03d 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoProductWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoProductWindow.java @@ -55,7 +55,6 @@ import org.adempiere.webui.util.ZKUpdateUtil; import org.compiere.minigrid.ColumnInfo; import org.compiere.minigrid.EmbedWinInfo; import org.compiere.model.GridField; -import org.compiere.model.Lookup; import org.compiere.model.MDocType; import org.compiere.model.MInfoWindow; import org.compiere.model.MProduct; @@ -183,26 +182,6 @@ public class InfoProductWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoProductWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - @Override protected String getSQLWhere() { /** diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java index 79410e7273..c0a40f04ea 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Consumer; import java.util.Properties; import java.util.TreeMap; import java.util.logging.Level; @@ -224,10 +225,6 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL /** true to auto collapse parameter panel after execution of query */ private boolean autoCollapsedParameterPanel = false; - - protected Lookup lookupModel = null; - - private ArrayList lookupIdentifiers; /** * @param WindowNo @@ -288,25 +285,6 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL */ public InfoWindow(int WindowNo, String tableName, String keyColumn, String queryValue, boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, String predefinedContextVariables) { - this(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, - (field != null ? field.getLookup() : null)); - } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, String predefinedContextVariables, Lookup lookupModel) { super(WindowNo, tableName, keyColumn, multipleSelection, whereClause, lookup, AD_InfoWindow_ID, queryValue); this.m_gridfield = field; @@ -337,14 +315,6 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL Env.setPredefinedVariables(Env.getCtx(), getWindowNo(), predefinedContextVariables); infoContext = new Properties(Env.getCtx()); - if (lookupModel != null) { - this.lookupModel = lookupModel; - if (lookupModel instanceof MLookup mLookup) { - if (mLookup.getLookupInfo().lookupDisplayColumnNames != null && mLookup.getLookupInfo().lookupDisplayColumnNames.size() > 0) { - this.lookupIdentifiers = new ArrayList(mLookup.getLookupInfo().lookupDisplayColumnNames); - } - } - } p_loadedOK = loadInfoDefinition(); // make process button only in window mode @@ -743,12 +713,57 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL } protected void testQueryForSplit(String [] values) { - // do fill value to editor - for(int i = 0; i < values.length && i < identifiers.size(); i++) { - WEditor editor = identifiers.get(i); - editor.setValue(values[i].trim()); + // store identifiers on info window, sort to follow identifier on m_table + List fillIdentifiers = new ArrayList<>(); + // store query value, ignore value for identifier not exists on info window + // this list is sync with fillIdentifiers (size and order) + List fillValues = new ArrayList<>(); + + List tableIdentifiers = null; + if (m_gridfield != null && m_gridfield.getLookup() != null + && m_gridfield.getLookup() instanceof MLookup) { + + MLookup mLookup = (MLookup)m_gridfield.getLookup(); + if (mLookup.getLookupInfo().lookupDisplayColumnNames.size() > 0) + tableIdentifiers = mLookup.getLookupInfo().lookupDisplayColumnNames; + } + + if (tableIdentifiers != null) { + for (int i = 0; i < tableIdentifiers.size(); i++) { + // final local variable to access inside lambda expression + int indexFinal = i; + List tableIdentifiersFinal = tableIdentifiers; + + // sort identifiers of info window to follow m_table + // ignore identifiers exists on m_table but not exists on info window + identifiers.forEach((Consumer)(identifierEditor) -> { + if (identifierEditor.getColumnName().equals(tableIdentifiersFinal.get(indexFinal))) { + fillIdentifiers.add(identifierEditor); + fillValues.add(values[indexFinal]); + } + }); + } + } + + // case not exists mLookup.getLookupInfo().lookupDisplayColumnNames + // or no identifiers on info window exists on m_table + // fall back to old logic and just set values to identifiers + if (fillIdentifiers.size() == 0) { + for(int i = 0; i < values.length && i < identifiers.size(); i++) { + fillIdentifiers.add(identifiers.get(i)); + fillValues.add(values[i]); + } + } + + + + // do fill value to editor (for both corrected order and fall back) + for(int i = 0; i < fillIdentifiers.size(); i++) { + WEditor editor = fillIdentifiers.get(i); + editor.setValue(fillValues.get(i).trim()); } testCount(false); + } @Override @@ -1888,20 +1903,6 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL if (!isAutoComplete) dynamicDisplay(null); - - //if using lookupIdentifiers, sort identifiers in the order of lookupIdentifiers - if (lookupIdentifiers != null && lookupIdentifiers.size() > 0 && identifiers.size() > 0) { - List list = new ArrayList(); - for(String columnName : lookupIdentifiers) { - for(WEditor editor : identifiers) { - if (columnName.equals(editor.getColumnName())) { - list.add(editor); - break; - } - } - } - identifiers = list; - } } /** @@ -2020,12 +2021,7 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL editor.showMenu(); - //if MLookup is available, use display columns of MLookup instead of InfoColumn's IsIdentifier flag - if (lookupIdentifiers != null && lookupIdentifiers.size() > 0) { - if (lookupIdentifiers.contains(infoColumn.getColumnName()) ) { - identifiers.add(editor); - } - } else if (infoColumn.isIdentifier()) { + if (infoColumn.isIdentifier()) { identifiers.add(editor); } From 9d6f8c852f7c93cc71817e0c52584bfcbcf6292a Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 31 May 2024 10:05:44 +0200 Subject: [PATCH 041/169] IDEMPIERE-6162 Error with Create PO from Requisition for different BPs on multiple lines with same product (#2383) --- .../src/org/compiere/process/RequisitionPOCreate.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.adempiere.base.process/src/org/compiere/process/RequisitionPOCreate.java b/org.adempiere.base.process/src/org/compiere/process/RequisitionPOCreate.java index 9d2c758c24..33e0926730 100644 --- a/org.adempiere.base.process/src/org/compiere/process/RequisitionPOCreate.java +++ b/org.adempiere.base.process/src/org/compiere/process/RequisitionPOCreate.java @@ -304,6 +304,7 @@ public class RequisitionPOCreate extends SvrProcess || rLine.getM_AttributeSetInstance_ID() != m_M_AttributeSetInstance_ID || rLine.getC_Charge_ID() != 0 // single line per charge || m_order == null + || (rLine.getC_BPartner_ID() > 0 && m_order.getC_BPartner_ID() != rLine.getC_BPartner_ID()) || m_order.getDatePromised().compareTo(rLine.getDateRequired()) != 0 ) { From cad01851d5308a3b8570230ee737a87c6c018823 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 31 May 2024 10:28:29 +0200 Subject: [PATCH 042/169] IDEMPIERE-6159 Add drop ship location to ITaxLookup (#2380) --- .../src/org/adempiere/model/CalloutRMA.java | 9 +- .../org/compiere/model/CalloutInvoice.java | 35 +++- .../compiere/model/CalloutInvoiceBatch.java | 44 ++++- .../src/org/compiere/model/CalloutOrder.java | 3 +- .../org/adempiere/base/DefaultTaxLookup.java | 18 +- .../src/org/adempiere/base/ITaxLookup.java | 60 +++++++ .../src/org/compiere/model/MInvoiceLine.java | 7 +- .../src/org/compiere/model/MOrderLine.java | 2 +- .../src/org/compiere/model/MRMALine.java | 8 +- .../src/org/compiere/model/Tax.java | 161 +++++++++++++----- org.idempiere.test/META-INF/MANIFEST.MF | 2 +- ...dempiere.test.model.MTaxTest_TaxLookup.xml | 7 + .../src/org/idempiere/test/DictionaryIDs.java | 34 +++- .../org/idempiere/test/model/MTaxTest.java | 127 ++++++++++++-- .../test/model/MTaxTest_TaxLookup.java | 53 ++++++ 15 files changed, 496 insertions(+), 74 deletions(-) create mode 100644 org.idempiere.test/OSGI-INF/org.idempiere.test.model.MTaxTest_TaxLookup.xml create mode 100644 org.idempiere.test/src/org/idempiere/test/model/MTaxTest_TaxLookup.java diff --git a/org.adempiere.base.callout/src/org/adempiere/model/CalloutRMA.java b/org.adempiere.base.callout/src/org/adempiere/model/CalloutRMA.java index d71138b047..338762ac5a 100644 --- a/org.adempiere.base.callout/src/org/adempiere/model/CalloutRMA.java +++ b/org.adempiere.base.callout/src/org/adempiere/model/CalloutRMA.java @@ -156,6 +156,11 @@ public class CalloutRMA extends CalloutEngine { MInvoice invoice = rma.getOriginalInvoice(); if (invoice != null) { + int dropshipLocationId = -1; + MOrder order = invoice.getOriginalOrder(); + if (order != null) + dropshipLocationId = order.getDropShip_Location_ID(); + pp.setM_PriceList_ID(invoice.getM_PriceList_ID()); pp.setPriceDate(invoice.getDateInvoiced()); @@ -168,7 +173,7 @@ public class CalloutRMA extends CalloutEngine { invoice.getDateInvoiced(), invoice.getDateInvoiced(), AD_Org_ID, rma.getShipment().getM_Warehouse_ID(), invoice.getC_BPartner_Location_ID(), // should be bill to - invoice.getC_BPartner_Location_ID(), rma.isSOTrx(), deliveryViaRule, null); + invoice.getC_BPartner_Location_ID(), dropshipLocationId, rma.isSOTrx(), deliveryViaRule, null); } else { @@ -183,7 +188,7 @@ public class CalloutRMA extends CalloutEngine { order.getDateOrdered(), order.getDateOrdered(), AD_Org_ID, order.getM_Warehouse_ID(), order.getC_BPartner_Location_ID(), // should be bill to - order.getC_BPartner_Location_ID(), rma.isSOTrx(), order.getDeliveryViaRule(), null); + order.getC_BPartner_Location_ID(), order.getDropShip_Location_ID(), rma.isSOTrx(), order.getDeliveryViaRule(), null); } else return "No Invoice/Order found the Shipment/Receipt associated"; diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java index e2db5081a5..f4b8ae324d 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java @@ -488,8 +488,9 @@ public class CalloutInvoice extends CalloutEngine // String deliveryViaRule = getLineDeliveryViaRule(ctx, WindowNo, mTab); + int dropshipLocationId = getDropShipLocationId(ctx, WindowNo, mTab); int C_Tax_ID = Core.getTaxLookup().get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, - AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipLocationId, Env.getContext(ctx, WindowNo, "IsSOTrx").equals("Y"), deliveryViaRule, null); if (log.isLoggable(Level.INFO)) log.info("Tax ID=" + C_Tax_ID); // @@ -501,6 +502,13 @@ public class CalloutInvoice extends CalloutEngine return amt (ctx, WindowNo, mTab, mField, value); } // tax + /** + * Get the delivery via rule from the related order + * @param ctx + * @param windowNo + * @param mTab + * @return + */ private String getLineDeliveryViaRule(Properties ctx, int windowNo, GridTab mTab) { if (mTab.getValue("C_OrderLine_ID") != null) { int C_OrderLine_ID = (Integer) mTab.getValue("C_OrderLine_ID"); @@ -523,7 +531,30 @@ public class CalloutInvoice extends CalloutEngine } return null; } - + + /** + * Get the drop shipment location ID from the related order + * @param ctx + * @param windowNo + * @param mTab + * @return + */ + private int getDropShipLocationId(Properties ctx, int windowNo, GridTab mTab) { + if (mTab.getValue("C_OrderLine_ID") != null) { + int C_OrderLine_ID = (Integer) mTab.getValue("C_OrderLine_ID"); + if (C_OrderLine_ID > 0) { + MOrderLine orderLine = new MOrderLine(ctx, C_OrderLine_ID, null); + return orderLine.getParent().getDropShip_Location_ID(); + } + } + int C_Order_ID = Env.getContextAsInt(ctx, windowNo, "C_Order_ID", true); + if (C_Order_ID > 0) { + MOrder order = new MOrder(ctx, C_Order_ID, null); + return order.getDropShip_Location_ID(); + } + return -1; + } + /** * Invoice - Amount. * - called from QtyInvoiced, PriceActual diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoiceBatch.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoiceBatch.java index cee2fe3d5c..52e367199e 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoiceBatch.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoiceBatch.java @@ -319,8 +319,9 @@ public class CalloutInvoiceBatch extends CalloutEngine // String deliveryViaRule = getLineDeliveryViaRule(ctx, WindowNo, mTab); + int dropshipLocationId = getDropShipLocationId(ctx, WindowNo, mTab); int C_Tax_ID = Core.getTaxLookup().get(ctx, 0, C_Charge_ID, billDate, shipDate, - AD_Org_ID, M_Warehouse_ID, C_BPartner_Location_ID, C_BPartner_Location_ID, + AD_Org_ID, M_Warehouse_ID, C_BPartner_Location_ID, C_BPartner_Location_ID, dropshipLocationId, Env.getContext(ctx, WindowNo, "IsSOTrx").equals("Y"), deliveryViaRule, null); if (log.isLoggable(Level.INFO)) log.info("Tax ID=" + C_Tax_ID); // @@ -332,6 +333,13 @@ public class CalloutInvoiceBatch extends CalloutEngine return amt (ctx, WindowNo, mTab, mField, value); } // tax + /** + * Get the drop shipment location ID from the related order + * @param ctx + * @param windowNo + * @param mTab + * @return + */ private String getLineDeliveryViaRule(Properties ctx, int windowNo, GridTab mTab) { if (mTab.getValue("C_InvoiceLine_ID") != null) { int C_InvoiceLine_ID = (Integer) mTab.getValue("C_InvoiceLine_ID"); @@ -361,7 +369,39 @@ public class CalloutInvoiceBatch extends CalloutEngine } return null; } - + + /** + * Get the drop shipment location ID from the related order + * @param ctx + * @param windowNo + * @param mTab + * @return + */ + private int getDropShipLocationId(Properties ctx, int windowNo, GridTab mTab) { + if (mTab.getValue("C_InvoiceLine_ID") != null) { + int C_InvoiceLine_ID = (Integer) mTab.getValue("C_InvoiceLine_ID"); + if (C_InvoiceLine_ID > 0) { + MInvoiceLine invoiceLine = new MInvoiceLine(ctx, C_InvoiceLine_ID, null); + int C_OrderLine_ID = invoiceLine.getC_OrderLine_ID(); + if (C_OrderLine_ID > 0) { + MOrderLine orderLine = new MOrderLine(ctx, C_OrderLine_ID, null); + return orderLine.getParent().getDropShip_Location_ID(); + } + } + } + if (mTab.getValue("C_Invoice_ID") != null) { + int C_Invoice_ID = (Integer) mTab.getValue("C_Invoice_ID"); + if (C_Invoice_ID > 0) { + MInvoice invoice = new MInvoice(ctx, C_Invoice_ID, null); + I_C_Order order = invoice.getC_Order(); + if (order != null) { + return order.getDropShip_Location_ID(); + } + } + } + return -1; + } + /** * Invoice - Amount. * - called from QtyEntered, PriceEntered diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java index 43856064c1..2df87e1ee1 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java @@ -972,8 +972,9 @@ public class CalloutOrder extends CalloutEngine // String deliveryViaRule = Env.getContext(ctx, WindowNo, I_C_Order.COLUMNNAME_DeliveryViaRule, true); + int dropshipLocationId = Env.getContextAsInt(ctx, WindowNo, I_C_Order.COLUMNNAME_DropShip_Location_ID, true); int C_Tax_ID = Core.getTaxLookup().get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, - AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipLocationId, "Y".equals(Env.getContext(ctx, WindowNo, "IsSOTrx")), deliveryViaRule, null); if (log.isLoggable(Level.INFO)) log.info("Tax ID=" + C_Tax_ID); // diff --git a/org.adempiere.base/src/org/adempiere/base/DefaultTaxLookup.java b/org.adempiere.base/src/org/adempiere/base/DefaultTaxLookup.java index 997083ae95..93b38eb052 100644 --- a/org.adempiere.base/src/org/adempiere/base/DefaultTaxLookup.java +++ b/org.adempiere.base/src/org/adempiere/base/DefaultTaxLookup.java @@ -51,10 +51,19 @@ public class DefaultTaxLookup implements ITaxLookup { public int get(Properties ctx, int M_Product_ID, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName) { - return Tax.get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + return Tax.get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); } + @Override + public int get(Properties ctx, int M_Product_ID, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, + int AD_Org_ID, int M_Warehouse_ID, int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int dropshipC_BPartner_Location_ID, + boolean IsSOTrx, String deliveryViaRule, String trxName) { + return Tax.get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + dropshipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); + } + @Override public int get(Properties ctx, int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, @@ -62,4 +71,11 @@ public class DefaultTaxLookup implements ITaxLookup { return Tax.get(ctx, C_TaxCategory_ID, IsSOTrx, shipDate, shipFromC_Location_ID, shipToC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); } + @Override + public int get(Properties ctx, int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, + int shipToC_Location_ID, int dropshipC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, + String trxName) { + return Tax.get(ctx, C_TaxCategory_ID, IsSOTrx, shipDate, shipFromC_Location_ID, shipToC_Location_ID, dropshipC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); + } + } diff --git a/org.adempiere.base/src/org/adempiere/base/ITaxLookup.java b/org.adempiere.base/src/org/adempiere/base/ITaxLookup.java index 7861059f86..6d014aa019 100644 --- a/org.adempiere.base/src/org/adempiere/base/ITaxLookup.java +++ b/org.adempiere.base/src/org/adempiere/base/ITaxLookup.java @@ -55,6 +55,37 @@ public interface ITaxLookup { int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName); + /** + * Find C_Tax_ID by Product/Charge + Warehouse Location + BPartner Location + DeliveryViaRule + * @param ctx + * @param M_Product_ID + * @param C_Charge_ID + * @param billDate Billing Date + * @param shipDate Shipment Date + * @param AD_Org_ID + * @param M_Warehouse_ID + * @param billC_BPartner_Location_ID Bill to location + * @param shipC_BPartner_Location_ID Ship to location + * @param dropshipC_BPartner_Location_ID Drop Ship to location (ignored if not implemented) + * @param IsSOTrx + * @param deliveryViaRule Order/Invoice's Delivery Via Rule + * @param trxName + * @return C_Tax_ID + */ + public default int get (Properties ctx, int M_Product_ID, int C_Charge_ID, + Timestamp billDate, Timestamp shipDate, + int AD_Org_ID, int M_Warehouse_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int dropshipC_BPartner_Location_ID, + boolean IsSOTrx, String deliveryViaRule, String trxName) { + // fallback to default method without dropshipC_BPartner_Location_ID if not implemented + return get(ctx, M_Product_ID, C_Charge_ID, + billDate, shipDate, + AD_Org_ID, M_Warehouse_ID, + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + IsSOTrx, deliveryViaRule, trxName); + } + /** * Find C_Tax_ID * @param ctx @@ -73,4 +104,33 @@ public interface ITaxLookup { int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, String trxName); + + /** + * Find C_Tax_ID + * @param ctx + * @param C_TaxCategory_ID + * @param IsSOTrx + * @param shipDate Shipment Date + * @param shipFromC_Location_ID Shipping from (not use in default lookup implementation) + * @param shipToC_Location_ID Shipping to (not use in default lookup implementation) + * @param dropshipC_Location_ID Drop Ship location + * @param billDate Billing Date + * @param billFromC_Location_ID Billing from (Tax Location from) + * @param billToC_Location_ID Billing to (Tax Location to) + * @param deliveryRule Order/Invoice's Delivery Via Rule + * @param trxName + * @return C_Tax_ID + */ + public default int get (Properties ctx, + int C_TaxCategory_ID, boolean IsSOTrx, + Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, + int dropshipC_Location_ID, + Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, String trxName) { + // fallback to default method without dropshipC_BPartner_Location_ID if not implemented + return get(ctx, + C_TaxCategory_ID, IsSOTrx, + shipDate, shipFromC_Location_ID, shipToC_Location_ID, + billDate, billFromC_Location_ID, billToC_Location_ID, trxName); + } + } diff --git a/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java b/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java index 3bd339d7d2..16c5c758e8 100644 --- a/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java +++ b/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java @@ -500,8 +500,11 @@ public class MInvoiceLine extends X_C_InvoiceLine int M_Warehouse_ID = Env.getContextAsInt(getCtx(), Env.M_WAREHOUSE_ID); // String deliveryViaRule = null; + int dropShipLocationId = -1; if (getC_OrderLine_ID() > 0) { - deliveryViaRule = new MOrderLine(getCtx(), getC_OrderLine_ID(), get_TrxName()).getParent().getDeliveryViaRule(); + MOrder order = new MOrderLine(getCtx(), getC_OrderLine_ID(), get_TrxName()).getParent(); + deliveryViaRule = order.getDeliveryViaRule(); + dropShipLocationId = order.getDropShip_Location_ID(); } else if (getM_InOutLine_ID() > 0) { deliveryViaRule = new MInOutLine(getCtx(), getM_InOutLine_ID(), get_TrxName()).getParent().getDeliveryViaRule(); } else if (getParent().getC_Order_ID() > 0) { @@ -510,7 +513,7 @@ public class MInvoiceLine extends X_C_InvoiceLine int C_Tax_ID = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID() , m_DateInvoiced, m_DateInvoiced, getAD_Org_ID(), M_Warehouse_ID, m_C_BPartner_Location_ID, // should be bill to - m_C_BPartner_Location_ID, m_IsSOTrx, deliveryViaRule, get_TrxName()); + m_C_BPartner_Location_ID, dropShipLocationId, m_IsSOTrx, deliveryViaRule, get_TrxName()); if (C_Tax_ID == 0) { log.log(Level.SEVERE, "No Tax found"); diff --git a/org.adempiere.base/src/org/compiere/model/MOrderLine.java b/org.adempiere.base/src/org/compiere/model/MOrderLine.java index bbdc0946df..0213e477d1 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrderLine.java +++ b/org.adempiere.base/src/org/compiere/model/MOrderLine.java @@ -348,7 +348,7 @@ public class MOrderLine extends X_C_OrderLine int ii = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID(), getDateOrdered(), getDateOrdered(), getAD_Org_ID(), getM_Warehouse_ID(), getC_BPartner_Location_ID(), // should be bill to - getC_BPartner_Location_ID(), m_IsSOTrx, getParent().getDeliveryViaRule(), get_TrxName()); + getC_BPartner_Location_ID(), getParent().getDropShip_Location_ID(), m_IsSOTrx, getParent().getDeliveryViaRule(), get_TrxName()); if (ii == 0) { log.log(Level.SEVERE, "No Tax found"); diff --git a/org.adempiere.base/src/org/compiere/model/MRMALine.java b/org.adempiere.base/src/org/compiere/model/MRMALine.java index 3d6295e213..0a89a4bb5d 100644 --- a/org.adempiere.base/src/org/compiere/model/MRMALine.java +++ b/org.adempiere.base/src/org/compiere/model/MRMALine.java @@ -181,6 +181,10 @@ public class MRMALine extends X_M_RMALine MInvoice invoice = getParent().getOriginalInvoice(); if (invoice != null) { + int dropshipLocationId = -1; + MOrder order = invoice.getOriginalOrder(); + if (order != null) + dropshipLocationId = order.getDropShip_Location_ID(); pp.setM_PriceList_ID(invoice.getM_PriceList_ID()); pp.setPriceDate(invoice.getDateInvoiced()); @@ -192,7 +196,7 @@ public class MRMALine extends X_M_RMALine taxId = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID(), invoice.getDateInvoiced(), invoice.getDateInvoiced(), getAD_Org_ID(), getParent().getShipment().getM_Warehouse_ID(), invoice.getC_BPartner_Location_ID(), // should be bill to - invoice.getC_BPartner_Location_ID(), getParent().isSOTrx(), deliveryViaRule, get_TrxName()); + invoice.getC_BPartner_Location_ID(), dropshipLocationId, getParent().isSOTrx(), deliveryViaRule, get_TrxName()); } else { @@ -206,7 +210,7 @@ public class MRMALine extends X_M_RMALine taxId = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID(), order.getDateOrdered(), order.getDateOrdered(), getAD_Org_ID(), order.getM_Warehouse_ID(), order.getC_BPartner_Location_ID(), // should be bill to - order.getC_BPartner_Location_ID(), getParent().isSOTrx(), order.getDeliveryViaRule(), get_TrxName()); + order.getC_BPartner_Location_ID(), order.getDropShip_Location_ID(), getParent().isSOTrx(), order.getDeliveryViaRule(), get_TrxName()); } else throw new IllegalStateException("No Invoice/Order found the Shipment/Receipt associated"); diff --git a/org.adempiere.base/src/org/compiere/model/Tax.java b/org.adempiere.base/src/org/compiere/model/Tax.java index 77127e2d87..876b0412aa 100644 --- a/org.adempiere.base/src/org/compiere/model/Tax.java +++ b/org.adempiere.base/src/org/compiere/model/Tax.java @@ -68,7 +68,7 @@ public class Tax int AD_Org_ID, int M_Warehouse_ID, int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, boolean IsSOTrx) { - return get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, null); + return get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, -1, IsSOTrx, null); } @@ -93,6 +93,7 @@ public class Tax * @param M_Warehouse_ID warehouse (ignored) * @param billC_BPartner_Location_ID invoice location * @param shipC_BPartner_Location_ID ship location (ignored) + * @param dropshipC_BPartner_Location_ID ship location (ignored) * @param IsSOTrx is a sales trx * @param trxName * @return C_Tax_ID @@ -101,11 +102,11 @@ public class Tax public static int get (Properties ctx, int M_Product_ID, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, - int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, int dropshipC_BPartner_Location_ID, boolean IsSOTrx, String trxName) { return get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, null, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipC_BPartner_Location_ID, IsSOTrx, null, trxName); } /************************************************************************** @@ -135,18 +136,59 @@ public class Tax * @return C_Tax_ID * @throws TaxCriteriaNotFoundException if a criteria was not found */ + public static int get (Properties ctx, int M_Product_ID, int C_Charge_ID, + Timestamp billDate, Timestamp shipDate, + int AD_Org_ID, int M_Warehouse_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + boolean IsSOTrx, String deliveryViaRule, String trxName) + { + return get(ctx, M_Product_ID, C_Charge_ID, + billDate, shipDate, + AD_Org_ID, M_Warehouse_ID, + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, -1, + IsSOTrx, deliveryViaRule, trxName); + } + + /************************************************************************** + * Get Tax ID - converts parameters to call Get Tax. + *
{@code
+	 *		M_Product_ID/C_Charge_ID	->	C_TaxCategory_ID
+	 *		billDate, shipDate			->	billDate, shipDate
+	 *		AD_Org_ID					->	billFromC_Location_ID
+	 *		M_Warehouse_ID				->	shipFromC_Location_ID
+	 *		billC_BPartner_Location_ID  ->	billToC_Location_ID
+	 *		shipC_BPartner_Location_ID 	->	shipToC_Location_ID
+	 *
+	 *  if IsSOTrx is false, bill and ship are reversed
+	 *  }
+ * @param ctx context + * @param M_Product_ID product + * @param C_Charge_ID product + * @param billDate invoice date + * @param shipDate ship date (ignored) + * @param AD_Org_ID org + * @param M_Warehouse_ID warehouse (ignored) + * @param billC_BPartner_Location_ID invoice location + * @param shipC_BPartner_Location_ID ship location (ignored) + * @param dropshipC_BPartner_Location_ID dropship location + * @param IsSOTrx is a sales trx + * @param deliveryViaRule if Delivery Via Rule is PickUp, use Warehouse Location instead of Billing Location as Tax Location to + * @param trxName + * @return C_Tax_ID + * @throws TaxCriteriaNotFoundException if a criteria was not found + */ public static int get (Properties ctx, int M_Product_ID, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, - int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, int dropshipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName) { if (M_Product_ID != 0) return getProduct (ctx, M_Product_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); else if (C_Charge_ID != 0) return getCharge (ctx, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); else return getExemptTax (ctx, AD_Org_ID, trxName); } // get @@ -207,7 +249,7 @@ public class Tax boolean IsSOTrx, String trxName) { return getCharge(ctx, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, null, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, -1, IsSOTrx, null, trxName); } /** @@ -231,6 +273,7 @@ public class Tax * @param M_Warehouse_ID warehouse (ignored) * @param billC_BPartner_Location_ID invoice location * @param shipC_BPartner_Location_ID ship location (ignored) + * @param dropshipC_BPartner_Location_ID * @param IsSOTrx is a sales trx * @param deliveryViaRule if Delivery Via Rule is PickUp, use Warehouse Location instead of Billing Location as Tax Location to * @param trxName @@ -241,12 +284,13 @@ public class Tax public static int getCharge (Properties ctx, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, - int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, int dropshipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName) { int C_TaxCategory_ID = 0; int shipFromC_Location_ID = 0; int shipToC_Location_ID = 0; + int dropshipC_Location_ID = 0; int billFromC_Location_ID = 0; int billToC_Location_ID = 0; int warehouseC_Location_ID = 0; @@ -256,24 +300,26 @@ public class Tax // Get all at once String sql = "SELECT c.C_TaxCategory_ID, o.C_Location_ID, il.C_Location_ID, b.IsTaxExempt, b.IsPOTaxExempt," - + " w.C_Location_ID, sl.C_Location_ID " - + "FROM C_Charge c, AD_OrgInfo o," - + " C_BPartner_Location il INNER JOIN C_BPartner b ON (il.C_BPartner_ID=b.C_BPartner_ID) " - + " LEFT OUTER JOIN M_Warehouse w ON (w.M_Warehouse_ID=?), C_BPartner_Location sl " - + "WHERE c.C_Charge_ID=?" - + " AND o.AD_Org_ID=?" - + " AND il.C_BPartner_Location_ID=?" - + " AND sl.C_BPartner_Location_ID=?"; + + " w.C_Location_ID, sl.C_Location_ID, dsl.C_Location_ID " + + "FROM C_Charge c" + + " JOIN AD_OrgInfo o ON (o.AD_Org_ID=?)" + + " JOIN C_BPartner_Location il ON (il.C_BPartner_Location_ID=?)" + + " INNER JOIN C_BPartner b ON (il.C_BPartner_ID=b.C_BPartner_ID) " + + " LEFT OUTER JOIN M_Warehouse w ON (w.M_Warehouse_ID=?)" + + " JOIN C_BPartner_Location sl ON (sl.C_BPartner_Location_ID=?)" + + " LEFT JOIN C_BPartner_Location dsl ON (dsl.C_BPartner_Location_ID=?)" + + "WHERE c.C_Charge_ID=?"; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement (sql, trxName); - pstmt.setInt (1, M_Warehouse_ID); - pstmt.setInt (2, C_Charge_ID); - pstmt.setInt (3, AD_Org_ID); - pstmt.setInt (4, billC_BPartner_Location_ID); - pstmt.setInt (5, shipC_BPartner_Location_ID); + pstmt.setInt (1, AD_Org_ID); + pstmt.setInt (2, billC_BPartner_Location_ID); + pstmt.setInt (3, M_Warehouse_ID); + pstmt.setInt (4, shipC_BPartner_Location_ID); + pstmt.setInt (5, dropshipC_BPartner_Location_ID); + pstmt.setInt (6, C_Charge_ID); rs = pstmt.executeQuery (); boolean found = false; if (rs.next ()) @@ -286,6 +332,7 @@ public class Tax IsTaxExempt = IsSOTrx ? IsSOTaxExempt : IsPOTaxExempt; shipFromC_Location_ID = rs.getInt (6); shipToC_Location_ID = rs.getInt (7); + dropshipC_Location_ID = rs.getInt (8); warehouseC_Location_ID = rs.getInt(6); found = true; } @@ -331,9 +378,10 @@ public class Tax + ", billFromC_Location_ID=" + billFromC_Location_ID + ", billToC_Location_ID=" + billToC_Location_ID + ", shipFromC_Location_ID=" + shipFromC_Location_ID - + ", shipToC_Location_ID=" + shipToC_Location_ID); + + ", shipToC_Location_ID=" + shipToC_Location_ID + + ", dropshipC_Location_ID=" + dropshipC_Location_ID); return Core.getTaxLookup().get (ctx, C_TaxCategory_ID, IsSOTrx, - shipDate, shipFromC_Location_ID, shipToC_Location_ID, + shipDate, shipFromC_Location_ID, shipToC_Location_ID, dropshipC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); } // getCharge @@ -393,7 +441,7 @@ public class Tax boolean IsSOTrx, String trxName) { return getProduct(ctx, M_Product_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, null, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, -1, IsSOTrx, null, trxName); } /** @@ -417,6 +465,7 @@ public class Tax * @param M_Warehouse_ID warehouse (ignored) * @param billC_BPartner_Location_ID invoice location * @param shipC_BPartner_Location_ID ship location (ignored) + * @param dropshipC_BPartner_Location_ID * @param IsSOTrx is a sales trx * @param deliveryViaRule if Delivery Via Rule is PickUp, use Warehouse Location instead of Billing Location as Tax Location to * @param trxName @@ -426,7 +475,7 @@ public class Tax public static int getProduct (Properties ctx, int M_Product_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, - int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, int dropshipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName) { String variable = ""; @@ -436,6 +485,7 @@ public class Tax int billFromC_Location_ID = 0; int billToC_Location_ID = 0; int warehouseC_Location_ID = 0; + int dropshipC_Location_ID = 0; String IsTaxExempt = null; String IsSOTaxExempt = null; String IsPOTaxExempt = null; @@ -447,20 +497,22 @@ public class Tax { // Get all at once sql = "SELECT p.C_TaxCategory_ID, o.C_Location_ID, il.C_Location_ID, b.IsTaxExempt, b.IsPOTaxExempt, " - + " w.C_Location_ID, sl.C_Location_ID " - + "FROM M_Product p, AD_OrgInfo o," - + " C_BPartner_Location il INNER JOIN C_BPartner b ON (il.C_BPartner_ID=b.C_BPartner_ID) " - + " LEFT OUTER JOIN M_Warehouse w ON (w.M_Warehouse_ID=?), C_BPartner_Location sl " - + "WHERE p.M_Product_ID=?" - + " AND o.AD_Org_ID=?" - + " AND il.C_BPartner_Location_ID=?" - + " AND sl.C_BPartner_Location_ID=?"; + + " w.C_Location_ID, sl.C_Location_ID, dsl.C_Location_ID " + + "FROM M_Product p" + + " JOIN AD_OrgInfo o ON (o.AD_Org_ID=?)" + + " JOIN C_BPartner_Location il ON (il.C_BPartner_Location_ID=?)" + + " INNER JOIN C_BPartner b ON (il.C_BPartner_ID=b.C_BPartner_ID)" + + " LEFT OUTER JOIN M_Warehouse w ON (w.M_Warehouse_ID=?)" + + " JOIN C_BPartner_Location sl ON (sl.C_BPartner_Location_ID=?)" + + " LEFT JOIN C_BPartner_Location dsl ON (dsl.C_BPartner_Location_ID=?) " + + "WHERE p.M_Product_ID=?"; pstmt = DB.prepareStatement(sql, trxName); - pstmt.setInt(1, M_Warehouse_ID); - pstmt.setInt(2, M_Product_ID); - pstmt.setInt(3, AD_Org_ID); - pstmt.setInt(4, billC_BPartner_Location_ID); - pstmt.setInt(5, shipC_BPartner_Location_ID); + pstmt.setInt(1, AD_Org_ID); + pstmt.setInt(2, billC_BPartner_Location_ID); + pstmt.setInt(3, M_Warehouse_ID); + pstmt.setInt(4, shipC_BPartner_Location_ID); + pstmt.setInt(5, dropshipC_BPartner_Location_ID); + pstmt.setInt(6, M_Product_ID); rs = pstmt.executeQuery(); boolean found = false; if (rs.next()) @@ -473,6 +525,7 @@ public class Tax IsTaxExempt = IsSOTrx ? IsSOTaxExempt : IsPOTaxExempt; shipFromC_Location_ID = rs.getInt(6); shipToC_Location_ID = rs.getInt(7); + dropshipC_Location_ID = rs.getInt(8); warehouseC_Location_ID = rs.getInt(6); found = true; } @@ -502,9 +555,10 @@ public class Tax + ", billFromC_Location_ID=" + billFromC_Location_ID + ", billToC_Location_ID=" + billToC_Location_ID + ", shipFromC_Location_ID=" + shipFromC_Location_ID - + ", shipToC_Location_ID=" + shipToC_Location_ID); + + ", shipToC_Location_ID=" + shipToC_Location_ID + + ", dropshipC_Location_ID=" + dropshipC_Location_ID); return Core.getTaxLookup().get(ctx, C_TaxCategory_ID, IsSOTrx, - shipDate, shipFromC_Location_ID, shipToC_Location_ID, + shipDate, shipFromC_Location_ID, shipToC_Location_ID, dropshipC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); } @@ -665,6 +719,33 @@ public class Tax int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, String trxName) + { + return get (ctx, + C_TaxCategory_ID, IsSOTrx, + shipDate, shipFromC_Location_ID, shipToC_Location_ID, -1, + billDate, billFromC_Location_ID, billToC_Location_ID, trxName); + } + + /************************************************************************** + * Get Tax ID (Detail). + * @param ctx context + * @param C_TaxCategory_ID tax category + * @param IsSOTrx Sales Order Trx + * @param shipDate ship date (ignored) + * @param shipFromC_Location_ID ship from (ignored) + * @param shipToC_Location_ID ship to (ignored) + * @param dropshipC_Location_ID + * @param billDate invoice date + * @param billFromC_Location_ID invoice from (Tax Location from) + * @param billToC_Location_ID invoice to (Tax Location to) + * @param trxName Transaction + * @return C_Tax_ID + * @throws TaxNotFoundException if no tax found for given criteria + */ + public static int get (Properties ctx, + int C_TaxCategory_ID, boolean IsSOTrx, + Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, int dropshipC_Location_ID, + Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, String trxName) { // C_TaxCategory contains CommodityCode diff --git a/org.idempiere.test/META-INF/MANIFEST.MF b/org.idempiere.test/META-INF/MANIFEST.MF index b076e9dc5b..f69db550a6 100644 --- a/org.idempiere.test/META-INF/MANIFEST.MF +++ b/org.idempiere.test/META-INF/MANIFEST.MF @@ -43,4 +43,4 @@ Bundle-Activator: org.idempiere.test.TestActivator Bundle-RequiredExecutionEnvironment: JavaSE-17 Export-Package: org.idempiere.test Bundle-ClassPath: . -Service-Component: OSGI-INF/org.idempiere.test.event.MyComponent.xml +Service-Component: OSGI-INF/*.xml diff --git a/org.idempiere.test/OSGI-INF/org.idempiere.test.model.MTaxTest_TaxLookup.xml b/org.idempiere.test/OSGI-INF/org.idempiere.test.model.MTaxTest_TaxLookup.xml new file mode 100644 index 0000000000..214fd2904f --- /dev/null +++ b/org.idempiere.test/OSGI-INF/org.idempiere.test.model.MTaxTest_TaxLookup.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java index ce180f06d5..b5b89310a1 100644 --- a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java +++ b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java @@ -96,7 +96,17 @@ public final class DictionaryIDs { this.id = id; } } - + + public enum AD_SysConfig { + TAX_LOOKUP_SERVICE(200198); + + public final int id; + + private AD_SysConfig(int id) { + this.id = id; + } + } + public enum AD_User { GARDEN_ADMIN(101), GARDEN_USER(102), @@ -295,7 +305,17 @@ public final class DictionaryIDs { this.id = id; } } - + + public enum C_Location { + ORG_WH_HQ(114); + + public final int id; + + private C_Location(int id) { + this.id = id; + } + } + public enum C_PaymentTerm { NET_30(100), NET_30_DAYS(107), @@ -345,6 +365,16 @@ public final class DictionaryIDs { } } + public enum C_Region { + CT(102); + + public final int id; + + private C_Region(int id) { + this.id = id; + } + } + public enum C_Tax { STANDARD(104), CT_SALES(105), diff --git a/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java b/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java index 1fb4095a12..6886600bef 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java @@ -31,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.Properties; import org.adempiere.base.Core; import org.compiere.model.MAccount; @@ -44,6 +45,7 @@ import org.compiere.model.MInOut; import org.compiere.model.MInOutLine; import org.compiere.model.MInvoice; import org.compiere.model.MInvoiceLine; +import org.compiere.model.MLocation; import org.compiere.model.MMatchPO; import org.compiere.model.MOrder; import org.compiere.model.MOrderLine; @@ -51,11 +53,13 @@ import org.compiere.model.MPriceList; import org.compiere.model.MPriceListVersion; import org.compiere.model.MProduct; import org.compiere.model.MProductPrice; +import org.compiere.model.MSysConfig; import org.compiere.model.MTax; import org.compiere.model.MTaxCategory; import org.compiere.model.MWarehouse; import org.compiere.model.ProductCost; import org.compiere.model.Tax; +import org.compiere.model.X_C_Order; import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; import org.compiere.process.ProcessInfo; @@ -66,12 +70,14 @@ import org.compiere.wf.MWorkflow; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.DictionaryIDs; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; /** * * @author hengsin * */ +@Isolated public class MTaxTest extends AbstractTestCase { public MTaxTest() { @@ -95,26 +101,110 @@ public class MTaxTest extends AbstractTestCase { @Test public void testTaxLookup() { - int taxExemptId = Tax.getExemptTax(Env.getCtx(), getAD_Org_ID(), getTrxName()); + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + int taxExemptId = Tax.getExemptTax(ctx, getAD_Org_ID(), trxName); assertTrue(taxExemptId>0, "Fail to get tax exempt Id"); - - MBPartner bp = new MBPartner(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id, getTrxName()); - bp.setIsTaxExempt(true); - bp.saveEx(); - - int id = Core.getTaxLookup().get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, getLoginDate(), getLoginDate(), getAD_Org_ID(), getM_Warehouse_ID(), - bp.getPrimaryC_BPartner_Location_ID(), bp.getPrimaryC_BPartner_Location_ID(), true, null, getTrxName()); - assertEquals(taxExemptId, id, "Unexpected tax id"); - - bp.setIsTaxExempt(false); - bp.saveEx(); - - id = Core.getTaxLookup().get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, getLoginDate(), getLoginDate(), getAD_Org_ID(), getM_Warehouse_ID(), - bp.getPrimaryC_BPartner_Location_ID(), bp.getPrimaryC_BPartner_Location_ID(), true, null, getTrxName()); - assertTrue(id != taxExemptId, "Unexpected tax id: " + id); - assertEquals(DictionaryIDs.C_Tax.STANDARD.id, id, "Unexpected tax id"); + + MBPartner bpTree = new MBPartner(ctx, DictionaryIDs.C_BPartner.TREE_FARM.id, trxName); + MBPartner bpPatio = new MBPartner(ctx, DictionaryIDs.C_BPartner.PATIO.id, trxName); + MBPartner bpCW = new MBPartner(ctx, DictionaryIDs.C_BPartner.C_AND_W.id, trxName); + MBPartner bpSeed = new MBPartner(ctx, DictionaryIDs.C_BPartner.SEED_FARM.id, trxName); + MBPartner bpJoe = new MBPartner(ctx, DictionaryIDs.C_BPartner.JOE_BLOCK.id, trxName); + bpJoe.setIsTaxExempt(true); + bpJoe.saveEx(); + + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + getAD_Org_ID(), getM_Warehouse_ID(), + bpJoe.getPrimaryC_BPartner_Location_ID(), bpJoe.getPrimaryC_BPartner_Location_ID(), -1, + true, null, taxExemptId); + + bpJoe.setIsTaxExempt(false); + bpJoe.saveEx(); + + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + getAD_Org_ID(), getM_Warehouse_ID(), + bpJoe.getPrimaryC_BPartner_Location_ID(), bpJoe.getPrimaryC_BPartner_Location_ID(), -1, + true, null, DictionaryIDs.C_Tax.STANDARD.id); + + // Sales charge from Germany (Org Fertilizer) to USA (Joe Block) -> expected Exempt + expectedTaxLookup(0, DictionaryIDs.C_Charge.FREIGHT.id, + DictionaryIDs.AD_Org.FERTILIZER.id, DictionaryIDs.M_Warehouse.FERTILIZER.id, + bpJoe.getPrimaryC_BPartner_Location_ID(), bpJoe.getPrimaryC_BPartner_Location_ID(), -1, + true, null, taxExemptId); + + // Purchase charge from USA New York (Patio BP) to USA Oregon (HQ Warehouse) -> expected Standard + expectedTaxLookup(0, DictionaryIDs.C_Charge.FREIGHT.id, + DictionaryIDs.AD_Org.HQ.id, DictionaryIDs.M_Warehouse.HQ.id, + bpPatio.getPrimaryC_BPartner_Location_ID(), bpPatio.getPrimaryC_BPartner_Location_ID(), -1, + false, null, DictionaryIDs.C_Tax.STANDARD.id); + + // Change location of Org HQ to Connecticut CT + MLocation location = new MLocation(ctx, DictionaryIDs.C_Location.ORG_WH_HQ.id, trxName); + location.setC_Region_ID(DictionaryIDs.C_Region.CT.id); + location.saveEx(); + // Sales charge from USA Connecticut (Org HQ modified) to USA Connecticut (C&W) -> expected CT Sales + expectedTaxLookup(0, DictionaryIDs.C_Charge.FREIGHT.id, + DictionaryIDs.AD_Org.HQ.id, DictionaryIDs.M_Warehouse.HQ.id, + bpCW.getPrimaryC_BPartner_Location_ID(), bpCW.getPrimaryC_BPartner_Location_ID(), -1, + true, null, DictionaryIDs.C_Tax.CT_SALES.id); + + // Sales product from USA Connecticut (Org HQ modified) to USA New Jersey (TreeFarm) -> expected Standard + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.HQ.id, DictionaryIDs.M_Warehouse.HQ.id, + bpTree.getPrimaryC_BPartner_Location_ID(), bpTree.getPrimaryC_BPartner_Location_ID(), -1, + true, null, DictionaryIDs.C_Tax.STANDARD.id); + + // Sales product from USA Connecticut (Org HQ modified) to USA New Jersey (TreeFarm) - pick rule -> expected CT Sales + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.HQ.id, DictionaryIDs.M_Warehouse.HQ.id, + bpTree.getPrimaryC_BPartner_Location_ID(), bpTree.getPrimaryC_BPartner_Location_ID(), -1, + true, X_C_Order.DELIVERYVIARULE_Pickup, DictionaryIDs.C_Tax.CT_SALES.id); + + // Purchase product from USA New York (Patio BP) to Germany (HQ Fertilizer) -> expected Exempt + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.FERTILIZER.id, DictionaryIDs.M_Warehouse.FERTILIZER.id, + bpPatio.getPrimaryC_BPartner_Location_ID(), bpPatio.getPrimaryC_BPartner_Location_ID(), -1, + false, null, taxExemptId); + + // Purchase product from USA Connecticut (Seed Farm BP) to Germany (HQ Fertilizer) drop ship to BP C&W -> expected Exempt (core) + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.FERTILIZER.id, DictionaryIDs.M_Warehouse.FERTILIZER.id, + bpSeed.getPrimaryC_BPartner_Location_ID(), bpSeed.getPrimaryC_BPartner_Location_ID(), bpCW.getPrimaryC_BPartner_Location_ID(), + false, null, taxExemptId); + + MSysConfig sysCfgTaxLookup = new MSysConfig(ctx, DictionaryIDs.AD_SysConfig.TAX_LOOKUP_SERVICE.id, null); + String oldValue = sysCfgTaxLookup.getValue(); + try { + sysCfgTaxLookup.setValue(MTaxTest_TaxLookup.class.getName()); + sysCfgTaxLookup.saveCrossTenantSafeEx(); + CacheMgt.get().reset(MSysConfig.Table_Name); + + // Drop Ship Case with custom tax lookup + // Purchase product from USA Connecticut (Seed Farm BP) to Germany (HQ Fertilizer) drop ship to BP C&W -> expected CT Sales (custom tax lookup) + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.FERTILIZER.id, DictionaryIDs.M_Warehouse.FERTILIZER.id, + bpSeed.getPrimaryC_BPartner_Location_ID(), bpSeed.getPrimaryC_BPartner_Location_ID(), bpCW.getPrimaryC_BPartner_Location_ID(), + false, null, DictionaryIDs.C_Tax.CT_SALES.id); + } finally { + sysCfgTaxLookup.setValue(oldValue); + sysCfgTaxLookup.saveCrossTenantSafeEx(); + } + } - + + private void expectedTaxLookup(int prodId, int chargeId, int orgId, int warehouseId, int billLocationId, int shipLocationId, int dropshipLocation, boolean isSOTrx, String deliveryViaRule, int expectedTaxId) { + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + int id = Core.getTaxLookup().get(ctx, prodId, chargeId, getLoginDate(), getLoginDate(), orgId, warehouseId, + billLocationId, shipLocationId, + dropshipLocation, + isSOTrx, deliveryViaRule, trxName); + assertEquals(expectedTaxId, id, "Unexpected tax id"); + } + @Test public void testDistributeTaxToProductCost() { MProduct product = null; @@ -402,4 +492,5 @@ public class MTaxTest extends AbstractTestCase { category.deleteEx(true); } } + } diff --git a/org.idempiere.test/src/org/idempiere/test/model/MTaxTest_TaxLookup.java b/org.idempiere.test/src/org/idempiere/test/model/MTaxTest_TaxLookup.java new file mode 100644 index 0000000000..b9af633822 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/model/MTaxTest_TaxLookup.java @@ -0,0 +1,53 @@ +/*********************************************************************** + * 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: * + * - Diego Ruiz - BX Service * + **********************************************************************/ +package org.idempiere.test.model; + +import java.sql.Timestamp; +import java.util.Properties; + +import org.adempiere.base.DefaultTaxLookup; +import org.adempiere.base.ITaxLookup; +import org.osgi.service.component.annotations.Component; + +// test case copied from bxservice/de.bxservice.europeantaxprovider plugin +@Component(immediate = true, service = {ITaxLookup.class}) +public class MTaxTest_TaxLookup extends DefaultTaxLookup { + + @Override + public int get(Properties ctx, int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, + int shipToC_Location_ID, int dropshipToC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, + String trxName) { + if (IsSOTrx) { + billToC_Location_ID = shipToC_Location_ID; + } else { + if (dropshipToC_Location_ID > 0 && dropshipToC_Location_ID != shipToC_Location_ID) + billToC_Location_ID = dropshipToC_Location_ID; + billFromC_Location_ID = shipFromC_Location_ID; + } + + return super.get(ctx, C_TaxCategory_ID, IsSOTrx, shipDate, shipFromC_Location_ID, shipToC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); + } + +} From 45544a2058789a78beadf324ed6c62c91df52686 Mon Sep 17 00:00:00 2001 From: hieplq Date: Thu, 6 Jun 2024 19:48:44 +0700 Subject: [PATCH 043/169] IDEMPIERE-6161:JasperReports encounters issues using encrypted functions due to incompatible dependencies (#2381) * IDEMPIERE-6161:JasperReports encounters issues using encrypted functions due to incompatible dependencies Co-authored-by: Carlos Ruiz Co-authored-by: hengsin --- .../maven.locations.xml | 4 +- .../org.idempiere.p2.targetplatform.target | 6 +- org.idempiere.test/META-INF/MANIFEST.MF | 4 +- ...sper.HandleSetupConfigurationPdfExport.xml | 5 + .../HandleSetupConfigurationPdfExport.java | 87 ++++++++++++ .../test/jasper/PrintWithinProcess.java | 127 ++++++++++++------ 6 files changed, 184 insertions(+), 49 deletions(-) create mode 100644 org.idempiere.test/OSGI-INF/org.idempiere.test.jasper.HandleSetupConfigurationPdfExport.xml create mode 100644 org.idempiere.test/src/org/idempiere/test/jasper/HandleSetupConfigurationPdfExport.java diff --git a/org.idempiere.p2.targetplatform/maven.locations.xml b/org.idempiere.p2.targetplatform/maven.locations.xml index 70dbca1536..70630219ae 100644 --- a/org.idempiere.p2.targetplatform/maven.locations.xml +++ b/org.idempiere.p2.targetplatform/maven.locations.xml @@ -360,13 +360,13 @@ Export-Package: *;-noimport:=true org.bouncycastle bcpg-jdk18on - 1.72.2 + 1.77 jar org.bouncycastle bcmail-jdk18on - 1.72 + 1.77 jar diff --git a/org.idempiere.p2.targetplatform/org.idempiere.p2.targetplatform.target b/org.idempiere.p2.targetplatform/org.idempiere.p2.targetplatform.target index 3c4a55cf1c..6159f9367a 100644 --- a/org.idempiere.p2.targetplatform/org.idempiere.p2.targetplatform.target +++ b/org.idempiere.p2.targetplatform/org.idempiere.p2.targetplatform.target @@ -1,7 +1,7 @@ - + @@ -502,13 +502,13 @@ Export-Package: *;-noimport:=true org.bouncycastle bcpg-jdk18on - 1.72.2 + 1.77 jar org.bouncycastle bcmail-jdk18on - 1.72 + 1.77 jar diff --git a/org.idempiere.test/META-INF/MANIFEST.MF b/org.idempiere.test/META-INF/MANIFEST.MF index f69db550a6..ec389ceadb 100644 --- a/org.idempiere.test/META-INF/MANIFEST.MF +++ b/org.idempiere.test/META-INF/MANIFEST.MF @@ -5,7 +5,9 @@ Bundle-SymbolicName: org.idempiere.test Bundle-Version: 11.0.0.qualifier Bundle-Vendor: iDempiere Automatic-Module-Name: org.idempiere.test -Import-Package: org.assertj.core.api;version="3.22.0", +Import-Package: net.sf.jasperreports.export, + org.adempiere.report.jasper, + org.assertj.core.api;version="3.22.0", org.assertj.core.api.junit.jupiter;version="3.22.0", org.junit.jupiter.api;version="5.9.0", org.junit.jupiter.api.condition;version="5.9.0", diff --git a/org.idempiere.test/OSGI-INF/org.idempiere.test.jasper.HandleSetupConfigurationPdfExport.xml b/org.idempiere.test/OSGI-INF/org.idempiere.test.jasper.HandleSetupConfigurationPdfExport.xml new file mode 100644 index 0000000000..d65e934384 --- /dev/null +++ b/org.idempiere.test/OSGI-INF/org.idempiere.test.jasper.HandleSetupConfigurationPdfExport.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/org.idempiere.test/src/org/idempiere/test/jasper/HandleSetupConfigurationPdfExport.java b/org.idempiere.test/src/org/idempiere/test/jasper/HandleSetupConfigurationPdfExport.java new file mode 100644 index 0000000000..396142fb84 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/jasper/HandleSetupConfigurationPdfExport.java @@ -0,0 +1,87 @@ +/*********************************************************************** + * 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. * + **********************************************************************/ + +package org.idempiere.test.jasper; + +import net.sf.jasperreports.export.SimplePdfExporterConfiguration; +import org.adempiere.base.event.AbstractEventHandler; +import org.adempiere.base.event.IEventManager; +import org.adempiere.report.jasper.JREventManage; +import org.compiere.process.ProcessInfo; +import org.compiere.process.ProcessInfoParameter; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.event.Event; + +@Component( +reference = @Reference( + name = "IEventManager", bind = "bindEventManager", unbind="unbindEventManager", + policy = ReferencePolicy.STATIC, cardinality =ReferenceCardinality.MANDATORY, service = IEventManager.class) +) +public class HandleSetupConfigurationPdfExport extends AbstractEventHandler { + + @Override + protected void initialize() { + registerEvent(JREventManage.JP_PDF_EXPORT_CONFIG_EVENT, null); + + } + + @Override + protected void doHandleEvent(Event event) { + // just handle pdf export event + if (!JREventManage.JP_PDF_EXPORT_CONFIG_EVENT.equals(event.getTopic())) + return; + + // get common object from event info + SimplePdfExporterConfiguration pdfExporterConfig = (SimplePdfExporterConfiguration)event.getProperty(JREventManage.JP_KEY_PDF_EXPORT_CONFIG); + //JRPdfExporter pdfExporter = (JRPdfExporter)event.getProperty(JP_KEY_PDF_EXPORT_EXPORTER); + ProcessInfo pi = (ProcessInfo)event.getProperty(JREventManage.JP_KEY_PROCESS_INFO); + + // can apply passowrd or not up to parameter + boolean isEncrypted = false; + String readPassword = ""; + String createPassword = ""; + + // control logic by parameter + for (ProcessInfoParameter parameter : pi.getParameter()) { + if ("isEncrypted".equals(parameter.getParameterName())) { + isEncrypted = parameter.getParameterAsBoolean(); + }else if ("readPassword".equals(parameter.getParameterName())) { + readPassword = parameter.getParameterAsString(); + }else if ("createPassword".equals(parameter.getParameterName())) { + createPassword = parameter.getParameterAsString(); + } + } + + if (isEncrypted) { + pdfExporterConfig.setEncrypted(true); + pdfExporterConfig.set128BitKey(true); + pdfExporterConfig.setUserPassword(readPassword); + pdfExporterConfig.setOwnerPassword(createPassword); + + } + + } + +} diff --git a/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java b/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java index 8bcf2fd5cc..910c924fba 100644 --- a/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java +++ b/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java @@ -40,6 +40,7 @@ import org.compiere.model.MProcess; import org.compiere.model.PO; import org.compiere.model.Query; import org.compiere.process.ProcessInfo; +import org.compiere.process.ProcessInfoParameter; import org.compiere.util.Env; import org.compiere.util.Trx; import org.idempiere.test.AbstractTestCase; @@ -56,6 +57,28 @@ public class PrintWithinProcess extends AbstractTestCase { public PrintWithinProcess() { } + private MProcess setupProcess (Properties ctx, String trxName, String jasperReport) { + MProcess process = new MProcess(ctx, 0, trxName); + process.set_ValueNoCheck("AD_Client_ID", 0); + process.setAD_Org_ID(0); + process.setJasperReport(jasperReport); + process.setName("Test Invoice Jasper"); + process.setValue("Test_Invoice_Jasper"); + process.saveCrossTenantSafeEx(); + commit(); + return process; + } + + private ProcessInfo setupProcessInfo(MProcess process) { + ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); + pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); + pi.setAD_User_ID(getAD_User_ID()); + pi.setAD_Client_ID(getAD_Client_ID()); + pi.setPrintPreview(false); + pi.setIsBatch(true); + return pi; + } + @Test public void testPrintWithLocalFile() { Properties ctx = Env.getCtx(); @@ -64,14 +87,7 @@ public class PrintWithinProcess extends AbstractTestCase { MProcess process = null; try { String fileName = putResourceInTempFolder("org/idempiere/test/jasper/AR_Invoice.jrxml"); - process = new MProcess(ctx, 0, trxName); - process.set_ValueNoCheck("AD_Client_ID", 0); - process.setAD_Org_ID(0); - process.setJasperReport("file://" + fileName); - process.setName("Test Invoice Jasper"); - process.setValue("Test_Invoice_Jasper"); - process.saveCrossTenantSafeEx(); - commit(); + process = setupProcess(ctx, trxName, "file://" + fileName); List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) .setClient_ID() @@ -83,12 +99,7 @@ public class PrintWithinProcess extends AbstractTestCase { invoice.saveEx(); } - ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); - pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); - pi.setAD_User_ID(getAD_User_ID()); - pi.setAD_Client_ID(getAD_Client_ID()); - pi.setPrintPreview(false); - pi.setIsBatch(true); + ProcessInfo pi = setupProcessInfo(process); Trx trx = Trx.get(trxName, false); List pdfList = new ArrayList(); @@ -144,7 +155,7 @@ public class PrintWithinProcess extends AbstractTestCase { } throw new AdempiereException("Resource " + resource + " not found"); } - + @Test public void testPrintWithBundleResource() { Properties ctx = Env.getCtx(); @@ -152,14 +163,7 @@ public class PrintWithinProcess extends AbstractTestCase { MProcess process = null; try { - process = new MProcess(ctx, 0, trxName); - process.set_ValueNoCheck("AD_Client_ID", 0); - process.setAD_Org_ID(0); - process.setJasperReport("bundle:org.idempiere.test:/AR_Invoice_Bundle.jrxml"); - process.setName("Test Invoice Jasper"); - process.setValue("Test_Invoice_Jasper"); - process.saveCrossTenantSafeEx(); - commit(); + process = setupProcess(ctx, trxName, "bundle:org.idempiere.test:/AR_Invoice_Bundle.jrxml"); List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) .setClient_ID() @@ -171,12 +175,7 @@ public class PrintWithinProcess extends AbstractTestCase { invoice.saveEx(); } - ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); - pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); - pi.setAD_User_ID(getAD_User_ID()); - pi.setAD_Client_ID(getAD_Client_ID()); - pi.setPrintPreview(false); - pi.setIsBatch(true); + ProcessInfo pi = setupProcessInfo(process); Trx trx = Trx.get(trxName, false); List pdfList = new ArrayList(); @@ -212,14 +211,7 @@ public class PrintWithinProcess extends AbstractTestCase { MProcess process = null; try { - process = new MProcess(ctx, 0, trxName); - process.set_ValueNoCheck("AD_Client_ID", 0); - process.setAD_Org_ID(0); - process.setJasperReport("resource:org.idempiere.test:org/idempiere/test/jasper/AR_Invoice.jrxml"); - process.setName("Test Invoice Jasper"); - process.setValue("Test_Invoice_Jasper"); - process.saveCrossTenantSafeEx(); - commit(); + process = setupProcess(ctx, trxName, "resource:org.idempiere.test:org/idempiere/test/jasper/AR_Invoice.jrxml"); List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) .setClient_ID() @@ -231,12 +223,7 @@ public class PrintWithinProcess extends AbstractTestCase { invoice.saveEx(); } - ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); - pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); - pi.setAD_User_ID(getAD_User_ID()); - pi.setAD_Client_ID(getAD_Client_ID()); - pi.setPrintPreview(false); - pi.setIsBatch(true); + ProcessInfo pi = setupProcessInfo(process); Trx trx = Trx.get(trxName, false); List pdfList = new ArrayList(); @@ -264,4 +251,58 @@ public class PrintWithinProcess extends AbstractTestCase { commit(); } } + + @Test + public void testEncryptReport() { + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + MProcess process = null; + try { + process = setupProcess(ctx, trxName, "bundle:org.idempiere.test:/AR_Invoice_Bundle.jrxml"); + List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) + .setClient_ID() + .setOnlyActiveRecords(true) + .setParameters(103, 109) + .list(); + for (MInvoice invoice : invoices) { + invoice.setDescription("Test Printing within a Process"); + invoice.saveEx(); + } + + ProcessInfo pi = setupProcessInfo(process); + pi.setExport(true); + Trx trx = Trx.get(trxName, false); + ProcessInfoParameter [] parameter = new ProcessInfoParameter [] + {new ProcessInfoParameter("isEncrypted", true, null, null, null), + new ProcessInfoParameter("readPassword", "readPassword", null, null, null), + new ProcessInfoParameter("createPassword", "createPassword", null, null, null)}; + + pi.setParameter(parameter); + + List pdfList = new ArrayList(); + for (MInvoice invoice : invoices) { + pi.setRecord_ID(invoice.getC_Invoice_ID()); + ProcessUtil.startJavaProcess(Env.getCtx(), pi, trx, false); + assertFalse(pi.isError(), pi.getSummary()); + assertFalse(pi.getExportFile() == null); + pdfList.add(pi.getExportFile()); + } + assertFalse(pdfList.isEmpty()); + } finally { + rollback(); + if (process != null) { + int oldRole = Env.getAD_Role_ID(ctx); + try { + PO.setCrossTenantSafe(); + Env.setContext(ctx, Env.AD_ROLE_ID, 0); // to allow deleting process + process.deleteEx(true); + } finally { + Env.setContext(ctx, Env.AD_ROLE_ID, oldRole); + PO.clearCrossTenantSafe(); + } + } + commit(); + } + } } From 9042f8319a25c0a6a8dae332714ee4f680074699 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Mon, 10 Jun 2024 10:17:46 +0200 Subject: [PATCH 044/169] IDEMPIERE-6167 BOM Price List must search just for Verified BOMs (#2388) --- db/oracle/functions/BOM_PriceLimit.sql | 1 + db/oracle/functions/BOM_PriceList.sql | 1 + db/oracle/functions/BOM_PriceStd.sql | 1 + db/postgresql/functions/BOM_PriceLimit.sql | 1 + db/postgresql/functions/BOM_PriceList.sql | 1 + db/postgresql/functions/BOM_PriceStd.sql | 1 + .../oracle/202406072206_IDEMPIERE-6167.sql | 161 ++++++++++++++++++ .../202406072206_IDEMPIERE-6167.sql | 117 +++++++++++++ 8 files changed, 284 insertions(+) create mode 100644 migration/iD11/oracle/202406072206_IDEMPIERE-6167.sql create mode 100644 migration/iD11/postgresql/202406072206_IDEMPIERE-6167.sql diff --git a/db/oracle/functions/BOM_PriceLimit.sql b/db/oracle/functions/BOM_PriceLimit.sql index 8ca102b31c..dc32f5912d 100644 --- a/db/oracle/functions/BOM_PriceLimit.sql +++ b/db/oracle/functions/BOM_PriceLimit.sql @@ -27,6 +27,7 @@ AS WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' AND b.IsActive='Y'; -- BEGIN diff --git a/db/oracle/functions/BOM_PriceList.sql b/db/oracle/functions/BOM_PriceList.sql index b9590f4501..a8e2c3d64f 100644 --- a/db/oracle/functions/BOM_PriceList.sql +++ b/db/oracle/functions/BOM_PriceList.sql @@ -27,6 +27,7 @@ AS WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' AND b.IsActive='Y'; -- BEGIN diff --git a/db/oracle/functions/BOM_PriceStd.sql b/db/oracle/functions/BOM_PriceStd.sql index 143dbf87d6..6501cc8187 100644 --- a/db/oracle/functions/BOM_PriceStd.sql +++ b/db/oracle/functions/BOM_PriceStd.sql @@ -27,6 +27,7 @@ AS WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' AND b.IsActive='Y'; -- BEGIN diff --git a/db/postgresql/functions/BOM_PriceLimit.sql b/db/postgresql/functions/BOM_PriceLimit.sql index 216dce20a5..f06200dcb4 100644 --- a/db/postgresql/functions/BOM_PriceLimit.sql +++ b/db/postgresql/functions/BOM_PriceLimit.sql @@ -20,6 +20,7 @@ BEGIN WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' AND b.IsActive='Y' LOOP v_ProductPrice := bomPriceLimit (bom.M_ProductBOM_ID, PriceList_Version_ID); diff --git a/db/postgresql/functions/BOM_PriceList.sql b/db/postgresql/functions/BOM_PriceList.sql index 03f69e0dae..4f81e25bde 100644 --- a/db/postgresql/functions/BOM_PriceList.sql +++ b/db/postgresql/functions/BOM_PriceList.sql @@ -20,6 +20,7 @@ BEGIN WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' AND b.IsActive='Y' LOOP v_ProductPrice := bomPriceList (bom.M_ProductBOM_ID, PriceList_Version_ID); diff --git a/db/postgresql/functions/BOM_PriceStd.sql b/db/postgresql/functions/BOM_PriceStd.sql index 5c34e42dca..fbfc35e194 100644 --- a/db/postgresql/functions/BOM_PriceStd.sql +++ b/db/postgresql/functions/BOM_PriceStd.sql @@ -20,6 +20,7 @@ BEGIN WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' AND b.IsActive='Y' LOOP v_ProductPrice := bomPriceStd (bom.M_ProductBOM_ID, PriceList_Version_ID); diff --git a/migration/iD11/oracle/202406072206_IDEMPIERE-6167.sql b/migration/iD11/oracle/202406072206_IDEMPIERE-6167.sql new file mode 100644 index 0000000000..52857fe3e4 --- /dev/null +++ b/migration/iD11/oracle/202406072206_IDEMPIERE-6167.sql @@ -0,0 +1,161 @@ +-- IDEMPIERE-6167 BOM Price List must search just for Verified BOMs +SELECT register_migration_script('202406072206_IDEMPIERE-6167.sql') FROM dual; + +CREATE OR REPLACE FUNCTION BOMPRICELIMIT +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceLimit.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Limit Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; +END Bompricelimit; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIST +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceList.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return List Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelist (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Qry=' || bom.BOMQty || ' @ ' || v_ProductPrice || ', Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricelist; +/ + +CREATE OR REPLACE FUNCTION BOMPRICESTD +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceStd.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Standard Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricestd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricestd; +/ + diff --git a/migration/iD11/postgresql/202406072206_IDEMPIERE-6167.sql b/migration/iD11/postgresql/202406072206_IDEMPIERE-6167.sql new file mode 100644 index 0000000000..74210abf71 --- /dev/null +++ b/migration/iD11/postgresql/202406072206_IDEMPIERE-6167.sql @@ -0,0 +1,117 @@ +-- IDEMPIERE-6167 BOM Price List must search just for Verified BOMs +SELECT register_migration_script('202406072206_IDEMPIERE-6167.sql') FROM dual; + +CREATE OR REPLACE FUNCTION bompricelimit (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_ProductPrice + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceLimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; + +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bompricelist (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_ProductPrice + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceList (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; + +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bompricestd (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_ProductPrice + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceStd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; + +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + From 0f02545dbb667b8e53e8b053650a4581207a7db9 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Mon, 10 Jun 2024 13:11:42 +0200 Subject: [PATCH 045/169] IDEMPIERE-6166 PostgreSQL DUAL table with more than one record (#2387) * IDEMPIERE-6166 PostgreSQL DUAL table with more than one record - create unique index on dual.dummy for postgresql * - implement suggestion from hengsin - make dual a view instead of a table - fix similar issue with the table dbreplicasyncverifier - it must ensure to contain just one table - running again a COPY command must fail --- .../functions/dbreplicasyncverifier.sql | 29 ++++++++++++++++++ .../functions/register_migration_script.sql | 11 ++----- .../oracle/202406072107_IDEMPIERE-6166.sql | 5 ++++ .../202406072107_IDEMPIERE-6166.sql | 30 +++++++++++++++++++ 4 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 db/postgresql/functions/dbreplicasyncverifier.sql create mode 100644 migration/iD11/oracle/202406072107_IDEMPIERE-6166.sql create mode 100644 migration/iD11/postgresql/202406072107_IDEMPIERE-6166.sql diff --git a/db/postgresql/functions/dbreplicasyncverifier.sql b/db/postgresql/functions/dbreplicasyncverifier.sql new file mode 100644 index 0000000000..f02a615e22 --- /dev/null +++ b/db/postgresql/functions/dbreplicasyncverifier.sql @@ -0,0 +1,29 @@ +/* +CREATE TABLE dbreplicasyncverifier (lastupdate date) +; + +INSERT INTO dbreplicasyncverifier values (to_date('1900-01-01 00:00:00', 'yyyy-mm-dd HH24:MI:SS')) +; +*/ + +CREATE OR REPLACE FUNCTION forbid_multiple_rows_in_dbreplicasyncverifier() +RETURNS TRIGGER AS $$ +BEGIN + -- Check if the table already contains a row + IF (SELECT COUNT(*) FROM dbreplicasyncverifier) > 0 THEN + RAISE EXCEPTION 'Table can only contain one row.'; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql +; + +CREATE TRIGGER single_row_only_trigger_dbreplicasyncverifier +BEFORE INSERT ON dbreplicasyncverifier +FOR EACH ROW +EXECUTE FUNCTION forbid_multiple_rows_in_dbreplicasyncverifier() +; + +CREATE OR REPLACE RULE delete_dbreplicasyncverifier AS ON DELETE TO dbreplicasyncverifier DO INSTEAD NOTHING +; + diff --git a/db/postgresql/functions/register_migration_script.sql b/db/postgresql/functions/register_migration_script.sql index 4d07a06bc4..5133534159 100644 --- a/db/postgresql/functions/register_migration_script.sql +++ b/db/postgresql/functions/register_migration_script.sql @@ -41,13 +41,6 @@ END; $BODY$ LANGUAGE plpgsql; -CREATE TABLE dual ( dummy char ); - -INSERT INTO dual values ( 'X' ); - -CREATE OR REPLACE RULE insert_dual AS ON INSERT TO dual DO INSTEAD NOTHING; - -CREATE OR REPLACE RULE update_dual AS ON UPDATE TO dual DO INSTEAD NOTHING; - -CREATE OR REPLACE RULE delete_dual AS ON DELETE TO dual DO INSTEAD NOTHING; +CREATE VIEW dual AS SELECT 'X'::varchar AS dummy +; diff --git a/migration/iD11/oracle/202406072107_IDEMPIERE-6166.sql b/migration/iD11/oracle/202406072107_IDEMPIERE-6166.sql new file mode 100644 index 0000000000..fe015d0e68 --- /dev/null +++ b/migration/iD11/oracle/202406072107_IDEMPIERE-6166.sql @@ -0,0 +1,5 @@ +-- IDEMPIERE-6166 PostgreSQL DUAL table with more than one record +SELECT register_migration_script('202406072107_IDEMPIERE-6166.sql') FROM dual; + +-- placeholder, this is just required for postgresql + diff --git a/migration/iD11/postgresql/202406072107_IDEMPIERE-6166.sql b/migration/iD11/postgresql/202406072107_IDEMPIERE-6166.sql new file mode 100644 index 0000000000..9d12bb5b3d --- /dev/null +++ b/migration/iD11/postgresql/202406072107_IDEMPIERE-6166.sql @@ -0,0 +1,30 @@ +-- IDEMPIERE-6166 PostgreSQL DUAL table with more than one record +SELECT register_migration_script('202406072107_IDEMPIERE-6166.sql') FROM dual; + +DROP TABLE IF EXISTS dual +; + +CREATE VIEW dual AS SELECT 'X'::varchar AS dummy +; + +DROP RULE insert_dbreplicasyncverifier ON dbreplicasyncverifier +; + +CREATE OR REPLACE FUNCTION forbid_multiple_rows_in_dbreplicasyncverifier() +RETURNS TRIGGER AS $$ +BEGIN + -- Check if the table already contains a row + IF (SELECT COUNT(*) FROM dbreplicasyncverifier) > 0 THEN + RAISE EXCEPTION 'Table dbreplicasyncverifier can only contain one row.'; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql +; + +CREATE TRIGGER single_row_only_trigger_dbreplicasyncverifier +BEFORE INSERT ON dbreplicasyncverifier +FOR EACH ROW +EXECUTE FUNCTION forbid_multiple_rows_in_dbreplicasyncverifier() +; + From 4875568cf2f5838c165ea15fa57c46818c1dfa0c Mon Sep 17 00:00:00 2001 From: hengsin Date: Tue, 11 Jun 2024 00:07:41 +0800 Subject: [PATCH 046/169] IDEMPIERE-6117 POI: WorkbookFactory.create cannot be used to open XLSX files (#2357) * IDEMPIERE-6117 POI: WorkbookFactory.create cannot be used to open XLSX files * IDEMPIERE-6117 POI: WorkbookFactory.create cannot be used to open XLSX files * IDEMPIERE-6117 POI: WorkbookFactory.create cannot be used to open XLSX files - Incorporate patch from Carlos --- .../oracle/202405131534_IDEMPIERE-6040.sql | 25 +++++++++++++++++++ .../202405131534_IDEMPIERE-6040.sql | 22 ++++++++++++++++ org.adempiere.base-feature/feature.xml | 4 +++ .../model.generator.launch | 1 + .../packinfolder.app.launch | 1 + .../sign.database.build.launch | 1 + .../synchronize-terminology.app.launch | 1 + .../translation.app.launch | 1 + .../src/org/adempiere/base/BaseActivator.java | 23 +++++++++++++++++ .../compiere/model/I_AD_ImportTemplate.java | 2 +- .../org/compiere/model/MImportTemplate.java | 18 +++---------- .../compiere/model/X_AD_ImportTemplate.java | 12 ++++----- org.adempiere.install/install.app.launch | 1 + .../install.console.app.launch | 1 + .../install.silent.app.launch | 1 + .../setup/configuration/config.ini | 1 + org.idempiere.test/pom.xml | 11 +++++--- 17 files changed, 101 insertions(+), 25 deletions(-) create mode 100644 migration/iD11/oracle/202405131534_IDEMPIERE-6040.sql create mode 100644 migration/iD11/postgresql/202405131534_IDEMPIERE-6040.sql diff --git a/migration/iD11/oracle/202405131534_IDEMPIERE-6040.sql b/migration/iD11/oracle/202405131534_IDEMPIERE-6040.sql new file mode 100644 index 0000000000..57a9109402 --- /dev/null +++ b/migration/iD11/oracle/202405131534_IDEMPIERE-6040.sql @@ -0,0 +1,25 @@ +-- IDEMPIERE-6040 Improvements for CSV import template +SELECT register_migration_script('202405131534_IDEMPIERE-6040.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- May 13, 2024, 3:34:49 PM CEST +UPDATE AD_Ref_List SET Name='Comma-separated values (CSV)',Updated=TO_TIMESTAMP('2024-05-13 15:34:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200704 +; + +-- May 13, 2024, 3:35:58 PM CEST +UPDATE AD_Ref_List SET Name='Excel (XLS/XLSX)',Updated=TO_TIMESTAMP('2024-05-13 15:35:58','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200706 +; + +-- May 13, 2024, 3:36:02 PM CEST +UPDATE AD_Ref_List SET IsActive='N',Updated=TO_TIMESTAMP('2024-05-13 15:36:02','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200705 +; + +UPDATE AD_ImportTemplate SET ImportTemplateType='XLSX' WHERE ImportTemplateType='XLS' +; + +-- May 13, 2024, 3:59:49 PM CEST +UPDATE AD_Field SET SeqNo=120, ColumnSpan=2,Updated=TO_TIMESTAMP('2024-05-13 15:59:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208476 +; + diff --git a/migration/iD11/postgresql/202405131534_IDEMPIERE-6040.sql b/migration/iD11/postgresql/202405131534_IDEMPIERE-6040.sql new file mode 100644 index 0000000000..962d64dfb4 --- /dev/null +++ b/migration/iD11/postgresql/202405131534_IDEMPIERE-6040.sql @@ -0,0 +1,22 @@ +-- IDEMPIERE-6040 Improvements for CSV import template +SELECT register_migration_script('202405131534_IDEMPIERE-6040.sql') FROM dual; + +-- May 13, 2024, 3:34:49 PM CEST +UPDATE AD_Ref_List SET Name='Comma-separated values (CSV)',Updated=TO_TIMESTAMP('2024-05-13 15:34:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200704 +; + +-- May 13, 2024, 3:35:58 PM CEST +UPDATE AD_Ref_List SET Name='Excel (XLS/XLSX)',Updated=TO_TIMESTAMP('2024-05-13 15:35:58','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200706 +; + +-- May 13, 2024, 3:36:02 PM CEST +UPDATE AD_Ref_List SET IsActive='N',Updated=TO_TIMESTAMP('2024-05-13 15:36:02','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200705 +; + +UPDATE AD_ImportTemplate SET ImportTemplateType='XLSX' WHERE ImportTemplateType='XLS' +; + +-- May 13, 2024, 3:59:49 PM CEST +UPDATE AD_Field SET SeqNo=120, ColumnSpan=2,Updated=TO_TIMESTAMP('2024-05-13 15:59:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208476 +; + diff --git a/org.adempiere.base-feature/feature.xml b/org.adempiere.base-feature/feature.xml index 839e7dd653..ad1ffdab44 100644 --- a/org.adempiere.base-feature/feature.xml +++ b/org.adempiere.base-feature/feature.xml @@ -273,6 +273,10 @@ id="org.apache.commons.commons-compress" version="0.0.0"/> + + diff --git a/org.adempiere.base-feature/model.generator.launch b/org.adempiere.base-feature/model.generator.launch index 0ddcb8cdd0..6c4e31f52e 100644 --- a/org.adempiere.base-feature/model.generator.launch +++ b/org.adempiere.base-feature/model.generator.launch @@ -60,6 +60,7 @@ + diff --git a/org.adempiere.base-feature/packinfolder.app.launch b/org.adempiere.base-feature/packinfolder.app.launch index 3a3c509882..e04ab163ee 100644 --- a/org.adempiere.base-feature/packinfolder.app.launch +++ b/org.adempiere.base-feature/packinfolder.app.launch @@ -68,6 +68,7 @@ + diff --git a/org.adempiere.base-feature/sign.database.build.launch b/org.adempiere.base-feature/sign.database.build.launch index e84c656caf..b3efd9a7a6 100644 --- a/org.adempiere.base-feature/sign.database.build.launch +++ b/org.adempiere.base-feature/sign.database.build.launch @@ -69,6 +69,7 @@ + diff --git a/org.adempiere.base-feature/synchronize-terminology.app.launch b/org.adempiere.base-feature/synchronize-terminology.app.launch index bf30922042..d907243845 100644 --- a/org.adempiere.base-feature/synchronize-terminology.app.launch +++ b/org.adempiere.base-feature/synchronize-terminology.app.launch @@ -69,6 +69,7 @@ + diff --git a/org.adempiere.base-feature/translation.app.launch b/org.adempiere.base-feature/translation.app.launch index e7d3cc10d8..e42b430b8b 100644 --- a/org.adempiere.base-feature/translation.app.launch +++ b/org.adempiere.base-feature/translation.app.launch @@ -69,6 +69,7 @@ + diff --git a/org.adempiere.base/src/org/adempiere/base/BaseActivator.java b/org.adempiere.base/src/org/adempiere/base/BaseActivator.java index d042c7c473..c10fc3534a 100644 --- a/org.adempiere.base/src/org/adempiere/base/BaseActivator.java +++ b/org.adempiere.base/src/org/adempiere/base/BaseActivator.java @@ -28,6 +28,14 @@ import java.util.Map; import java.util.Properties; import org.adempiere.base.equinox.StackTraceCommand; +import org.apache.poi.extractor.ExtractorFactory; +import org.apache.poi.extractor.MainExtractorFactory; +import org.apache.poi.hssf.usermodel.HSSFWorkbookFactory; +import org.apache.poi.ooxml.extractor.POIXMLExtractorFactory; +import org.apache.poi.sl.usermodel.SlideShowFactory; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.xssf.usermodel.XSSFWorkbookFactory; +import org.apache.poi.xslf.usermodel.XSLFSlideShowFactory; import org.compiere.Adempiere; import org.eclipse.osgi.framework.console.CommandProvider; import org.osgi.framework.BundleActivator; @@ -61,6 +69,13 @@ public class BaseActivator implements BundleActivator { context.registerService(CommandProvider.class.getName(), new StackTraceCommand(), null); blacklistService = new ComponentBlackListService(context); + + //setup poi factory + WorkbookFactory.addProvider(new HSSFWorkbookFactory()); + WorkbookFactory.addProvider(new XSSFWorkbookFactory()); + SlideShowFactory.addProvider(new XSLFSlideShowFactory()); + ExtractorFactory.addProvider(new POIXMLExtractorFactory()); + ExtractorFactory.addProvider(new MainExtractorFactory()); } /* @@ -120,6 +135,14 @@ public class BaseActivator implements BundleActivator { blacklistService.stop(context); blacklistService = null; } + + //remove poi factory + WorkbookFactory.removeProvider(HSSFWorkbookFactory.class); + WorkbookFactory.removeProvider(XSSFWorkbookFactory.class); + SlideShowFactory.removeProvider(XSLFSlideShowFactory.class); + ExtractorFactory.removeProvider(POIXMLExtractorFactory.class); + ExtractorFactory.removeProvider(MainExtractorFactory.class); + Adempiere.stop(); } diff --git a/org.adempiere.base/src/org/compiere/model/I_AD_ImportTemplate.java b/org.adempiere.base/src/org/compiere/model/I_AD_ImportTemplate.java index daa56bd6f7..54420f4dc5 100644 --- a/org.adempiere.base/src/org/compiere/model/I_AD_ImportTemplate.java +++ b/org.adempiere.base/src/org/compiere/model/I_AD_ImportTemplate.java @@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair; /** Generated Interface for AD_ImportTemplate * @author iDempiere (generated) - * @version Release 11 + * @version Release 12 */ public interface I_AD_ImportTemplate { diff --git a/org.adempiere.base/src/org/compiere/model/MImportTemplate.java b/org.adempiere.base/src/org/compiere/model/MImportTemplate.java index d0579c5f4a..bb19a22df1 100644 --- a/org.adempiere.base/src/org/compiere/model/MImportTemplate.java +++ b/org.adempiere.base/src/org/compiere/model/MImportTemplate.java @@ -34,13 +34,12 @@ import java.util.Properties; import java.util.logging.Level; import org.adempiere.exceptions.AdempiereException; -import org.apache.poi.hssf.usermodel.HSSFWorkbookFactory; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.xssf.usermodel.XSSFWorkbookFactory; +import org.apache.poi.ss.usermodel.WorkbookFactory; import org.compiere.util.CCache; import org.compiere.util.CLogger; import org.compiere.util.DB; @@ -238,8 +237,7 @@ public class MImportTemplate extends X_AD_ImportTemplate implements ImmutablePOS */ public InputStream validateFile(InputStream in) { - if ( MImportTemplate.IMPORTTEMPLATETYPE_XLS.equals(getImportTemplateType()) - || MImportTemplate.IMPORTTEMPLATETYPE_XLSX.equals(getImportTemplateType())) { + if (MImportTemplate.IMPORTTEMPLATETYPE_ExcelXLSXLSX.equals(getImportTemplateType())) { try { in = convertExcelToCSV(in); } catch (Exception e) { @@ -308,17 +306,7 @@ public class MImportTemplate extends X_AD_ImportTemplate implements ImmutablePOS */ public InputStream convertExcelToCSV(InputStream excelIs) throws IOException { - Workbook workbook = null; - if (MImportTemplate.IMPORTTEMPLATETYPE_XLS.equals(getImportTemplateType())) { - HSSFWorkbookFactory xlsWbf = new HSSFWorkbookFactory(); - workbook = xlsWbf.create(excelIs); - } else if (MImportTemplate.IMPORTTEMPLATETYPE_XLSX.equals(getImportTemplateType())) { - XSSFWorkbookFactory xlsxWbf = new XSSFWorkbookFactory(); - workbook = xlsxWbf.create(excelIs); - } else { - // unexpected error - throw new AdempiereException("Wrong template type -> " + getImportTemplateType()); - } + Workbook workbook = WorkbookFactory.create(excelIs); List colTypes = calculateAndValidateColumnTypes(); diff --git a/org.adempiere.base/src/org/compiere/model/X_AD_ImportTemplate.java b/org.adempiere.base/src/org/compiere/model/X_AD_ImportTemplate.java index 5cc228ef54..f114b05a72 100644 --- a/org.adempiere.base/src/org/compiere/model/X_AD_ImportTemplate.java +++ b/org.adempiere.base/src/org/compiere/model/X_AD_ImportTemplate.java @@ -23,7 +23,7 @@ import org.compiere.util.KeyNamePair; /** Generated Model for AD_ImportTemplate * @author iDempiere (generated) - * @version Release 11 - $Id$ */ + * @version Release 12 - $Id$ */ @org.adempiere.base.Model(table="AD_ImportTemplate") public class X_AD_ImportTemplate extends PO implements I_AD_ImportTemplate, I_Persistent { @@ -31,7 +31,7 @@ public class X_AD_ImportTemplate extends PO implements I_AD_ImportTemplate, I_Pe /** * */ - private static final long serialVersionUID = 20240327L; + private static final long serialVersionUID = 20240513L; /** Standard Constructor */ public X_AD_ImportTemplate (Properties ctx, int AD_ImportTemplate_ID, String trxName) @@ -300,12 +300,12 @@ public class X_AD_ImportTemplate extends PO implements I_AD_ImportTemplate, I_Pe /** ImportTemplateType AD_Reference_ID=200268 */ public static final int IMPORTTEMPLATETYPE_AD_Reference_ID=200268; - /** CSV = CSV */ - public static final String IMPORTTEMPLATETYPE_CSV = "CSV"; + /** Comma-separated values (CSV) = CSV */ + public static final String IMPORTTEMPLATETYPE_Comma_SeparatedValuesCSV = "CSV"; /** XLS = XLS */ public static final String IMPORTTEMPLATETYPE_XLS = "XLS"; - /** XLSX = XLSX */ - public static final String IMPORTTEMPLATETYPE_XLSX = "XLSX"; + /** Excel (XLS/XLSX) = XLSX */ + public static final String IMPORTTEMPLATETYPE_ExcelXLSXLSX = "XLSX"; /** Set Import Template Type. @param ImportTemplateType Import Template Type */ diff --git a/org.adempiere.install/install.app.launch b/org.adempiere.install/install.app.launch index f3dd7b73b3..cf69899ccc 100644 --- a/org.adempiere.install/install.app.launch +++ b/org.adempiere.install/install.app.launch @@ -58,6 +58,7 @@ + diff --git a/org.adempiere.install/install.console.app.launch b/org.adempiere.install/install.console.app.launch index 1527c6d139..4e6ab62e21 100644 --- a/org.adempiere.install/install.console.app.launch +++ b/org.adempiere.install/install.console.app.launch @@ -58,6 +58,7 @@ + diff --git a/org.adempiere.install/install.silent.app.launch b/org.adempiere.install/install.silent.app.launch index cad6279072..d8435d2c56 100644 --- a/org.adempiere.install/install.silent.app.launch +++ b/org.adempiere.install/install.silent.app.launch @@ -58,6 +58,7 @@ + diff --git a/org.adempiere.server-feature/setup/configuration/config.ini b/org.adempiere.server-feature/setup/configuration/config.ini index 9dfa2bb61a..15ed10c22c 100644 --- a/org.adempiere.server-feature/setup/configuration/config.ini +++ b/org.adempiere.server-feature/setup/configuration/config.ini @@ -30,6 +30,7 @@ osgi.bundles=org.apache.felix.scr@1:start,\ wrapped.org.apache.poi.poi-ooxml,\ wrapped.org.apache.poi.poi-ooxml-lite,\ org.apache.commons.commons-compress,\ + org.apache.commons.commons-io,\ wrapped.org.apache.xmlbeans.xmlbeans,\ org.eclipse.core.jobs,\ org.eclipse.equinox.preferences,\ diff --git a/org.idempiere.test/pom.xml b/org.idempiere.test/pom.xml index 40f3818142..c447127b41 100644 --- a/org.idempiere.test/pom.xml +++ b/org.idempiere.test/pom.xml @@ -126,9 +126,14 @@ eclipse-plugin - org.idempiere.tablepartition - 0.0.0 - + org.idempiere.tablepartition + 0.0.0 + + + eclipse-plugin + wrapped.org.apache.xmlbeans.xmlbeans + 0.0.0 + From 6d9c35a3656ee03670a6a96cb7f0587cae3e00dc Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Tue, 11 Jun 2024 09:15:45 +0200 Subject: [PATCH 047/169] IDEMPIERE-6168 Found problems with jasper setting filename (#2390) --- .../src/org/compiere/print/ReportEngine.java | 4 ++-- .../src/org/compiere/process/ServerProcessCtl.java | 5 +++++ .../org/adempiere/report/jasper/ReportStarter.java | 11 ++--------- .../src/org/compiere/print/ReportCtl.java | 8 ++++++-- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/print/ReportEngine.java b/org.adempiere.base/src/org/compiere/print/ReportEngine.java index e7814ae70a..3168acee80 100644 --- a/org.adempiere.base/src/org/compiere/print/ReportEngine.java +++ b/org.adempiere.base/src/org/compiere/print/ReportEngine.java @@ -1686,10 +1686,10 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) if (DocSubTypeSO == null) DocSubTypeSO = ""; // WalkIn Receipt, WalkIn Invoice, - if (DocSubTypeSO.equals("WR") || DocSubTypeSO.equals("WI")) + if (DocSubTypeSO.equals(MOrder.DocSubTypeSO_POS) || DocSubTypeSO.equals(MOrder.DocSubTypeSO_OnCredit)) what[0] = INVOICE; // WalkIn Pickup, - else if (DocSubTypeSO.equals("WP")) + else if (DocSubTypeSO.equals(MOrder.DocSubTypeSO_Warehouse)) what[0] = SHIPMENT; // Offer Binding, Offer Nonbinding, Standard Order else diff --git a/org.adempiere.base/src/org/compiere/process/ServerProcessCtl.java b/org.adempiere.base/src/org/compiere/process/ServerProcessCtl.java index 7106492551..99dcff6bc4 100644 --- a/org.adempiere.base/src/org/compiere/process/ServerProcessCtl.java +++ b/org.adempiere.base/src/org/compiere/process/ServerProcessCtl.java @@ -256,6 +256,11 @@ public class ServerProcessCtl implements Runnable { m_pi.setReportingProcess(true); m_pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); startProcess(); + if (m_pi.isError()) { + MPInstance pinstance = new MPInstance(Env.getCtx(), m_pi.getAD_PInstance_ID(), null); + pinstance.setErrorMsg(m_pi.getSummary()); + pinstance.saveEx(); + } return; } diff --git a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java index a1eb39be90..34f0644f51 100644 --- a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java +++ b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java @@ -229,10 +229,7 @@ public class ReportStarter implements ProcessCall, ClientProcess String[] reportPathList = reportFilePath.split(";"); for (String reportPath : reportPathList) { if (Util.isEmpty(reportPath, true)) - { - pi.setSummary("Invalid report file path: " + reportFilePath, true); - return false; - } + throw new AdempiereException("Invalid report file path: " + reportFilePath); if (reportPath.startsWith("@#LocalHttpAddr@")) { String localaddr = Env.getContext(Env.getCtx(), Env.LOCAL_HTTP_ADDRESS); if (!Util.isEmpty(localaddr)) { @@ -263,11 +260,7 @@ public class ReportStarter implements ProcessCall, ClientProcess } if (reportFile == null && reportURL == null) - { - String tmp = "Can not load report from path: " + reportPath; - pi.setSummary(tmp, true); - return false; - } + throw new AdempiereException("Can not load report from path: " + reportPath); JasperInfo jasperInfo = reportFile != null ? getJasperInfo(reportFile) : getJasperInfo(reportURL); JasperReport jasperReport = jasperInfo.getJasperReport(); diff --git a/org.adempiere.ui/src/org/compiere/print/ReportCtl.java b/org.adempiere.ui/src/org/compiere/print/ReportCtl.java index 2718753393..272cfc54de 100644 --- a/org.adempiere.ui/src/org/compiere/print/ReportCtl.java +++ b/org.adempiere.ui/src/org/compiere/print/ReportCtl.java @@ -476,8 +476,12 @@ public class ReportCtl // ============================== if(format.getJasperProcess_ID() > 0) { - ServerReportCtl.runJasperProcess(Record_ID, re, IsDirectPrint, printerName); - if (IsDirectPrint) { + int jasperRecordId = Record_ID; + if (re.getPrintInfo() != null && re.getPrintInfo().getRecord_ID() > 0) + jasperRecordId = re.getPrintInfo().getRecord_ID(); + boolean result = ServerReportCtl.runJasperProcess(jasperRecordId, re, IsDirectPrint, printerName); + if (result && IsDirectPrint) + { ReportEngine.printConfirm(type, Record_ID); } } From 689573c90d33198e90c3d04259302ca1d5ff9d0c Mon Sep 17 00:00:00 2001 From: Diego Ruiz <12065321+d-ruiz@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:51:58 +0200 Subject: [PATCH 048/169] =?UTF-8?q?IDEMPIERE-6165=20-=20Custom=20name=20is?= =?UTF-8?q?=20not=20taken=20when=20running=20a=20core=20report=20=E2=80=A6?= =?UTF-8?q?=20(#2386)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * IDEMPIERE-6165 - Custom name is not taken when running a core report programatically * IDEMPIERE-6165 - Add unit test * IDEMPIERE-6165 - Peer review changes * IDEMPIERE-6165 - Modified condition to avoid potential NPE * IDEMPIERE-6165 - Changed Unit Test to be thread safe * IDEMPIERE-6165 - Create file in a thread-safe way respecting folder name if existing * IDEMPIERE-6165 - Updated Unit test * IDEMPIERE-6165 - Updated Unit test * IDEMPIERE-6165 - Code refactor to remove code duplication in different classes * IDEMPIERE-6165 - Improved Javadoc --- .../src/org/compiere/print/ReportEngine.java | 24 ++---- .../org/compiere/print/ServerReportCtl.java | 2 + .../src/org/compiere/tools/FileUtil.java | 70 +++++++++++++++-- .../report/jasper/ReportStarter.java | 25 ++---- .../org/idempiere/test/base/ReportTest.java | 76 +++++++++++++++++++ 5 files changed, 154 insertions(+), 43 deletions(-) create mode 100644 org.idempiere.test/src/org/idempiere/test/base/ReportTest.java diff --git a/org.adempiere.base/src/org/compiere/print/ReportEngine.java b/org.adempiere.base/src/org/compiere/print/ReportEngine.java index 3168acee80..232075d855 100644 --- a/org.adempiere.base/src/org/compiere/print/ReportEngine.java +++ b/org.adempiere.base/src/org/compiere/print/ReportEngine.java @@ -857,7 +857,8 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".pdf"); + file = (m_pi != null && !Util.isEmpty(m_pi.getPDFFileName(),true)) ? FileUtil.createFile(m_pi.getPDFFileName()) : + FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".pdf"); } catch (IOException e) { @@ -888,7 +889,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".html"); + file = FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".html"); } catch (IOException e) { @@ -919,7 +920,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".csv"); + file = FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".csv"); } catch (IOException e) { @@ -950,7 +951,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".xls"); + file = FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".xls"); } catch (IOException e) { @@ -988,7 +989,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".xlsx"); + file = FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".xlsx"); } catch (IOException e) { @@ -1064,19 +1065,6 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) return file2.exists(); } // createPDF - private String makePrefix(String name) { - StringBuilder prefix = new StringBuilder(); - char[] nameArray = name.toCharArray(); - for (char ch : nameArray) { - if (Character.isLetterOrDigit(ch)) { - prefix.append(ch); - } else { - prefix.append("_"); - } - } - return prefix.toString(); - } - /** * Create PDF as Data array * @return pdf data diff --git a/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java b/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java index 87a1f7ac03..1ade000110 100644 --- a/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java +++ b/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java @@ -94,6 +94,7 @@ public class ServerReportCtl { { if (pi != null && pi.isBatch() && pi.isPrintPreview()) { + re.setProcessInfo(pi); if ("HTML".equals(pi.getReportType())) { pi.setExport(true); @@ -163,6 +164,7 @@ public class ServerReportCtl { if (pi != null) { jasperProcessInfo.setPrintPreview(pi.isPrintPreview()); jasperProcessInfo.setIsBatch(pi.isBatch()); + jasperProcessInfo.setPDFFileName(pi.getPDFFileName()); } else { jasperProcessInfo.setPrintPreview( !IsDirectPrint ); } diff --git a/org.adempiere.base/src/org/compiere/tools/FileUtil.java b/org.adempiere.base/src/org/compiere/tools/FileUtil.java index f60aceeafd..17cd380c67 100644 --- a/org.adempiere.base/src/org/compiere/tools/FileUtil.java +++ b/org.adempiere.base/src/org/compiere/tools/FileUtil.java @@ -475,12 +475,7 @@ public class FileUtil if (suffix == null) suffix = ".tmp"; - Calendar cal = Calendar.getInstance(); - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); - String dt = sdf.format(cal.getTime()); - String tmpdirname = (directory != null) ? directory.getCanonicalPath() : System.getProperty("java.io.tmpdir"); - tmpdirname += System.getProperty("file.separator") + "rpttmp_" + dt + "_" + Env.getContext(Env.getCtx(), Env.AD_SESSION_ID) + System.getProperty("file.separator"); - + String tmpdirname = getTempFolderName(directory); File tmpdir = new File(tmpdirname); tmpdir.mkdirs(); @@ -490,11 +485,74 @@ public class FileUtil return f; } + + /** + * Generates a unique temporary folder name based on the current timestamp and session ID.
+ * The folder name is either within the specified directory or the default temporary directory. + * + * @param directory the base directory where the temporary folder will be created; + * if null, the system's default temporary directory is used + * @return a string representing the path to the unique temporary folder + * @throws IOException + */ + public static String getTempFolderName(File directory) throws IOException { + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + String dt = sdf.format(cal.getTime()); + String tmpdirname = (directory != null) ? directory.getCanonicalPath() : System.getProperty("java.io.tmpdir"); + tmpdirname += System.getProperty("file.separator") + "rpttmp_" + dt + "_" + Env.getContext(Env.getCtx(), Env.AD_SESSION_ID) + System.getProperty("file.separator"); + + return tmpdirname; + } public static File createTempFile(String prefix, String suffix) throws IOException { return createTempFile(prefix, suffix, null); } + + /** + * Creates a file with the given filename.
+ * If the filename includes the path, the file is created as requested.
+ * If it only includes the name, the file is created in a thread-safe temporary folder. + * @param fileName + * @return file + * @throws IOException + */ + public static File createFile(String fileName) throws IOException { + if (Util.isEmpty(fileName)) + throw new IllegalArgumentException("Name is required"); + + File file = null; + if (fileName.contains(System.getProperty("file.separator"))) { + file = new File(fileName); + } else { + String tmpdirname = getTempFolderName(null); + File tmpdir = new File(tmpdirname); + tmpdir.mkdirs(); + + file = new File(tmpdirname, fileName); + } + + return file; + } + + /** + * Creates a valid file name prefix from "name" + * @param name + * @return file name prefix + */ + public static String makePrefix(String name) { + StringBuilder prefix = new StringBuilder(); + char[] nameArray = name.toCharArray(); + for (char ch : nameArray) { + if (Character.isLetterOrDigit(ch)) { + prefix.append(ch); + } else { + prefix.append("_"); + } + } + return prefix.toString(); + } /** * diff --git a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java index 34f0644f51..94b40863aa 100644 --- a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java +++ b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java @@ -492,7 +492,7 @@ public class ReportStarter implements ProcessCall, ClientProcess processInfo.setPDFReport(batchPDFExportList.get(0)); } else { try { - File pdfFile = File.createTempFile(makePrefix(processInfo.getTitle()), ".pdf"); + File pdfFile = File.createTempFile(FileUtil.makePrefix(processInfo.getTitle()), ".pdf"); Util.mergePdf(batchPDFExportList, pdfFile); processInfo.setPDFReport(pdfFile); } catch (Exception e) { @@ -542,7 +542,7 @@ public class ReportStarter implements ProcessCall, ClientProcess } private File createMultiFileArchive(List exportFileList) throws Exception { - File archiveFile = File.createTempFile(makePrefix(processInfo.getTitle()), ".zip"); + File archiveFile = File.createTempFile(FileUtil.makePrefix(processInfo.getTitle()), ".zip"); try (FileOutputStream out = new FileOutputStream(archiveFile)) { try (ZipOutputStream zip = new ZipOutputStream(out);) { zip.setMethod(ZipOutputStream.DEFLATED); @@ -575,9 +575,9 @@ public class ReportStarter implements ProcessCall, ClientProcess { File pdfFile = null; if (processInfo.getPDFFileName() != null) { - pdfFile = new File(processInfo.getPDFFileName()); + pdfFile = FileUtil.createFile(processInfo.getPDFFileName()); } else { - pdfFile = File.createTempFile(makePrefix(jasperPrint.getName()), ".pdf"); + pdfFile = File.createTempFile(FileUtil.makePrefix(jasperPrint.getName()), ".pdf"); } JRPdfExporter exporter = new JRPdfExporter(jasperReportContext); @@ -637,7 +637,7 @@ public class ReportStarter implements ProcessCall, ClientProcess else newQueryText = originalQueryText + " WHERE " + query.toString(); - File jrxmlFile = File.createTempFile(makePrefix(jasperReport.getName()), ".jrxml"); + File jrxmlFile = File.createTempFile(FileUtil.makePrefix(jasperReport.getName()), ".jrxml"); JRXmlWriter.writeReport(jasperReport, new FileOutputStream(jrxmlFile), "UTF-8"); JasperDesign jasperDesign = JRXmlLoader.load(jrxmlFile); @@ -706,7 +706,7 @@ public class ReportStarter implements ProcessCall, ClientProcess if (ext == null) ext = "pdf"; try { - File exportFile = File.createTempFile(makePrefix(jasperPrint.getName()), "." + ext); + File exportFile = File.createTempFile(FileUtil.makePrefix(jasperPrint.getName()), "." + ext); try (FileOutputStream outputStream = new FileOutputStream(exportFile);) { @@ -826,19 +826,6 @@ public class ReportStarter implements ProcessCall, ClientProcess return viewerLauncher; } - private String makePrefix(String name) { - StringBuilder prefix = new StringBuilder(); - char[] nameArray = name.toCharArray(); - for (char ch : nameArray) { - if (Character.isLetterOrDigit(ch)) { - prefix.append(ch); - } else { - prefix.append("_"); - } - } - return prefix.toString(); - } - private WebResourceLoader getWebResourceLoader() { if (webResourceLoader == null) webResourceLoader = new WebResourceLoader(getLocalDownloadFolder()); diff --git a/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java b/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java new file mode 100644 index 0000000000..eab18899e8 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java @@ -0,0 +1,76 @@ +/*********************************************************************** + * 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: * + * - Carlos Ruiz - globalqss * + **********************************************************************/ +package org.idempiere.test.base; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; + +import org.compiere.model.MOrder; +import org.compiere.model.MPInstance; +import org.compiere.model.MProcess; +import org.compiere.process.ProcessInfo; +import org.compiere.process.ServerProcessCtl; +import org.compiere.util.Env; +import org.idempiere.test.AbstractTestCase; +import org.junit.jupiter.api.Test; + +/** + * @author Diego Ruiz - BX Service GmbH + */ +public class ReportTest extends AbstractTestCase { + + public ReportTest() { + } + + private static final int Order_Print_Process = 110; + + /** + * https://idempiere.atlassian.net/browse/IDEMPIERE-6165 + */ + @Test + public void testPDFFileName() { + MProcess orderReport = MProcess.get(Env.getCtx(), Order_Print_Process); + MOrder order = new MOrder(Env.getCtx(), 108, getTrxName()); // Garden Order 60000 + + String fileName = order.getDocumentNo() + ".pdf"; + + ProcessInfo pi = new ProcessInfo(orderReport.getName(), orderReport.getAD_Process_ID()); + pi.setRecord_ID(order.getC_Order_ID()); + pi.setAD_Client_ID(Env.getAD_Client_ID(Env.getCtx())); + pi.setTable_ID(order.get_Table_ID()); + pi.setPrintPreview(true); + pi.setIsBatch(true); + pi.setPDFFileName(fileName); + pi.setReportType("PDF"); + MPInstance instance = new MPInstance(orderReport, order.get_Table_ID(), order.getC_Order_ID(), order.getC_Order_UU()); + instance.saveEx(); + ServerProcessCtl.process(pi, null); + File file = pi.getPDFReport(); + + assertEquals(file.getName(), fileName); + + } +} From a45283b85530dc5cf1df8f78bffb85374b3f06b2 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 13 Jun 2024 03:39:44 +0200 Subject: [PATCH 049/169] IDEMPIERE-6165 + IDEMPIERE-6168 More problems with jasper setting filename (#2392) - the test case is failing for orders with jasper print format - the downloadable file name for jasper is wrong in zk when using FileNamePattern --- .../src/org/compiere/print/ServerReportCtl.java | 5 ++++- .../WEB-INF/src/org/adempiere/webui/window/ZkJRViewer.java | 2 +- .../src/org/idempiere/test/base/ReportTest.java | 5 ++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java b/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java index 1ade000110..7cd9babe63 100644 --- a/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java +++ b/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java @@ -85,7 +85,10 @@ public class ServerReportCtl { // ============================== if(format.getJasperProcess_ID() > 0) { - boolean result = runJasperProcess(Record_ID, re, true, printerName, pi); + int jasperRecordId = Record_ID; + if (re.getPrintInfo() != null && re.getPrintInfo().getRecord_ID() > 0) + jasperRecordId = re.getPrintInfo().getRecord_ID(); + boolean result = runJasperProcess(jasperRecordId, re, true, printerName, pi); return(result); } else diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkJRViewer.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkJRViewer.java index db8be74326..9bc5902cc1 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkJRViewer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkJRViewer.java @@ -427,7 +427,7 @@ public class ZkJRViewer extends Window implements EventListener, ITabOnCl mediaSuppliers.put(toMediaType(PDF_MIME_TYPE, PDF_FILE_EXT), () -> { try { attachment = getPDF(); - return new AMedia(m_title+"."+PDF_FILE_EXT, PDF_FILE_EXT, PDF_MIME_TYPE, attachment, true); + return new AMedia(attachment.getName(), PDF_FILE_EXT, PDF_MIME_TYPE, attachment, true); } catch (Exception e) { if (e instanceof RuntimeException) throw (RuntimeException)e; diff --git a/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java b/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java index eab18899e8..9d806bd423 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java @@ -31,6 +31,7 @@ import java.io.File; import org.compiere.model.MOrder; import org.compiere.model.MPInstance; import org.compiere.model.MProcess; +import org.compiere.model.SystemIDs; import org.compiere.process.ProcessInfo; import org.compiere.process.ServerProcessCtl; import org.compiere.util.Env; @@ -45,14 +46,12 @@ public class ReportTest extends AbstractTestCase { public ReportTest() { } - private static final int Order_Print_Process = 110; - /** * https://idempiere.atlassian.net/browse/IDEMPIERE-6165 */ @Test public void testPDFFileName() { - MProcess orderReport = MProcess.get(Env.getCtx(), Order_Print_Process); + MProcess orderReport = MProcess.get(Env.getCtx(), SystemIDs.PROCESS_RPT_C_ORDER); MOrder order = new MOrder(Env.getCtx(), 108, getTrxName()); // Garden Order 60000 String fileName = order.getDocumentNo() + ".pdf"; From 5f44c9c19de33cd6259eb771946c8254099e7d32 Mon Sep 17 00:00:00 2001 From: Diego Ruiz <12065321+d-ruiz@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:10:43 +0100 Subject: [PATCH 050/169] IDEMPIERE-2981 - Implement JSON field type (#2255) * IDEMPIERE-2981 - Implement JSON Field Type * IDEMPIERE-2981 - Added field to the Test Window and Unit Tests * IDEMPIERE-2981 - Fixed migration scripts * IDEMPIERE-2981 - Changed oracle json syntax * IDEMPIERE-2981 - Increased the number of lines of jsonData test field * IDEMPIERE-2981 - Validate and Prettify JSON string on the field editor * IDEMPIERE-2981 - Support for oracle * IDEMPIERE-2981 - Applied patch from Carlos Ruiz --- .../oracle/202402261300_IDEMPIERE-2981.sql | 10 +++ .../oracle/202402261354_IDEMPIERE-2981.sql | 30 ++++++++ .../202402261300_IDEMPIERE-2981.sql | 7 ++ .../202402261354_IDEMPIERE-2981.sql | 27 +++++++ .../org/compiere/process/TabCreateFields.java | 2 +- .../adempiere/impexp/GridTabCSVImporter.java | 2 +- .../org/compiere/db/AdempiereDatabase.java | 16 +++++ .../src/org/compiere/model/GridField.java | 1 + .../src/org/compiere/model/I_Test.java | 23 ++++-- .../src/org/compiere/model/MColumn.java | 2 +- .../src/org/compiere/model/PO.java | 8 ++- .../src/org/compiere/model/PO_LOB.java | 2 +- .../src/org/compiere/model/SystemIDs.java | 1 + .../src/org/compiere/model/X_Test.java | 20 +++++- .../src/org/compiere/print/DataEngine.java | 2 +- .../src/org/compiere/util/DB.java | 18 +++++ .../src/org/compiere/util/DisplayType.java | 18 +++-- .../src/org/compiere/util/Util.java | 21 ++++++ .../src/org/adempiere/pipo2/PoFiller.java | 3 +- .../webui/apps/ProcessParameterPanel.java | 1 + .../adempiere/webui/editor/WJsonEditor.java | 39 +++++++++++ .../webui/factory/DefaultEditorFactory.java | 5 ++ .../src/org/compiere/db/DB_Oracle.java | 23 ++++++ .../src/org/compiere/db/DB_PostgreSQL.java | 30 +++++++- .../idempiere/test/base/JsonFieldTest.java | 70 +++++++++++++++++++ 25 files changed, 360 insertions(+), 21 deletions(-) create mode 100644 migration/iD11/oracle/202402261300_IDEMPIERE-2981.sql create mode 100644 migration/iD11/oracle/202402261354_IDEMPIERE-2981.sql create mode 100644 migration/iD11/postgresql/202402261300_IDEMPIERE-2981.sql create mode 100644 migration/iD11/postgresql/202402261354_IDEMPIERE-2981.sql create mode 100644 org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WJsonEditor.java create mode 100644 org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java diff --git a/migration/iD11/oracle/202402261300_IDEMPIERE-2981.sql b/migration/iD11/oracle/202402261300_IDEMPIERE-2981.sql new file mode 100644 index 0000000000..aab134c6d2 --- /dev/null +++ b/migration/iD11/oracle/202402261300_IDEMPIERE-2981.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-2981 - Implement JSON Field type +SELECT register_migration_script('202402261300_IDEMPIERE-2981.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Feb 26, 2024, 1:00:29 PM CET +INSERT INTO AD_Reference (AD_Reference_ID,Name,Description,ValidationType,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,IsOrderByValue,AD_Reference_UU,ShowInactive) VALUES (200267,'JSON','JSON format values','D',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,'D','N','b6fcc751-edd8-4421-acd0-3cde02a9576d','N') +; + diff --git a/migration/iD11/oracle/202402261354_IDEMPIERE-2981.sql b/migration/iD11/oracle/202402261354_IDEMPIERE-2981.sql new file mode 100644 index 0000000000..d43ee4bb10 --- /dev/null +++ b/migration/iD11/oracle/202402261354_IDEMPIERE-2981.sql @@ -0,0 +1,30 @@ +-- IDEMPIERE-2981 - Implement JSON Field type +SELECT register_migration_script('202402261354_IDEMPIERE-2981.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Feb 26, 2024, 1:54:35 PM CET +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203924,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,'JsonData','JSON Data','The json field stores json data.','JSON Data','D','c4ea7a81-96a9-4a5d-bb87-e913e1c8ed48') +; + +-- Feb 26, 2024, 1:55:37 PM CET +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216570,0,'JSON Data','The json field stores json data.',135,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','927b83df-d161-4332-ad44-8ffed99e8cf4','Y',0,'N','N','N','N') +; + +-- Feb 28, 2024, 5:41:55 PM CET +ALTER TABLE Test ADD JsonData CLOB DEFAULT NULL CONSTRAINT test_jsondata_ij CHECK (JsonData IS JSON) +; + +-- Feb 26, 2024, 1:56:08 PM CET +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan) VALUES (208472,'JSON Data','The json field stores json data.',152,216570,'Y',100,310,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','e47ef529-71ba-4f9b-9014-b188e17e8ef4','Y',290,5) +; + +-- Feb 29, 2024, 1:52:50 PM CET +UPDATE AD_Field SET NumLines=5,Updated=TO_TIMESTAMP('2024-02-29 13:52:50','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208472 +; + +-- Feb 29, 2024, 2:07:30 PM CET +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Invalid JSON',0,0,'Y',TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,200876,'InvalidJSON','D','a263376f-a12e-4943-92f1-7d7ce8a67a2b') +; + diff --git a/migration/iD11/postgresql/202402261300_IDEMPIERE-2981.sql b/migration/iD11/postgresql/202402261300_IDEMPIERE-2981.sql new file mode 100644 index 0000000000..e12163649a --- /dev/null +++ b/migration/iD11/postgresql/202402261300_IDEMPIERE-2981.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-2981 - Implement JSON Field type +SELECT register_migration_script('202402261300_IDEMPIERE-2981.sql') FROM dual; + +-- Feb 26, 2024, 1:00:29 PM CET +INSERT INTO AD_Reference (AD_Reference_ID,Name,Description,ValidationType,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,IsOrderByValue,AD_Reference_UU,ShowInactive) VALUES (200267,'JSON','JSON format values','D',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,'D','N','b6fcc751-edd8-4421-acd0-3cde02a9576d','N') +; + diff --git a/migration/iD11/postgresql/202402261354_IDEMPIERE-2981.sql b/migration/iD11/postgresql/202402261354_IDEMPIERE-2981.sql new file mode 100644 index 0000000000..57daa577b4 --- /dev/null +++ b/migration/iD11/postgresql/202402261354_IDEMPIERE-2981.sql @@ -0,0 +1,27 @@ +-- IDEMPIERE-2981 - Implement JSON Field type +SELECT register_migration_script('202402261354_IDEMPIERE-2981.sql') FROM dual; + +-- Feb 26, 2024, 1:54:35 PM CET +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203924,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,'JsonData','JSON Data','The json field stores json data.','JSON Data','D','c4ea7a81-96a9-4a5d-bb87-e913e1c8ed48') +; + +-- Feb 26, 2024, 1:55:37 PM CET +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216570,0,'JSON Data','The json field stores json data.',135,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','927b83df-d161-4332-ad44-8ffed99e8cf4','Y',0,'N','N','N','N') +; + +-- Feb 26, 2024, 1:55:55 PM CET +ALTER TABLE Test ADD COLUMN JsonData JSON DEFAULT NULL +; + +-- Feb 26, 2024, 1:56:08 PM CET +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan) VALUES (208472,'JSON Data','The json field stores json data.',152,216570,'Y',100,310,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','e47ef529-71ba-4f9b-9014-b188e17e8ef4','Y',290,5) +; + +-- Feb 29, 2024, 1:52:50 PM CET +UPDATE AD_Field SET NumLines=5,Updated=TO_TIMESTAMP('2024-02-29 13:52:50','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208472 +; + +-- Feb 29, 2024, 2:07:30 PM CET +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Invalid JSON',0,0,'Y',TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,200876,'InvalidJSON','D','a263376f-a12e-4943-92f1-7d7ce8a67a2b') +; + diff --git a/org.adempiere.base.process/src/org/compiere/process/TabCreateFields.java b/org.adempiere.base.process/src/org/compiere/process/TabCreateFields.java index f3d7559dbd..6ee3e406d5 100644 --- a/org.adempiere.base.process/src/org/compiere/process/TabCreateFields.java +++ b/org.adempiere.base.process/src/org/compiere/process/TabCreateFields.java @@ -167,7 +167,7 @@ public class TabCreateFields extends SvrProcess } if (column.getAD_Reference_ID() == DisplayType.Text) { field.setNumLines(3); - } else if (column.getAD_Reference_ID() == DisplayType.TextLong) { + } else if (column.getAD_Reference_ID() == DisplayType.TextLong || column.getAD_Reference_ID() == DisplayType.JSON) { field.setNumLines(5); } else if (column.getAD_Reference_ID() == DisplayType.Memo) { field.setNumLines(8); diff --git a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java index 2b4e28bddc..e315b44a65 100644 --- a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java +++ b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java @@ -1421,7 +1421,7 @@ public class GridTabCSVImporter implements IGridTabImporter return (new Optional(new ParseBigDecimal(new DecimalFormatSymbols(Language.getLoginLanguage().getLocale())))); } else if (DisplayType.YesNo == field.getDisplayType()) { return (new Optional(new ParseBool("y", "n"))); - } else if (DisplayType.TextLong == field.getDisplayType()) { + } else if (DisplayType.TextLong == field.getDisplayType() || DisplayType.JSON == field.getDisplayType()) { return (new Optional(new StrMinMax(1, Long.MAX_VALUE))); } else if (DisplayType.isText(field.getDisplayType())) { return (new Optional(new StrMinMax(1, field.getFieldLength()))); diff --git a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java index 84d1034f25..6be146daa3 100644 --- a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java +++ b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java @@ -202,7 +202,18 @@ public interface AdempiereDatabase */ public String TO_NUMBER (BigDecimal number, int displayType); + /** + * Return string as JSON object for INSERT statements + * @param value + * @return value as JSON + */ + public String TO_JSON (String value); + /** + * @return string with right casting for JSON inserts + */ + public String getJSONCast (); + /** * Get next sequence number in this Sequence * @param Name Sequence name @@ -424,6 +435,11 @@ public interface AdempiereDatabase */ public String getClobDataType(); + /** + * @return json object data type name + */ + public String getJsonDataType(); + /** * @return time stamp data type name */ diff --git a/org.adempiere.base/src/org/compiere/model/GridField.java b/org.adempiere.base/src/org/compiere/model/GridField.java index 69ac181f94..27e2c65c12 100644 --- a/org.adempiere.base/src/org/compiere/model/GridField.java +++ b/org.adempiere.base/src/org/compiere/model/GridField.java @@ -2106,6 +2106,7 @@ public class GridField if (m_vo.displayType == DisplayType.Text || m_vo.displayType == DisplayType.Memo || m_vo.displayType == DisplayType.TextLong + || m_vo.displayType == DisplayType.JSON || m_vo.displayType == DisplayType.Binary || m_vo.displayType == DisplayType.RowID || isEncrypted()) diff --git a/org.adempiere.base/src/org/compiere/model/I_Test.java b/org.adempiere.base/src/org/compiere/model/I_Test.java index 5c508de115..5cdda1cdae 100644 --- a/org.adempiere.base/src/org/compiere/model/I_Test.java +++ b/org.adempiere.base/src/org/compiere/model/I_Test.java @@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair; /** Generated Interface for Test * @author iDempiere (generated) - * @version Release 11 + * @version Release 12 */ public interface I_Test { @@ -55,8 +55,8 @@ public interface I_Test /** Column name AD_Client_ID */ public static final String COLUMNNAME_AD_Client_ID = "AD_Client_ID"; - /** Get Tenant. - * Tenant for this installation. + /** Get Client. + * Client/Tenant for this installation. */ public int getAD_Client_ID(); @@ -64,12 +64,12 @@ public interface I_Test public static final String COLUMNNAME_AD_Org_ID = "AD_Org_ID"; /** Set Organization. - * Organizational entity within tenant + * Organizational entity within client */ public void setAD_Org_ID (int AD_Org_ID); /** Get Organization. - * Organizational entity within tenant + * Organizational entity within client */ public int getAD_Org_ID(); @@ -253,6 +253,19 @@ public interface I_Test */ public boolean isActive(); + /** Column name JsonData */ + public static final String COLUMNNAME_JsonData = "JsonData"; + + /** Set JSON Data. + * The json field stores json data. + */ + public void setJsonData (Object JsonData); + + /** Get JSON Data. + * The json field stores json data. + */ + public Object getJsonData(); + /** Column name M_Locator_ID */ public static final String COLUMNNAME_M_Locator_ID = "M_Locator_ID"; diff --git a/org.adempiere.base/src/org/compiere/model/MColumn.java b/org.adempiere.base/src/org/compiere/model/MColumn.java index 7757e91dd2..5284eb7351 100644 --- a/org.adempiere.base/src/org/compiere/model/MColumn.java +++ b/org.adempiere.base/src/org/compiere/model/MColumn.java @@ -380,7 +380,7 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport } int displayType = getAD_Reference_ID(); - if (DisplayType.isLOB(displayType)) // LOBs are 0 + if (DisplayType.isLOB(displayType) || displayType == DisplayType.JSON) // LOBs are 0 { if (getFieldLength() != 0) setFieldLength(0); diff --git a/org.adempiere.base/src/org/compiere/model/PO.java b/org.adempiere.base/src/org/compiere/model/PO.java index 33834a22b6..3b25eb8544 100644 --- a/org.adempiere.base/src/org/compiere/model/PO.java +++ b/org.adempiere.base/src/org/compiere/model/PO.java @@ -115,7 +115,7 @@ public abstract class PO /** * */ - private static final long serialVersionUID = -7758079724744033518L; + private static final long serialVersionUID = 6591172659109078284L; /* String key to create a new record based in UUID constructor */ public static final String UUID_NEW_RECORD = ""; @@ -3046,6 +3046,8 @@ public abstract class PO { if (value instanceof Timestamp && dt == DisplayType.Date) sql.append("trunc(cast(? as date))"); + else if (dt == DisplayType.JSON) + sql.append(DB.getJSONCast()); else sql.append("?"); @@ -3071,7 +3073,7 @@ public abstract class PO } else { params.add(encrypt(i,value)); } - } + } else { params.add(value); @@ -3660,6 +3662,8 @@ public abstract class PO { if (value instanceof Timestamp && dt == DisplayType.Date) sqlValues.append("trunc(cast(? as date))"); + else if (dt == DisplayType.JSON) + sqlValues.append(DB.getJSONCast()); else sqlValues.append("?"); diff --git a/org.adempiere.base/src/org/compiere/model/PO_LOB.java b/org.adempiere.base/src/org/compiere/model/PO_LOB.java index a42085939d..200715ce25 100644 --- a/org.adempiere.base/src/org/compiere/model/PO_LOB.java +++ b/org.adempiere.base/src/org/compiere/model/PO_LOB.java @@ -154,7 +154,7 @@ public class PO_LOB implements Serializable try { pstmt = con.prepareStatement(sql.toString()); - if (m_displayType == DisplayType.TextLong) + if (m_displayType == DisplayType.TextLong || m_displayType == DisplayType.JSON) pstmt.setString(1, (String)m_value); else pstmt.setBytes(1, (byte[])m_value); diff --git a/org.adempiere.base/src/org/compiere/model/SystemIDs.java b/org.adempiere.base/src/org/compiere/model/SystemIDs.java index ab75753e01..f9a30434ef 100644 --- a/org.adempiere.base/src/org/compiere/model/SystemIDs.java +++ b/org.adempiere.base/src/org/compiere/model/SystemIDs.java @@ -164,6 +164,7 @@ public class SystemIDs public final static int REFERENCE_DATATYPE_TABLEDIR_UU = 200234; public final static int REFERENCE_DATATYPE_TEXT = 14; public final static int REFERENCE_DATATYPE_TEXTLONG = 36; + public final static int REFERENCE_DATATYPE_JSON = 200267; public final static int REFERENCE_DATATYPE_TIME = 24; public final static int REFERENCE_DATATYPE_TIMESTAMP_WITH_TIMEZONE = 200133; public final static int REFERENCE_DATATYPE_TIMEZONE = 200135; diff --git a/org.adempiere.base/src/org/compiere/model/X_Test.java b/org.adempiere.base/src/org/compiere/model/X_Test.java index b553753bdf..d6cfe6e8ca 100644 --- a/org.adempiere.base/src/org/compiere/model/X_Test.java +++ b/org.adempiere.base/src/org/compiere/model/X_Test.java @@ -26,7 +26,7 @@ import org.compiere.util.KeyNamePair; /** Generated Model for Test * @author iDempiere (generated) - * @version Release 11 - $Id$ */ + * @version Release 12 - $Id$ */ @org.adempiere.base.Model(table="Test") public class X_Test extends PO implements I_Test, I_Persistent { @@ -34,7 +34,7 @@ public class X_Test extends PO implements I_Test, I_Persistent /** * */ - private static final long serialVersionUID = 20231222L; + private static final long serialVersionUID = 20240226L; /** Standard Constructor */ public X_Test (Properties ctx, int Test_ID, String trxName) @@ -382,6 +382,22 @@ public class X_Test extends PO implements I_Test, I_Persistent return (String)get_Value(COLUMNNAME_Help); } + /** Set JSON Data. + @param JsonData The json field stores json data. + */ + public void setJsonData (Object JsonData) + { + set_Value (COLUMNNAME_JsonData, JsonData); + } + + /** Get JSON Data. + @return The json field stores json data. + */ + public Object getJsonData() + { + return get_Value(COLUMNNAME_JsonData); + } + public I_M_Locator getM_Locator() throws RuntimeException { return (I_M_Locator)MTable.get(getCtx(), I_M_Locator.Table_ID) diff --git a/org.adempiere.base/src/org/compiere/print/DataEngine.java b/org.adempiere.base/src/org/compiere/print/DataEngine.java index 50a2569590..64b39fb7c9 100644 --- a/org.adempiere.base/src/org/compiere/print/DataEngine.java +++ b/org.adempiere.base/src/org/compiere/print/DataEngine.java @@ -1140,7 +1140,7 @@ public class DataEngine pde = new PrintDataElement(pdc.getAD_PrintFormatItem_ID(), pdc.getColumnName(), Boolean.valueOf(b), pdc.getDisplayType(), pdc.getFormatPattern()); } } - else if (pdc.getDisplayType() == DisplayType.TextLong) + else if (pdc.getDisplayType() == DisplayType.TextLong || (pdc.getDisplayType() == DisplayType.JSON && DB.isOracle())) { String value = ""; if ("java.lang.String".equals(rs.getMetaData().getColumnClassName(counter))) diff --git a/org.adempiere.base/src/org/compiere/util/DB.java b/org.adempiere.base/src/org/compiere/util/DB.java index 54426992f8..b1f43c1ec6 100644 --- a/org.adempiere.base/src/org/compiere/util/DB.java +++ b/org.adempiere.base/src/org/compiere/util/DB.java @@ -2208,6 +2208,24 @@ public final class DB // return out.toString(); } // TO_STRING + + /** + * Return string as JSON object for INSERT statements with correct precision + * @param value + * @return value as json + */ + public static String TO_JSON (String value) + { + return s_cc.getDatabase().TO_JSON(value); + } + + /** + * @return string with right casting for JSON inserts + */ + public static String getJSONCast() + { + return s_cc.getDatabase().getJSONCast(); + } /** * Convenient method to close result set diff --git a/org.adempiere.base/src/org/compiere/util/DisplayType.java b/org.adempiere.base/src/org/compiere/util/DisplayType.java index 2a02579987..8c731ac4c1 100644 --- a/org.adempiere.base/src/org/compiere/util/DisplayType.java +++ b/org.adempiere.base/src/org/compiere/util/DisplayType.java @@ -66,6 +66,7 @@ import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_TIMEZONE; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_URL; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_UUID; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_YES_NO; +import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_JSON; import java.text.DateFormat; import java.text.DecimalFormat; @@ -196,8 +197,9 @@ public final class DisplayType public static final int RecordID = REFERENCE_DATATYPE_RECORD_ID; public static final int RecordUU = REFERENCE_DATATYPE_RECORD_UU; - + public static final int JSON = REFERENCE_DATATYPE_JSON; + public static final int TimestampWithTimeZone = REFERENCE_DATATYPE_TIMESTAMP_WITH_TIMEZONE; public static final int TimeZoneId = REFERENCE_DATATYPE_TIMEZONE; @@ -409,7 +411,7 @@ public final class DisplayType public static boolean isText(int displayType) { if (displayType == String || displayType == Text - || displayType == TextLong || displayType == Memo + || displayType == TextLong || displayType == JSON || displayType == Memo || displayType == FilePath || displayType == FileName || displayType == URL || displayType == PrinterName || displayType == SingleSelectionGrid || displayType == Color @@ -588,7 +590,8 @@ public final class DisplayType public static boolean isLOB (int displayType) { if (displayType == Binary - || displayType == TextLong) + || displayType == TextLong + || (displayType == JSON && DB.isOracle())) return true; //not custom type, don't have to check factory @@ -932,7 +935,7 @@ public final class DisplayType */ public static Class getClass (int displayType, boolean yesNoAsBoolean) { - if (isText(displayType) || displayType == List || displayType == Payment || displayType == RadiogroupList) + if (isText(displayType) || displayType == List || displayType == Payment || displayType == RadiogroupList || displayType == JSON) return String.class; else if (isID(displayType) || displayType == Integer) // note that Integer is stored as BD return Integer.class; @@ -972,6 +975,7 @@ public final class DisplayType s_customDisplayTypeNegativeCache.put(customTypeKey, Boolean.TRUE); } } + // return Object.class; } // getClass @@ -1044,7 +1048,9 @@ public final class DisplayType return getDatabase().getNumericDataType()+"(10)"; else return getDatabase().getCharacterDataType()+"(" + fieldLength + ")"; - } + } + if (displayType == DisplayType.JSON) + return getDatabase().getJsonDataType(); IServiceReferenceHolder cache = s_displayTypeFactoryCache.get(displayType); if (cache != null) { @@ -1175,6 +1181,8 @@ public final class DisplayType return "Text"; case TextLong: return "TextLong"; + case JSON: + return "JSON"; case Time: return "Time"; case TimestampWithTimeZone: diff --git a/org.adempiere.base/src/org/compiere/util/Util.java b/org.adempiere.base/src/org/compiere/util/Util.java index b1ccb1baa3..6ccc2af021 100644 --- a/org.adempiere.base/src/org/compiere/util/Util.java +++ b/org.adempiere.base/src/org/compiere/util/Util.java @@ -44,8 +44,14 @@ import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.KeyStroke; +import org.adempiere.exceptions.AdempiereException; import org.compiere.Adempiere; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.pdf.PdfContentByte; @@ -783,5 +789,20 @@ public class Util public static boolean isDeveloperMode() { return Files.isDirectory(Paths.get(Adempiere.getAdempiereHome() + File.separator + "org.adempiere.base")) || "Y".equals(System.getProperty("org.idempiere.developermode")); } + + /** + * Returns a string with a formatted JSON object + * @return string with a pretty JSON format + */ + public static String prettifyJSONString(String value) { + Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create(); + try { + JsonElement jsonElement = JsonParser.parseString(value); + return gson.toJson(jsonElement); + } catch (JsonSyntaxException e) { + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "InvalidJSON")); + } + } + } // Util diff --git a/org.adempiere.pipo/src/org/adempiere/pipo2/PoFiller.java b/org.adempiere.pipo/src/org/adempiere/pipo2/PoFiller.java index 88a1a674c6..210745f3ce 100644 --- a/org.adempiere.pipo/src/org/adempiere/pipo2/PoFiller.java +++ b/org.adempiere.pipo/src/org/adempiere/pipo2/PoFiller.java @@ -397,7 +397,8 @@ public class PoFiller{ setInteger(qName); } else if (info.getColumnClass(index) == Timestamp.class) { setTimestamp(qName); - }else if(DisplayType.TextLong == info.getColumnDisplayType(index)) {// export column from system have type is normal string, but import to system have this column but type is textlong (mean blob) + } else if(DisplayType.TextLong == info.getColumnDisplayType(index) || DisplayType.JSON == info.getColumnDisplayType(index)) { + // export column from system have type is normal string, but import to system have this column but type is text long (mean blob) if (getStringValue (qName) != null && !isBlobOnPackinFile(qName)) { setString(qName); }else { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java index e4471a02ad..199a74727d 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java @@ -1447,6 +1447,7 @@ public class ProcessParameterPanel extends Panel implements if (displayType == DisplayType.Text || displayType == DisplayType.Memo || displayType == DisplayType.TextLong + || displayType == DisplayType.JSON || displayType == DisplayType.Binary || displayType == DisplayType.RowID || editor.getGridField().isEncrypted()) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WJsonEditor.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WJsonEditor.java new file mode 100644 index 0000000000..505fc4ef09 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WJsonEditor.java @@ -0,0 +1,39 @@ +package org.adempiere.webui.editor; + +import org.compiere.model.GridField; +import org.compiere.util.Util; + + +public class WJsonEditor extends WStringEditor { + + /** + * + * @param gridField + */ + public WJsonEditor(GridField gridField) { + super(gridField); + getComponent().setMultiline(true); + setChangeEventWhenEditing(false); + } + + /** + * + * @param gridField + * @param tableEditor + * @param editorConfiguration + */ + public WJsonEditor(GridField gridField, boolean tableEditor, IEditorConfiguration editorConfiguration) { + super(gridField, tableEditor, editorConfiguration); + getComponent().setMultiline(true); + setChangeEventWhenEditing(false); + } + + @Override + public void setValue(Object value) { + super.setValue(value); + + if (value != null && !Util.isEmpty(value.toString())) + getComponent().setValue(Util.prettifyJSONString(value.toString())); + } + +} diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java index 5ea62430a9..fc4523ea56 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java @@ -30,6 +30,7 @@ import org.adempiere.webui.editor.WFileDirectoryEditor; import org.adempiere.webui.editor.WFilenameEditor; import org.adempiere.webui.editor.WHtmlEditor; import org.adempiere.webui.editor.WImageEditor; +import org.adempiere.webui.editor.WJsonEditor; import org.adempiere.webui.editor.WLocationEditor; import org.adempiere.webui.editor.WLocatorEditor; import org.adempiere.webui.editor.WNumberEditor; @@ -240,6 +241,10 @@ public class DefaultEditorFactory implements IEditorFactory { else if (displayType == DisplayType.RecordUU) { editor = new WRecordUUIDEditor(gridField, tableEditor, editorConfiguration); + } + else if (displayType == DisplayType.JSON) + { + editor = new WJsonEditor(gridField, tableEditor, editorConfiguration); } else { diff --git a/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java b/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java index 9dddfb4670..e97381d417 100644 --- a/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java +++ b/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java @@ -536,7 +536,23 @@ public class DB_Oracle implements AdempiereDatabase } return result.toString(); } // TO_NUMBER + + /** + * @return string with right casting for JSON inserts + */ + public String getJSONCast () { + return "?"; + } + /** + * Return string as JSON object for INSERT statements + * @param value + * @return value as json + */ + public String TO_JSON (String value) + { + return value; + } /** * Get SQL Commands. @@ -1026,6 +1042,11 @@ public class DB_Oracle implements AdempiereDatabase public String getClobDataType() { return "CLOB"; } + + @Override + public String getJsonDataType() { + return getClobDataType(); + } @Override public String getTimestampDataType() { @@ -1072,6 +1093,8 @@ public class DB_Oracle implements AdempiereDatabase // Inline Constraint if (column.getAD_Reference_ID() == DisplayType.YesNo) sql.append(" CHECK (").append(column.getColumnName()).append(" IN ('Y','N'))"); + else if (column.getAD_Reference_ID() == DisplayType.JSON) + sql.append("CONSTRAINT ").append(column.getAD_Table().getTableName()).append("_").append(column.getColumnName()).append("_isjson CHECK (").append(column.getColumnName()).append(" IS JSON)"); // Null if (column.isMandatory()) diff --git a/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java b/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java index e41300713a..ff9c055054 100755 --- a/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java +++ b/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java @@ -539,8 +539,31 @@ public class DB_PostgreSQL implements AdempiereDatabase } return result.toString(); } // TO_NUMBER + + /** + * @return string with right casting for JSON inserts + */ + public String getJSONCast () { + return "CAST (? AS jsonb)"; + } + + /** + * Return string as JSON object for INSERT statements + * @param value + * @return value as json + */ + public String TO_JSON (String value) + { + if (value == null) + return "NULL"; - + StringBuilder retValue = null; + retValue = new StringBuilder("CAST ("); + retValue.append(value); + retValue.append(" AS jsonb)"); + return retValue.toString(); + } + /** * Get SQL Commands * @param cmdType CMD_* @@ -1211,6 +1234,11 @@ public class DB_PostgreSQL implements AdempiereDatabase public String getClobDataType() { return "TEXT"; } + + @Override + public String getJsonDataType() { + return "JSONB"; + } @Override public String getTimestampDataType() { diff --git a/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java b/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java new file mode 100644 index 0000000000..42b09f0921 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java @@ -0,0 +1,70 @@ +package org.idempiere.test.base; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; + +import org.compiere.dbPort.Convert; +import org.compiere.model.MTest; +import org.compiere.util.Env; +import org.compiere.util.Ini; +import org.idempiere.test.AbstractTestCase; +import org.junit.jupiter.api.Test; + +public class JsonFieldTest extends AbstractTestCase { + + /** + * + */ + public JsonFieldTest() { + } + + @Test + public void testSavingJSONValue() { + MTest testPO = new MTest(Env.getCtx(), getClass().getName(), 1, getTrxName()); + boolean updated; + testPO.setJsonData("Testing if JSON allows to save regular strings"); + updated = testPO.save(); + assertFalse(updated); + + testPO = new MTest(Env.getCtx(), getClass().getName(), 1, getTrxName()); + String validJsonString = "{ \"name\": \"iDempiere\", \"id\": 100 }"; + testPO.setJsonData(validJsonString); + updated = testPO.save(); + assertTrue(updated); + + String validJsonArray= "[ {\"type\": \"mobile\", \"phone\": \"001001\"} , {\"type\": \"fix\", \"phone\": \"002002\"} ]"; + testPO.setJsonData(validJsonArray); + updated = testPO.save(); + assertTrue(updated); + + testPO.setJsonData(null); + updated = testPO.save(); + assertTrue(updated); + + String fileName = Convert.getMigrationScriptFileName("testLogMigrationScript"); + String folderPg = Convert.getMigrationScriptFolder("postgresql"); + String folderOr = Convert.getMigrationScriptFolder("oracle"); + + //Test inserting/updating with Values + Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, "Y"); + testPO.setJsonData(validJsonString); + updated = testPO.save(); + assertTrue(updated); + + testPO.setJsonData(validJsonArray); + updated = testPO.save(); + assertTrue(updated); + + Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, ""); + + rollback(); + File file = new File(folderPg + fileName); + assertTrue(file.exists(), "Not found: " + folderPg + fileName); + file.delete(); + file = new File(folderOr + fileName); + assertTrue(file.exists(), "Not found: " + folderOr + fileName); + file.delete(); + } +} From 924bdb6fc19fcda3ebe7e0f6b86c5a09ea4c6597 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 8 Mar 2024 04:24:29 +0100 Subject: [PATCH 051/169] IDEMPIERE-2981 Fixes to failing unit tests (#2263) --- .../src/org/compiere/dbPort/Convert.java | 54 +++++++++++++++++-- .../idempiere/test/base/JsonFieldTest.java | 19 ++++--- .../src/org/idempiere/test/base/POTest.java | 11 ++-- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/dbPort/Convert.java b/org.adempiere.base/src/org/compiere/dbPort/Convert.java index 0d8c5896ef..a3d894d0d5 100644 --- a/org.adempiere.base/src/org/compiere/dbPort/Convert.java +++ b/org.adempiere.base/src/org/compiere/dbPort/Convert.java @@ -83,10 +83,12 @@ public abstract class Convert /** Logger */ private static final CLogger log = CLogger.getCLogger (Convert.class); + private static File fileOr = null; private static FileOutputStream fosScriptOr = null; - private static Writer writerOr; + private static Writer writerOr = null; + private static File filePg = null; private static FileOutputStream fosScriptPg = null; - private static Writer writerPg; + private static Writer writerPg = null; /** * Set Verbose @@ -471,7 +473,7 @@ public abstract class Convert Files.createDirectories(Paths.get(folderPg)); } if (fosScriptOr == null) { - File fileOr = new File(folderOr + fileName); + fileOr = new File(folderOr + fileName); fosScriptOr = new FileOutputStream(fileOr, true); writerOr = new BufferedWriter(new OutputStreamWriter(fosScriptOr, "UTF8")); writerOr.append("-- "); @@ -488,7 +490,7 @@ public abstract class Convert pgStatement = r[0]; } if (fosScriptPg == null) { - File filePg = new File(folderPg + fileName); + filePg = new File(folderPg + fileName); fosScriptPg = new FileOutputStream(filePg, true); writerPg = new BufferedWriter(new OutputStreamWriter(fosScriptPg, "UTF8")); writerPg.append("-- "); @@ -688,4 +690,48 @@ public abstract class Convert w.flush(); } + /** + * Close the files for migration scripts, used just on Tests + */ + public static void closeLogMigrationScript() { + try { + if (writerOr != null) { + writerOr.flush(); + writerOr.close(); + writerOr = null; + } + if (writerPg != null) { + writerPg.flush(); + writerPg.close(); + writerPg = null; + } + if (fosScriptOr != null) { + fosScriptOr.flush(); + fosScriptOr.close(); + fosScriptOr = null; + } + if (fosScriptPg != null) { + fosScriptPg.flush(); + fosScriptPg.close(); + fosScriptPg = null; + } + fileOr = null; + filePg = null; + } catch (IOException e) { + // ignore + e.printStackTrace(); + } + } + + /** + * Get the name of the migration script file + * @return + */ + public static String getGeneratedMigrationScriptFileName() { + if (filePg != null) { + return filePg.getName(); + } + return null; + } + } // Convert \ No newline at end of file diff --git a/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java b/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java index 42b09f0921..fa04940332 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java @@ -6,12 +6,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import org.compiere.dbPort.Convert; +import org.compiere.model.I_AD_UserPreference; import org.compiere.model.MTest; import org.compiere.util.Env; import org.compiere.util.Ini; import org.idempiere.test.AbstractTestCase; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; +/** + * Tests for JSON data type + * Run Isolated because of migration script file management + */ +@Isolated public class JsonFieldTest extends AbstractTestCase { /** @@ -42,13 +49,10 @@ public class JsonFieldTest extends AbstractTestCase { testPO.setJsonData(null); updated = testPO.save(); assertTrue(updated); - - String fileName = Convert.getMigrationScriptFileName("testLogMigrationScript"); - String folderPg = Convert.getMigrationScriptFolder("postgresql"); - String folderOr = Convert.getMigrationScriptFolder("oracle"); - + //Test inserting/updating with Values Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, "Y"); + Env.setContext(Env.getCtx(), I_AD_UserPreference.COLUMNNAME_MigrationScriptComment, "IDEMPIERE-02981 JsonFieldTest"); testPO.setJsonData(validJsonString); updated = testPO.save(); assertTrue(updated); @@ -59,7 +63,10 @@ public class JsonFieldTest extends AbstractTestCase { Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, ""); - rollback(); + String fileName = Convert.getGeneratedMigrationScriptFileName(); + String folderPg = Convert.getMigrationScriptFolder("postgresql"); + String folderOr = Convert.getMigrationScriptFolder("oracle"); + Convert.closeLogMigrationScript(); File file = new File(folderPg + fileName); assertTrue(file.exists(), "Not found: " + folderPg + fileName); file.delete(); diff --git a/org.idempiere.test/src/org/idempiere/test/base/POTest.java b/org.idempiere.test/src/org/idempiere/test/base/POTest.java index 5387072a34..d856dace80 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/POTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/POTest.java @@ -58,12 +58,16 @@ import org.compiere.util.Trx; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.DictionaryIDs; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; /** * Tests for {@link org.compiere.model.PO} class. * @author Teo Sarca, SC ARHIPAC SERVICE SRL * @author hengsin + * + * Run Isolated because of migration script file management */ +@Isolated public class POTest extends AbstractTestCase { public static class MyTestPO extends MTest @@ -512,9 +516,6 @@ public class POTest extends AbstractTestCase Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, "Y"); Env.setContext(Env.getCtx(), I_AD_UserPreference.COLUMNNAME_MigrationScriptComment, "testLogMigrationScript"); assertTrue(Env.isLogMigrationScript(MProduct.Table_Name), "Unexpected Log Migration Script Y/N value for MProduct"); - String fileName = Convert.getMigrationScriptFileName("testLogMigrationScript"); - String folderPg = Convert.getMigrationScriptFolder("postgresql"); - String folderOr = Convert.getMigrationScriptFolder("oracle"); MProductCategory lotLevel = new MProductCategory(Env.getCtx(), 0, null); lotLevel.setName("testLogMigrationScript"); @@ -548,6 +549,10 @@ public class POTest extends AbstractTestCase lotLevel.deleteEx(true); } + String fileName = Convert.getGeneratedMigrationScriptFileName(); + String folderPg = Convert.getMigrationScriptFolder("postgresql"); + String folderOr = Convert.getMigrationScriptFolder("oracle"); + Convert.closeLogMigrationScript(); File file = new File(folderPg + fileName); assertTrue(file.exists(), "Not found: " + folderPg + fileName); file.delete(); From abeb2452262a936b1ed8de21d70525e77f7c11ef Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 13 Jun 2024 18:32:24 +0200 Subject: [PATCH 052/169] IDEMPIERE-6169 Performance on AD_ChangeLog with Record_UU (#2393) --- .../iD11/oracle/202406131736_IDEMPIERE-6169.sql | 12 ++++++++++++ .../iD11/postgresql/202406131736_IDEMPIERE-6169.sql | 9 +++++++++ 2 files changed, 21 insertions(+) create mode 100644 migration/iD11/oracle/202406131736_IDEMPIERE-6169.sql create mode 100644 migration/iD11/postgresql/202406131736_IDEMPIERE-6169.sql diff --git a/migration/iD11/oracle/202406131736_IDEMPIERE-6169.sql b/migration/iD11/oracle/202406131736_IDEMPIERE-6169.sql new file mode 100644 index 0000000000..f1564872e8 --- /dev/null +++ b/migration/iD11/oracle/202406131736_IDEMPIERE-6169.sql @@ -0,0 +1,12 @@ +-- IDEMPIERE-6169 Performance on AD_ChangeLog with Record_UU +SELECT register_migration_script('202406131736_IDEMPIERE-6169.sql') FROM dual; + +ALTER TABLE ad_changelog DROP CONSTRAINT ad_changelog_key +; + +DROP INDEX ad_changelog_key +; + +ALTER TABLE ad_changelog ADD CONSTRAINT ad_changelog_pkey PRIMARY KEY (ad_session_id, ad_column_id, ad_changelog_id) +; + diff --git a/migration/iD11/postgresql/202406131736_IDEMPIERE-6169.sql b/migration/iD11/postgresql/202406131736_IDEMPIERE-6169.sql new file mode 100644 index 0000000000..f1c233ddaf --- /dev/null +++ b/migration/iD11/postgresql/202406131736_IDEMPIERE-6169.sql @@ -0,0 +1,9 @@ +-- IDEMPIERE-6169 Performance on AD_ChangeLog with Record_UU +SELECT register_migration_script('202406131736_IDEMPIERE-6169.sql') FROM dual; + +ALTER TABLE ad_changelog DROP CONSTRAINT ad_changelog_pkey +; + +ALTER TABLE ad_changelog ADD CONSTRAINT ad_changelog_pkey PRIMARY KEY (ad_session_id, ad_column_id, ad_changelog_id) +; + From 77f0ababe68105e9c035acbe57d0fce881c4ffd2 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Tue, 18 Jun 2024 05:52:58 +0200 Subject: [PATCH 054/169] IDEMPIERE-6176 UUID indexes without constraint (#2397) --- .../oracle/202406180027_IDEMPIERE-6176.sql | 22 +++++++++++++++++++ .../202406180027_IDEMPIERE-6176.sql | 19 ++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 migration/iD11/oracle/202406180027_IDEMPIERE-6176.sql create mode 100644 migration/iD11/postgresql/202406180027_IDEMPIERE-6176.sql diff --git a/migration/iD11/oracle/202406180027_IDEMPIERE-6176.sql b/migration/iD11/oracle/202406180027_IDEMPIERE-6176.sql new file mode 100644 index 0000000000..cdc959eda6 --- /dev/null +++ b/migration/iD11/oracle/202406180027_IDEMPIERE-6176.sql @@ -0,0 +1,22 @@ +-- IDEMPIERE-6176 UUID indexes without constraint +SELECT register_migration_script('202406180027_IDEMPIERE-6176.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Jun 18, 2024, 12:29:20 AM CEST +UPDATE AD_IndexColumn SET IsActive='N',Updated=TO_TIMESTAMP('2024-06-18 00:29:20','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_IndexColumn_ID=200980 +; + +-- Jun 18, 2024, 12:29:27 AM CEST +UPDATE AD_TableIndex SET IsActive='N', IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:29:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=200806 +; + +-- Jun 18, 2024, 12:30:44 AM CEST +UPDATE AD_TableIndex SET IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:30:44','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=201272 +; + +-- Jun 18, 2024, 12:31:49 AM CEST +UPDATE AD_TableIndex SET IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:31:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=201275 +; + diff --git a/migration/iD11/postgresql/202406180027_IDEMPIERE-6176.sql b/migration/iD11/postgresql/202406180027_IDEMPIERE-6176.sql new file mode 100644 index 0000000000..3c5551609e --- /dev/null +++ b/migration/iD11/postgresql/202406180027_IDEMPIERE-6176.sql @@ -0,0 +1,19 @@ +-- IDEMPIERE-6176 UUID indexes without constraint +SELECT register_migration_script('202406180027_IDEMPIERE-6176.sql') FROM dual; + +-- Jun 18, 2024, 12:29:20 AM CEST +UPDATE AD_IndexColumn SET IsActive='N',Updated=TO_TIMESTAMP('2024-06-18 00:29:20','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_IndexColumn_ID=200980 +; + +-- Jun 18, 2024, 12:29:27 AM CEST +UPDATE AD_TableIndex SET IsActive='N', IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:29:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=200806 +; + +-- Jun 18, 2024, 12:30:44 AM CEST +UPDATE AD_TableIndex SET IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:30:44','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=201272 +; + +-- Jun 18, 2024, 12:31:49 AM CEST +UPDATE AD_TableIndex SET IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:31:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=201275 +; + From 061ef5fec4710a20a06d39fc025b20ca6c1396f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Tak=C3=A1cs?= <93127072+PeterTakacs300@users.noreply.github.com> Date: Wed, 19 Jun 2024 05:08:14 +0200 Subject: [PATCH 055/169] IDEMPIERE-6174 - Create Production from Order process doesn't set BOM Formula (#2399) --- .../src/org/compiere/process/OrderCreateProduction.java | 3 +++ .../src/org/compiere/process/OrderLineCreateProduction.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/org.adempiere.base.process/src/org/compiere/process/OrderCreateProduction.java b/org.adempiere.base.process/src/org/compiere/process/OrderCreateProduction.java index f2d548c0e3..7dd1768a97 100644 --- a/org.adempiere.base.process/src/org/compiere/process/OrderCreateProduction.java +++ b/org.adempiere.base.process/src/org/compiere/process/OrderCreateProduction.java @@ -37,6 +37,7 @@ import org.compiere.model.MWarehouse; import org.compiere.model.Query; import org.compiere.util.Env; import org.compiere.util.Msg; +import org.eevolution.model.MPPProductBOM; /** * @@ -90,11 +91,13 @@ public class OrderCreateProduction extends SvrProcess { MProduction production = new MProduction(line); MProduct product = new MProduct(getCtx(), line.getM_Product_ID(), get_TrxName()); + MPPProductBOM productBOM = MPPProductBOM.getDefault(product, get_TrxName()); production.setM_Product_ID(line.getM_Product_ID()); production.setProductionQty(line.getQtyOrdered().subtract(line.getQtyDelivered())); production.setDatePromised(line.getDatePromised()); production.setC_OrderLine_ID(line.getC_OrderLine_ID()); + production.setPP_Product_BOM_ID(productBOM.getPP_Product_BOM_ID()); int locator = product.getM_Locator_ID(); if (locator == 0) diff --git a/org.adempiere.base.process/src/org/compiere/process/OrderLineCreateProduction.java b/org.adempiere.base.process/src/org/compiere/process/OrderLineCreateProduction.java index 9f821365dc..ba8514ae05 100644 --- a/org.adempiere.base.process/src/org/compiere/process/OrderLineCreateProduction.java +++ b/org.adempiere.base.process/src/org/compiere/process/OrderLineCreateProduction.java @@ -27,6 +27,7 @@ import org.compiere.model.MWarehouse; import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.Msg; +import org.eevolution.model.MPPProductBOM; /** * Create (Generate) Production from OrderLine @@ -86,11 +87,13 @@ public class OrderLineCreateProduction extends SvrProcess MProduction production = new MProduction( line ); MProduct product = new MProduct (getCtx(), line.getM_Product_ID(), get_TrxName()); + MPPProductBOM productBOM = MPPProductBOM.getDefault(product, get_TrxName()); production.setM_Product_ID(line.getM_Product_ID()); production.setProductionQty(line.getQtyOrdered().subtract(line.getQtyDelivered())); production.setDatePromised(line.getDatePromised()); production.setC_OrderLine_ID(p_C_OrderLine_ID); + production.setPP_Product_BOM_ID(productBOM.getPP_Product_BOM_ID()); int locator = product.getM_Locator_ID(); if ( locator == 0 ) From 4e250b6a52a45bbb4983ed5858658f670a0b1a41 Mon Sep 17 00:00:00 2001 From: hengsin Date: Wed, 19 Jun 2024 16:30:21 +0800 Subject: [PATCH 056/169] IDEMPIERE-6172 Open from a highlighted window, DateRangePicker popup will auto close when selecting an item in dropdown list (#2400) - Implement workaround for highlighted+popup+combobox --- .../webui/window/DateRangeButton.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/DateRangeButton.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/DateRangeButton.java index 05d5c57e00..cc081ce0fc 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/DateRangeButton.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/DateRangeButton.java @@ -28,10 +28,12 @@ import java.util.Properties; import org.adempiere.webui.LayoutUtils; import org.adempiere.webui.component.ToolBarButton; +import org.adempiere.webui.component.Window; import org.adempiere.webui.editor.WDateEditor; import org.adempiere.webui.editor.WEditor; import org.adempiere.webui.theme.ThemeManager; import org.compiere.util.Env; +import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.event.Events; /** @@ -74,9 +76,24 @@ public class DateRangeButton extends ToolBarButton implements WEditor.DynamicDis setImage(ThemeManager.getThemeResource(IMAGES_CONTEXT_HISTORY_PNG)); DateRangePicker popup = new DateRangePicker(editor, editor2); - this.setTooltip(popup); this.addEventListener(Events.ON_CLICK, event -> { - popup.setPage(this.getPage()); + Window window = null; + Component component = this.getParent(); + while(component != null) { + if (component instanceof Window w) { + window = w; + break; + } else { + component = component.getParent(); + } + } + // Popup must be a child of highlighted parent, otherwise combobox wouldn't work + // https://tracker.zkoss.org/browse/ZK-5740 + if (window != null && "highlighted".equals(window.getMode())) { + window.appendChild(popup); + } else { + popup.setPage(this.getPage()); + } popup.open(this, "after_center"); LayoutUtils.autoDetachOnClose(popup); }); From 0641e614c2f85ee8252c24ab81659c9916f3297c Mon Sep 17 00:00:00 2001 From: hengsin Date: Thu, 20 Jun 2024 21:54:55 +0800 Subject: [PATCH 057/169] IDEMPIERE-6178 Level 3 tab is invisible after collapse and expand of bottom pane (#2401) --- .../WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java index c27de5daac..cb60ddefb2 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java @@ -1597,9 +1597,12 @@ DataStatusListener, IADTabpanel, IdSpace, IFieldEditorContainer if (detailPane.getParent() == null) { formContainer.appendSouth(detailPane); } + IADTabpanel tabPanel = detailPane.getSelectedADTabpanel(); if (tabPanel != null) { - if (!tabPanel.isActivated()) { + if (!tabPanel.isActivated() || !detailPane.isVisible()) { + if (!detailPane.isVisible()) + detailPane.setVisible(true); tabPanel.activate(true); } else { tabPanel.getGridView().invalidateGridView(); From b28edf2be1a3bf8fa530771e0ef615bf9d06dc45 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 20 Jun 2024 18:52:27 +0200 Subject: [PATCH 058/169] IDEMPIERE-2981 Implement JSON Field type - generate model for Test (#2402) --- .../src/org/compiere/model/I_Test.java | 174 ++++++------- .../src/org/compiere/model/X_Test.java | 237 +++++++++--------- 2 files changed, 204 insertions(+), 207 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/I_Test.java b/org.adempiere.base/src/org/compiere/model/I_Test.java index 5cdda1cdae..7988b0a1ea 100644 --- a/org.adempiere.base/src/org/compiere/model/I_Test.java +++ b/org.adempiere.base/src/org/compiere/model/I_Test.java @@ -41,22 +41,11 @@ public interface I_Test /** Load Meta Data */ - /** Column name Account_Acct */ - public static final String COLUMNNAME_Account_Acct = "Account_Acct"; - - /** Set Account_Acct */ - public void setAccount_Acct (int Account_Acct); - - /** Get Account_Acct */ - public int getAccount_Acct(); - - public I_C_ValidCombination getAccount_A() throws RuntimeException; - /** Column name AD_Client_ID */ public static final String COLUMNNAME_AD_Client_ID = "AD_Client_ID"; - /** Get Client. - * Client/Tenant for this installation. + /** Get Tenant. + * Tenant for this installation. */ public int getAD_Client_ID(); @@ -64,12 +53,12 @@ public interface I_Test public static final String COLUMNNAME_AD_Org_ID = "AD_Org_ID"; /** Set Organization. - * Organizational entity within client + * Organizational entity within tenant */ public void setAD_Org_ID (int AD_Org_ID); /** Get Organization. - * Organizational entity within client + * Organizational entity within tenant */ public int getAD_Org_ID(); @@ -88,18 +77,29 @@ public interface I_Test public org.compiere.model.I_AD_Table getAD_Table() throws RuntimeException; + /** Column name Account_Acct */ + public static final String COLUMNNAME_Account_Acct = "Account_Acct"; + + /** Set Account_Acct */ + public void setAccount_Acct (int Account_Acct); + + /** Get Account_Acct */ + public int getAccount_Acct(); + + public I_C_ValidCombination getAccount_A() throws RuntimeException; + /** Column name BinaryData */ public static final String COLUMNNAME_BinaryData = "BinaryData"; /** Set Binary Data. * Binary Data */ - public void setBinaryData (int BinaryData); + public void setBinaryData (byte[] BinaryData); /** Get Binary Data. * Binary Data */ - public int getBinaryData(); + public byte[] getBinaryData(); /** Column name C_BPartner_ID */ public static final String COLUMNNAME_C_BPartner_ID = "C_BPartner_ID"; @@ -131,19 +131,6 @@ public interface I_Test public org.compiere.model.I_C_Currency getC_Currency() throws RuntimeException; - /** Column name CharacterData */ - public static final String COLUMNNAME_CharacterData = "CharacterData"; - - /** Set Character Data. - * Long Character Field - */ - public void setCharacterData (String CharacterData); - - /** Get Character Data. - * Long Character Field - */ - public String getCharacterData(); - /** Column name C_Location_ID */ public static final String COLUMNNAME_C_Location_ID = "C_Location_ID"; @@ -159,15 +146,6 @@ public interface I_Test public I_C_Location getC_Location() throws RuntimeException; - /** Column name Color */ - public static final String COLUMNNAME_Color = "Color"; - - /** Set Color */ - public void setColor (String Color); - - /** Get Color */ - public String getColor(); - /** Column name C_Payment_ID */ public static final String COLUMNNAME_C_Payment_ID = "C_Payment_ID"; @@ -183,6 +161,43 @@ public interface I_Test public org.compiere.model.I_C_Payment getC_Payment() throws RuntimeException; + /** Column name C_UOM_ID */ + public static final String COLUMNNAME_C_UOM_ID = "C_UOM_ID"; + + /** Set UOM. + * Unit of Measure + */ + public void setC_UOM_ID (int C_UOM_ID); + + /** Get UOM. + * Unit of Measure + */ + public int getC_UOM_ID(); + + public org.compiere.model.I_C_UOM getC_UOM() throws RuntimeException; + + /** Column name CharacterData */ + public static final String COLUMNNAME_CharacterData = "CharacterData"; + + /** Set Character Data. + * Long Character Field + */ + public void setCharacterData (String CharacterData); + + /** Get Character Data. + * Long Character Field + */ + public String getCharacterData(); + + /** Column name Color */ + public static final String COLUMNNAME_Color = "Color"; + + /** Set Color */ + public void setColor (String Color); + + /** Get Color */ + public String getColor(); + /** Column name Created */ public static final String COLUMNNAME_Created = "Created"; @@ -199,21 +214,6 @@ public interface I_Test */ public int getCreatedBy(); - /** Column name C_UOM_ID */ - public static final String COLUMNNAME_C_UOM_ID = "C_UOM_ID"; - - /** Set UOM. - * Unit of Measure - */ - public void setC_UOM_ID (int C_UOM_ID); - - /** Get UOM. - * Unit of Measure - */ - public int getC_UOM_ID(); - - public org.compiere.model.I_C_UOM getC_UOM() throws RuntimeException; - /** Column name Description */ public static final String COLUMNNAME_Description = "Description"; @@ -259,12 +259,12 @@ public interface I_Test /** Set JSON Data. * The json field stores json data. */ - public void setJsonData (Object JsonData); + public void setJsonData (String JsonData); /** Get JSON Data. * The json field stores json data. */ - public Object getJsonData(); + public String getJsonData(); /** Column name M_Locator_ID */ public static final String COLUMNNAME_M_Locator_ID = "M_Locator_ID"; @@ -380,37 +380,6 @@ public interface I_Test /** Get DateTime */ public Timestamp getT_DateTime(); - /** Column name Test_ID */ - public static final String COLUMNNAME_Test_ID = "Test_ID"; - - /** Set Test ID */ - public void setTest_ID (int Test_ID); - - /** Get Test ID */ - public int getTest_ID(); - - /** Column name Test_UU */ - public static final String COLUMNNAME_Test_UU = "Test_UU"; - - /** Set Test_UU */ - public void setTest_UU (String Test_UU); - - /** Get Test_UU */ - public String getTest_UU(); - - /** Column name TestVirtualQty */ - public static final String COLUMNNAME_TestVirtualQty = "TestVirtualQty"; - - /** Set Virtual Quantity. - * Used only for testing purposes - */ - public void setTestVirtualQty (BigDecimal TestVirtualQty); - - /** Get Virtual Quantity. - * Used only for testing purposes - */ - public BigDecimal getTestVirtualQty(); - /** Column name T_Integer */ public static final String COLUMNNAME_T_Integer = "T_Integer"; @@ -451,6 +420,37 @@ public interface I_Test */ public Timestamp getT_Timestamp(); + /** Column name TestVirtualQty */ + public static final String COLUMNNAME_TestVirtualQty = "TestVirtualQty"; + + /** Set Virtual Quantity. + * Used only for testing purposes + */ + public void setTestVirtualQty (BigDecimal TestVirtualQty); + + /** Get Virtual Quantity. + * Used only for testing purposes + */ + public BigDecimal getTestVirtualQty(); + + /** Column name Test_ID */ + public static final String COLUMNNAME_Test_ID = "Test_ID"; + + /** Set Test ID */ + public void setTest_ID (int Test_ID); + + /** Get Test ID */ + public int getTest_ID(); + + /** Column name Test_UU */ + public static final String COLUMNNAME_Test_UU = "Test_UU"; + + /** Set Test_UU */ + public void setTest_UU (String Test_UU); + + /** Get Test_UU */ + public String getTest_UU(); + /** Column name Updated */ public static final String COLUMNNAME_Updated = "Updated"; diff --git a/org.adempiere.base/src/org/compiere/model/X_Test.java b/org.adempiere.base/src/org/compiere/model/X_Test.java index d6cfe6e8ca..9b814d0d7f 100644 --- a/org.adempiere.base/src/org/compiere/model/X_Test.java +++ b/org.adempiere.base/src/org/compiere/model/X_Test.java @@ -34,7 +34,7 @@ public class X_Test extends PO implements I_Test, I_Persistent /** * */ - private static final long serialVersionUID = 20240226L; + private static final long serialVersionUID = 20240620L; /** Standard Constructor */ public X_Test (Properties ctx, int Test_ID, String trxName) @@ -108,30 +108,6 @@ public class X_Test extends PO implements I_Test, I_Persistent return sb.toString(); } - public I_C_ValidCombination getAccount_A() throws RuntimeException - { - return (I_C_ValidCombination)MTable.get(getCtx(), I_C_ValidCombination.Table_ID) - .getPO(getAccount_Acct(), get_TrxName()); - } - - /** Set Account_Acct. - @param Account_Acct Account_Acct - */ - public void setAccount_Acct (int Account_Acct) - { - set_Value (COLUMNNAME_Account_Acct, Integer.valueOf(Account_Acct)); - } - - /** Get Account_Acct. - @return Account_Acct */ - public int getAccount_Acct() - { - Integer ii = (Integer)get_Value(COLUMNNAME_Account_Acct); - if (ii == null) - return 0; - return ii.intValue(); - } - public org.compiere.model.I_AD_Table getAD_Table() throws RuntimeException { return (org.compiere.model.I_AD_Table)MTable.get(getCtx(), org.compiere.model.I_AD_Table.Table_ID) @@ -160,23 +136,44 @@ public class X_Test extends PO implements I_Test, I_Persistent return ii.intValue(); } + public I_C_ValidCombination getAccount_A() throws RuntimeException + { + return (I_C_ValidCombination)MTable.get(getCtx(), I_C_ValidCombination.Table_ID) + .getPO(getAccount_Acct(), get_TrxName()); + } + + /** Set Account_Acct. + @param Account_Acct Account_Acct + */ + public void setAccount_Acct (int Account_Acct) + { + set_Value (COLUMNNAME_Account_Acct, Integer.valueOf(Account_Acct)); + } + + /** Get Account_Acct. + @return Account_Acct */ + public int getAccount_Acct() + { + Integer ii = (Integer)get_Value(COLUMNNAME_Account_Acct); + if (ii == null) + return 0; + return ii.intValue(); + } + /** Set Binary Data. @param BinaryData Binary Data */ - public void setBinaryData (int BinaryData) + public void setBinaryData (byte[] BinaryData) { - set_Value (COLUMNNAME_BinaryData, Integer.valueOf(BinaryData)); + set_Value (COLUMNNAME_BinaryData, BinaryData); } /** Get Binary Data. @return Binary Data */ - public int getBinaryData() + public byte[] getBinaryData() { - Integer ii = (Integer)get_Value(COLUMNNAME_BinaryData); - if (ii == null) - return 0; - return ii.intValue(); + return (byte[])get_Value(COLUMNNAME_BinaryData); } public org.compiere.model.I_C_BPartner getC_BPartner() throws RuntimeException @@ -235,22 +232,6 @@ public class X_Test extends PO implements I_Test, I_Persistent return ii.intValue(); } - /** Set Character Data. - @param CharacterData Long Character Field - */ - public void setCharacterData (String CharacterData) - { - set_Value (COLUMNNAME_CharacterData, CharacterData); - } - - /** Get Character Data. - @return Long Character Field - */ - public String getCharacterData() - { - return (String)get_Value(COLUMNNAME_CharacterData); - } - public I_C_Location getC_Location() throws RuntimeException { return (I_C_Location)MTable.get(getCtx(), I_C_Location.Table_ID) @@ -279,21 +260,6 @@ public class X_Test extends PO implements I_Test, I_Persistent return ii.intValue(); } - /** Set Color. - @param Color Color - */ - public void setColor (String Color) - { - set_Value (COLUMNNAME_Color, Color); - } - - /** Get Color. - @return Color */ - public String getColor() - { - return (String)get_Value(COLUMNNAME_Color); - } - public org.compiere.model.I_C_Payment getC_Payment() throws RuntimeException { return (org.compiere.model.I_C_Payment)MTable.get(getCtx(), org.compiere.model.I_C_Payment.Table_ID) @@ -350,6 +316,37 @@ public class X_Test extends PO implements I_Test, I_Persistent return ii.intValue(); } + /** Set Character Data. + @param CharacterData Long Character Field + */ + public void setCharacterData (String CharacterData) + { + set_Value (COLUMNNAME_CharacterData, CharacterData); + } + + /** Get Character Data. + @return Long Character Field + */ + public String getCharacterData() + { + return (String)get_Value(COLUMNNAME_CharacterData); + } + + /** Set Color. + @param Color Color + */ + public void setColor (String Color) + { + set_Value (COLUMNNAME_Color, Color); + } + + /** Get Color. + @return Color */ + public String getColor() + { + return (String)get_Value(COLUMNNAME_Color); + } + /** Set Description. @param Description Optional short description of the record */ @@ -385,7 +382,7 @@ public class X_Test extends PO implements I_Test, I_Persistent /** Set JSON Data. @param JsonData The json field stores json data. */ - public void setJsonData (Object JsonData) + public void setJsonData (String JsonData) { set_Value (COLUMNNAME_JsonData, JsonData); } @@ -393,9 +390,9 @@ public class X_Test extends PO implements I_Test, I_Persistent /** Get JSON Data. @return The json field stores json data. */ - public Object getJsonData() + public String getJsonData() { - return get_Value(COLUMNNAME_JsonData); + return (String)get_Value(COLUMNNAME_JsonData); } public I_M_Locator getM_Locator() throws RuntimeException @@ -608,60 +605,6 @@ public class X_Test extends PO implements I_Test, I_Persistent return (Timestamp)get_Value(COLUMNNAME_T_DateTime); } - /** Set Test ID. - @param Test_ID Test ID - */ - public void setTest_ID (int Test_ID) - { - if (Test_ID < 1) - set_ValueNoCheck (COLUMNNAME_Test_ID, null); - else - set_ValueNoCheck (COLUMNNAME_Test_ID, Integer.valueOf(Test_ID)); - } - - /** Get Test ID. - @return Test ID */ - public int getTest_ID() - { - Integer ii = (Integer)get_Value(COLUMNNAME_Test_ID); - if (ii == null) - return 0; - return ii.intValue(); - } - - /** Set Test_UU. - @param Test_UU Test_UU - */ - public void setTest_UU (String Test_UU) - { - set_Value (COLUMNNAME_Test_UU, Test_UU); - } - - /** Get Test_UU. - @return Test_UU */ - public String getTest_UU() - { - return (String)get_Value(COLUMNNAME_Test_UU); - } - - /** Set Virtual Quantity. - @param TestVirtualQty Used only for testing purposes - */ - public void setTestVirtualQty (BigDecimal TestVirtualQty) - { - throw new IllegalArgumentException ("TestVirtualQty is virtual column"); } - - /** Get Virtual Quantity. - @return Used only for testing purposes - */ - public BigDecimal getTestVirtualQty() - { - BigDecimal bd = (BigDecimal)get_Value(COLUMNNAME_TestVirtualQty); - if (bd == null) - return Env.ZERO; - return bd; - } - /** Set Integer. @param T_Integer Integer */ @@ -731,4 +674,58 @@ public class X_Test extends PO implements I_Test, I_Persistent { return (Timestamp)get_Value(COLUMNNAME_T_Timestamp); } + + /** Set Virtual Quantity. + @param TestVirtualQty Used only for testing purposes + */ + public void setTestVirtualQty (BigDecimal TestVirtualQty) + { + throw new IllegalArgumentException ("TestVirtualQty is virtual column"); } + + /** Get Virtual Quantity. + @return Used only for testing purposes + */ + public BigDecimal getTestVirtualQty() + { + BigDecimal bd = (BigDecimal)get_Value(COLUMNNAME_TestVirtualQty); + if (bd == null) + return Env.ZERO; + return bd; + } + + /** Set Test ID. + @param Test_ID Test ID + */ + public void setTest_ID (int Test_ID) + { + if (Test_ID < 1) + set_ValueNoCheck (COLUMNNAME_Test_ID, null); + else + set_ValueNoCheck (COLUMNNAME_Test_ID, Integer.valueOf(Test_ID)); + } + + /** Get Test ID. + @return Test ID */ + public int getTest_ID() + { + Integer ii = (Integer)get_Value(COLUMNNAME_Test_ID); + if (ii == null) + return 0; + return ii.intValue(); + } + + /** Set Test_UU. + @param Test_UU Test_UU + */ + public void setTest_UU (String Test_UU) + { + set_Value (COLUMNNAME_Test_UU, Test_UU); + } + + /** Get Test_UU. + @return Test_UU */ + public String getTest_UU() + { + return (String)get_Value(COLUMNNAME_Test_UU); + } } \ No newline at end of file From b11828353042e7e9645d7aca3574d33a5c69f84d Mon Sep 17 00:00:00 2001 From: Diego Ruiz <12065321+d-ruiz@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:47:47 +0200 Subject: [PATCH 059/169] IDEMPIERE-6181 - Fix MBPartner.getPrimaryAD_User_ID() returning 0 when the business partner has no contacts (#2405) --- org.adempiere.base/src/org/compiere/model/MBPartner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/MBPartner.java b/org.adempiere.base/src/org/compiere/model/MBPartner.java index 510a804522..f07f68f057 100644 --- a/org.adempiere.base/src/org/compiere/model/MBPartner.java +++ b/org.adempiere.base/src/org/compiere/model/MBPartner.java @@ -686,7 +686,7 @@ public class MBPartner extends X_C_BPartner implements ImmutablePOSupport /** * Get Primary AD_User_ID - * @return AD_User_ID or 0 + * @return AD_User_ID or -1 */ public int getPrimaryAD_User_ID() { @@ -697,7 +697,7 @@ public class MBPartner extends X_C_BPartner implements ImmutablePOSupport setPrimaryAD_User_ID(users[0].getAD_User_ID()); } if (m_primaryAD_User_ID == null) - return 0; + return -1; return m_primaryAD_User_ID.intValue(); } // getPrimaryAD_User_ID From 6192a8fff84bf49f4b59bfb7bd2c8e85880a9303 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 4 Jul 2024 19:52:25 +0200 Subject: [PATCH 060/169] IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) (#2408) fix new issue found informing timeout errors when no records are found after a failed count --- org.adempiere.base/src/org/compiere/model/GridTable.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/org.adempiere.base/src/org/compiere/model/GridTable.java b/org.adempiere.base/src/org/compiere/model/GridTable.java index a460c96e18..8b18f67a28 100644 --- a/org.adempiere.base/src/org/compiere/model/GridTable.java +++ b/org.adempiere.base/src/org/compiere/model/GridTable.java @@ -98,7 +98,7 @@ public class GridTable extends AbstractTableModel /** * */ - private static final long serialVersionUID = -2602189278069194311L; + private static final long serialVersionUID = 3948220810042370826L; protected static final String SORTED_DSE_EVENT = "Sorted"; @@ -172,6 +172,7 @@ public class GridTable extends AbstractTableModel /** Rowcount */ private int m_rowCount = 0; private boolean m_rowCountTimeout = false; + private boolean m_rowLoadTimeout = false; /** Has Data changed? */ private boolean m_changed = false; /** Index of changed row via SetValueAt */ @@ -1160,6 +1161,9 @@ public class GridTable extends AbstractTableModel if (savedEx != null) throw new IllegalStateException(savedEx); } + // zero rows found without load timeout + if (row == 0 && m_sort.size() == 0 && m_rowCountTimeout && !m_rowLoadTimeout) + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "FindZeroRecords")); if (row >= m_sort.size()) { log.warning("Reached " + timeout + " seconds timeout loading row " + (row+1) + " for SQL=" + m_SQL); //adjust row count @@ -1268,6 +1272,7 @@ public class GridTable extends AbstractTableModel PreparedStatement stmt = null; ResultSet rs = null; + m_rowLoadTimeout = false; try { stmt = DB.prepareStatement(sql.toString(), null); @@ -1305,6 +1310,8 @@ public class GridTable extends AbstractTableModel } catch (SQLException e) { + if (DB.getDatabase().isQueryTimeout(e)) + m_rowLoadTimeout = true; log.log(Level.SEVERE, e.getLocalizedMessage(), e); } finally From afe578e9b31c31f65396a8d2091cdb36dda99589 Mon Sep 17 00:00:00 2001 From: Diego Ruiz <12065321+d-ruiz@users.noreply.github.com> Date: Thu, 4 Jul 2024 19:56:50 +0200 Subject: [PATCH 061/169] IDEMPIERE-4087 - Set isDisplayed='N' by default for virtual search columns in field (#2407) --- org.adempiere.base/src/org/compiere/model/MField.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/org.adempiere.base/src/org/compiere/model/MField.java b/org.adempiere.base/src/org/compiere/model/MField.java index 14a4d638fa..ea4fc53e75 100644 --- a/org.adempiere.base/src/org/compiere/model/MField.java +++ b/org.adempiere.base/src/org/compiere/model/MField.java @@ -232,6 +232,15 @@ public class MField extends X_AD_Field implements ImmutablePOSupport setAD_Val_Rule_ID(0); if (getIsToolbarButton() != null) setIsToolbarButton(null); + } + + //If the column is a virtual search column - set displayed to false + if (isDisplayed()) { + MColumn column = (MColumn) getAD_Column(); + if (column.isVirtualSearchColumn()) { + setIsDisplayed(false); + setIsDisplayedGrid(false); + } } //validate logic expression From 1a89cd6b8f5c03e0bf41b2583ccd5a2a5fb7ae2c Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 4 Jul 2024 22:42:26 +0200 Subject: [PATCH 062/169] IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) (#2408) (#2409) --- org.adempiere.base/src/org/compiere/model/GridTable.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.adempiere.base/src/org/compiere/model/GridTable.java b/org.adempiere.base/src/org/compiere/model/GridTable.java index 8b18f67a28..ae7af29209 100644 --- a/org.adempiere.base/src/org/compiere/model/GridTable.java +++ b/org.adempiere.base/src/org/compiere/model/GridTable.java @@ -3099,6 +3099,7 @@ public class GridTable extends AbstractTableModel catch (SQLException e) { if (DB.getDatabase().isQueryTimeout(e)) { + m_rowLoadTimeout = true; throw new AdempiereException(Msg.getMsg(Env.getCtx(), LOAD_TIMEOUT_ERROR_MESSAGE), e); } else { log.saveError(e.getLocalizedMessage(), e); From 8938fe1d3c9d10ccba935180250228429668a6fa Mon Sep 17 00:00:00 2001 From: Diego Ruiz <12065321+d-ruiz@users.noreply.github.com> Date: Fri, 5 Jul 2024 12:30:51 +0200 Subject: [PATCH 063/169] IDEMPIERE-6186 - Fix MMailText setting the wrong language when bp language is null (#2410) --- org.adempiere.base/src/org/compiere/model/MMailText.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.adempiere.base/src/org/compiere/model/MMailText.java b/org.adempiere.base/src/org/compiere/model/MMailText.java index 94cc816225..5e49f654c2 100644 --- a/org.adempiere.base/src/org/compiere/model/MMailText.java +++ b/org.adempiere.base/src/org/compiere/model/MMailText.java @@ -332,7 +332,7 @@ public class MMailText extends X_R_MailText m_MailText3 = super.getMailText3(); if ((m_bpartner != null && m_bpartner.getAD_Language() != null) || !Util.isEmpty(m_language)) { - String adLanguage = m_bpartner != null ? m_bpartner.getAD_Language() : m_language; + String adLanguage = m_bpartner != null && m_bpartner.getAD_Language() != null ? m_bpartner.getAD_Language() : m_language; StringBuilder key = new StringBuilder().append(adLanguage).append(get_ID()); MMailTextTrl trl = s_cacheTrl.get(key.toString()); if (trl == null) From 55f9bf234992e60570db42c52e3ab30233f7dc7c Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 5 Jul 2024 16:02:43 +0200 Subject: [PATCH 064/169] IDEMPIERE-6168 Found problems with jasper setting filename (#2411) - fix issues found sending orders after complete on workflow --- .../src/org/compiere/model/MOrder.java | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/MOrder.java b/org.adempiere.base/src/org/compiere/model/MOrder.java index 7851c95837..137512f889 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrder.java +++ b/org.adempiere.base/src/org/compiere/model/MOrder.java @@ -41,12 +41,9 @@ import org.adempiere.model.ITaxProvider; import org.adempiere.process.SalesOrderRateInquiryProcess; import org.adempiere.util.IReservationTracer; import org.adempiere.util.IReservationTracerFactory; -import org.compiere.print.MPrintFormat; import org.compiere.print.ReportEngine; import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; -import org.compiere.process.ProcessInfo; -import org.compiere.process.ServerProcessCtl; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.Env; @@ -887,16 +884,7 @@ public class MOrder extends X_C_Order implements DocAction @Override public File createPDF () { - try - { - File temp = File.createTempFile(get_TableName()+get_ID()+"_", ".pdf"); - return createPDF (temp); - } - catch (Exception e) - { - log.severe("Could not create PDF - " + e.getMessage()); - } - return null; + return createPDF (null); } // getPDF /** @@ -909,21 +897,6 @@ public class MOrder extends X_C_Order implements DocAction ReportEngine re = ReportEngine.get (getCtx(), ReportEngine.ORDER, getC_Order_ID(), get_TrxName()); if (re == null) return null; - MPrintFormat format = re.getPrintFormat(); - // We have a Jasper Print Format - // ============================== - if(format.getJasperProcess_ID() > 0) - { - ProcessInfo pi = new ProcessInfo ("", format.getJasperProcess_ID()); - pi.setRecord_ID ( getC_Order_ID() ); - pi.setIsBatch(true); - - ServerProcessCtl.process(pi, null); - - return pi.getPDFReport(); - } - // Standard Print Format (Non-Jasper) - // ================================== return re.getPDF(file); } // createPDF From 22fc9ea462975007f7b6fc3b9412ff844b0a2118 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 5 Jul 2024 19:37:54 +0200 Subject: [PATCH 065/169] IDEMPIERE-6187 Transaction closed when running workflow on certain conditions (#2413) --- org.adempiere.base/src/org/compiere/print/ReportEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.adempiere.base/src/org/compiere/print/ReportEngine.java b/org.adempiere.base/src/org/compiere/print/ReportEngine.java index 232075d855..6900da39c0 100644 --- a/org.adempiere.base/src/org/compiere/print/ReportEngine.java +++ b/org.adempiere.base/src/org/compiere/print/ReportEngine.java @@ -1048,7 +1048,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) } pi.setIsBatch(true); pi.setPDFFileName(fileName); - ServerProcessCtl.process(pi, (m_trxName == null ? null : Trx.get(m_trxName, false))); + ServerProcessCtl.process(pi, (m_trxName == null ? null : Trx.get(m_trxName, false)), false); } else { PDFReportRendererConfiguration config = new PDFReportRendererConfiguration().setOutputFile(file); new PDFReportRenderer().renderReport(this, config); From 2813cab60325f4417afbc777cb921e90f4e95016 Mon Sep 17 00:00:00 2001 From: hengsin Date: Sat, 6 Jul 2024 17:06:32 +0800 Subject: [PATCH 066/169] IDEMPIERE-6182 Update datatables extension for HTML report (#2406) --- org.idempiere.zk.datatable/README.md | 15 + .../src/metainfo/zk/lang-addon.xml | 2 +- .../zk/datatable/DataTableOptions.java | 70 +- .../zk/datatable/DatatableReportRenderer.java | 84 +- .../src/web/js/datatables/customization.css | 5 + .../js/datatables/dataTables.rowGroup.min.js | 28 - .../src/web/js/datatables/datatables.css | 1992 +- .../src/web/js/datatables/datatables.js | 36346 ++++++++++------ .../src/web/js/datatables/datatables.min.css | 20 +- .../src/web/js/datatables/datatables.min.js | 607 +- .../src/web/js/datatables/i18n/en.json | 8 +- .../js/datatables/jquery.dataTables.min.js | 187 - .../js/datatables/jquery.floatThead.min.js | 3 - .../src/web/js/datatables/jquery.min.js | 4 - 14 files changed, 23788 insertions(+), 15583 deletions(-) create mode 100644 org.idempiere.zk.datatable/README.md create mode 100644 org.idempiere.zk.datatable/src/web/js/datatables/customization.css delete mode 100644 org.idempiere.zk.datatable/src/web/js/datatables/dataTables.rowGroup.min.js delete mode 100644 org.idempiere.zk.datatable/src/web/js/datatables/jquery.dataTables.min.js delete mode 100644 org.idempiere.zk.datatable/src/web/js/datatables/jquery.floatThead.min.js delete mode 100644 org.idempiere.zk.datatable/src/web/js/datatables/jquery.min.js diff --git a/org.idempiere.zk.datatable/README.md b/org.idempiere.zk.datatable/README.md new file mode 100644 index 0000000000..f06e96a0a1 --- /dev/null +++ b/org.idempiere.zk.datatable/README.md @@ -0,0 +1,15 @@ +* Steps to get datatables css and js from https://datatables.net/download/index + * Step 1. Choose a styling framework + * Datatables + * Step 2. Select packages + * jQuery3 + * DataTables + * Extensions + * Buttons > Column visibility + * ColReorder + * DateTime + * FixedColumns + * Responsive + * RowGroup + * Step 3. Pick a download method + * Download > Download files diff --git a/org.idempiere.zk.datatable/src/metainfo/zk/lang-addon.xml b/org.idempiere.zk.datatable/src/metainfo/zk/lang-addon.xml index 6eb17fa654..d4bf0b341e 100644 --- a/org.idempiere.zk.datatable/src/metainfo/zk/lang-addon.xml +++ b/org.idempiere.zk.datatable/src/metainfo/zk/lang-addon.xml @@ -3,5 +3,5 @@ datatables xul/html - + diff --git a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DataTableOptions.java b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DataTableOptions.java index 6d55b204fa..12ca0f9e99 100644 --- a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DataTableOptions.java +++ b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DataTableOptions.java @@ -32,6 +32,7 @@ import org.compiere.util.Util; import org.idempiere.zk.datatable.DatatableReportRenderer.FunctionTypes; import org.json.JSONArray; import org.json.JSONString; +import org.zkoss.zk.ui.Executions; public class DataTableOptions { @@ -68,30 +69,54 @@ public class DataTableOptions { /** * Get datatables settings - * @param path * @return json datatables settings */ - public String getDataTableOptions(String path) { + public String getDataTableOptions() { + String localePath = geti18nURL(); + String i18nPath = localePath != null && Executions.getCurrent() != null ? Executions.encodeURL(localePath) : null; StringBuilder dataOptions = new StringBuilder(); dataOptions.append(" { "); dataOptions.append(" pageLength: ").append(250); dataOptions.append(", lengthMenu: [ [250, 500, 1000, -1], [250, 500, 1000,\"").append(Msg.getMsg( Language.getAD_Language(locale), "All")).append("\" ] ] "); - dataOptions.append(", colReorder: ").append(true); - dataOptions.append(", responsive: ").append(true); - dataOptions.append(", ordering: ").append(true); - dataOptions.append(", search: { return: ").append(true).append(" }"); - dataOptions.append(", language: { url: '").append(path).append("DataTables/i18n/").append(this.locale).append(".json' }"); - dataOptions.append(", dom: \"lfrtip\" "); + dataOptions.append(", colReorder: true"); + dataOptions.append(", responsive: false"); + dataOptions.append(", ordering: true"); + if (i18nPath != null) + dataOptions.append(", language: { url: '").append(i18nPath).append("' }"); + dataOptions.append(", layout: {topStart:['buttons'], topEnd:['pageLength'], bottomStart: ['info'], bottomEnd: ['paging']} "); + dataOptions.append(""" + , buttons: [{extend: 'colvis', collectionLayout: 'fixed columns'}, + { text: 'Responsive', + action: function ( e, dt, node, config ) { + let option = dt.init(); + option.responsive = !option.responsive; + option.buttons[1].text = option.responsive ? 'Responsive ✓' : 'Responsive'; + dt.destroy(); + $('#JS_DataTable').DataTable(option); + } + }]"""); - dataOptions.append(", initComplete:").append(" function () " - + "{ this.api().columns().every( function () " - + "{ var that = this; $('input', this.footer()).on('keyup change clear', function () " - + "{ if (that.search() !== this.value) " - + "{ that.search(this.value).draw();}});});}"); + dataOptions.append(", initComplete:").append(""" + function () { + let tbl = this; + this.api().columns().every(function () { + let that = this; + let selector = 'th[data-dt-column="' + this.index() + '"]'; + let headerCell = tbl.find(selector); + if (headerCell.length) { + let input = headerCell.find('input'); + input.on('keyup change clear', + function () { + if (that.search() !== this.value) { + that.search(this.value).draw(); + } + }); + } + }); + }"""); String orderBy = getOrderBy(); - if(orderBy != null) dataOptions.append(", order: ").append(orderBy); @@ -116,6 +141,23 @@ public class DataTableOptions { return dataOptions.toString(); } + + /** + * Get i18n json URL for datatables + * @return i18n json URL + */ + public String geti18nURL() { + String localePath = "~./js/datatables/i18n/"+this.locale+".json"; + if (getClass().getResource("/web"+localePath.substring(2)) == null) { + if (this.locale.contains("-")) { + localePath = "~./js/datatables/i18n/"+this.locale.substring(0, this.locale.indexOf("-")) +".json"; + if (getClass().getResource("/web"+localePath.substring(2)) == null) { + localePath = null; + } + } + } + return localePath; + } /** * Get group rendering function diff --git a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportRenderer.java b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportRenderer.java index 89e83abc40..26418d2c61 100644 --- a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportRenderer.java +++ b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportRenderer.java @@ -47,6 +47,7 @@ import java.util.logging.Level; import org.adempiere.exceptions.AdempiereException; import org.apache.ecs.XhtmlDocument; import org.apache.ecs.xhtml.a; +import org.apache.ecs.xhtml.input; import org.apache.ecs.xhtml.link; import org.apache.ecs.xhtml.script; import org.apache.ecs.xhtml.span; @@ -173,7 +174,7 @@ public class DatatableReportRenderer implements IReportRenderer> mapCssInfo = new HashMap<>(); try { - DataTableOptions dataTableOptions = new DataTableOptions(language.getLanguageCode()); + DataTableOptions dataTableOptions = new DataTableOptions(language.getLocale().toLanguageTag()); //collect column to print List columns = new ArrayList<>(); List asiElements = new ArrayList<>(); @@ -280,14 +281,10 @@ public class DatatableReportRenderer implements IReportRenderer"); - doc.appendHead(""); - if (extension != null && !isExport){ extension.setWebAttribute(doc.getBody()); } @@ -401,27 +398,8 @@ public class DatatableReportRenderer implements IReportRenderer"); - tr tfoot = new tr(); - - for (int col = 0; col < printFormat.getItemCount(); col++) - { - MPrintFormatItem item = printFormat.getItem(col); - if (item.isPrinted()) - { - var printName = item.getPrintName(language); - if (!Util.isEmpty(printName)) - { - th th = new th(); - tfoot.addElement(th); - th.setTagText(printName); - } - } - } - tfoot.output(w); - w.print(""); - thead thead = new thead(); + thead.setClass("sticky"); tbody tbody = new tbody(); tbody.setNeedClosingTag(false); @@ -434,10 +412,33 @@ public class DatatableReportRenderer implements IReportRenderer suppressMap = new HashMap<>(); + //search input at header + { + tr tr = new tr(); + for (int col = 0; col < printFormat.getItemCount(); col++) + { + MPrintFormatItem item = printFormat.getItem(col); + if (item.isPrinted()) + { + var printName = item.getPrintName(language); + if (!Util.isEmpty(printName)) + { + th th = new th(); + th.addAttribute("data-dt-order", "disable"); + tr.addElement(th); + input searchInput = new input(); + searchInput.addAttribute("placeholder", "Search "+printName); + th.addElement(searchInput); + } + } + } + thead.addElement(tr); + } + // for all rows (-1 = header row) for (int row = -1; row < printData.getRowCount(); row++) { - tr tr = new tr(); + tr tr = new tr(); if (row != -1) { printData.setRowIndex(row); @@ -687,7 +688,7 @@ public class DatatableReportRenderer implements IReportRenderer"); + w.print(""); w.print(""); if (suppressMap.size() > 0) { @@ -707,7 +708,7 @@ public class DatatableReportRenderer implements IReportRenderer"); w.print(""); - String dataTableOptionString = dataTableOptions.getDataTableOptions("~./web/js"); + String dataTableOptionString = dataTableOptions.getDataTableOptions(); if( dataTableOptionString != null ) { w.print(""); @@ -782,8 +783,7 @@ public class DatatableReportRenderer implements IReportRenderer urls = Arrays.asList("~./js/datatables/jquery.min.js","~./js/datatables/jquery.floatThead.min.js" - ,"~./js/datatables/datatables.js","~./js/datatables/jquery.dataTables.min.js","~./js/datatables/dataTables.rowGroup.min.js"); + List urls = Arrays.asList("~./js/datatables/datatables.min.js"); if (isExport){ // embed script by content for (String extraScriptPath : urls){ @@ -802,11 +802,12 @@ public class DatatableReportRenderer implements IReportRenderer urls = Arrays.asList("~./js/datatables/datatables.css"); + private void appendStyles (XhtmlDocument doc, boolean isExport, String i18nURL) throws IOException, URISyntaxException{ + List urls = Arrays.asList("~./js/datatables/datatables.min.css", "~./js/datatables/customization.css"); if (isExport){ // embed css by content for (String extraStylePath : urls){ @@ -817,6 +818,13 @@ public class DatatableReportRenderer implements IReportRenderer").append(b("").attr("colspan",this._colspan()).attr("scope","row").append(a))).addClass(this.c.className).addClass(f).addClass("dtrg-level-"+h)}});k.defaults={className:"dtrg-group",dataSrc:0,emptyDataGroup:"No group",enable:!0,endClassName:"dtrg-end",endRender:null,startClassName:"dtrg-start",startRender:function(a,f){return f}};k.version="1.2.0";b.fn.dataTable.RowGroup= -k;b.fn.DataTable.RowGroup=k;e.Api.register("rowGroup()",function(){return this});e.Api.register("rowGroup().disable()",function(){return this.iterator("table",function(a){a.rowGroup&&a.rowGroup.enable(!1)})});e.Api.register("rowGroup().enable()",function(a){return this.iterator("table",function(f){f.rowGroup&&f.rowGroup.enable(a===g?!0:a)})});e.Api.register("rowGroup().enabled()",function(){var a=this.context;return a.length&&a[0].rowGroup?a[0].rowGroup.enabled():!1});e.Api.register("rowGroup().dataSrc()", -function(a){return a===g?this.context[0].rowGroup.dataSrc():this.iterator("table",function(f){f.rowGroup&&f.rowGroup.dataSrc(a)})});b(d).on("preInit.dt.dtrg",function(a,f,h){"dt"===a.namespace&&(a=f.oInit.rowGroup,h=e.defaults.rowGroup,a||h)&&(h=b.extend({},h,a),!1!==a&&new k(f,h))});return k}); diff --git a/org.idempiere.zk.datatable/src/web/js/datatables/datatables.css b/org.idempiere.zk.datatable/src/web/js/datatables/datatables.css index 401040caee..3fc9c3b75b 100644 --- a/org.idempiere.zk.datatable/src/web/js/datatables/datatables.css +++ b/org.idempiere.zk.datatable/src/web/js/datatables/datatables.css @@ -4,93 +4,150 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.12.1/b-2.2.3/b-colvis-2.2.3/cr-1.5.6/fh-3.2.4/sp-2.0.2 + * https://datatables.net/download/#dt/jq-3.7.0/dt-2.0.8/b-3.0.2/b-colvis-3.0.2/cr-2.0.3/date-1.5.2/fc-5.0.1/r-3.0.2/rg-1.5.0 * * Included libraries: - * DataTables 1.12.1, Buttons 2.2.3, Column visibility 2.2.3, ColReorder 1.5.6, FixedHeader 3.2.4, SearchPanes 2.0.2 + * jQuery 3 3.7.0, DataTables 2.0.8, Buttons 3.0.2, Column visibility 3.0.2, ColReorder 2.0.3, DateTime 1.5.2, FixedColumns 5.0.1, Responsive 3.0.2, RowGroup 1.5.0 */ @charset "UTF-8"; +:root { + --dt-row-selected: 13, 110, 253; + --dt-row-selected-text: 255, 255, 255; + --dt-row-selected-link: 9, 10, 11; + --dt-row-stripe: 0, 0, 0; + --dt-row-hover: 0, 0, 0; + --dt-column-ordering: 0, 0, 0; + --dt-html-background: white; +} +:root.dark { + --dt-html-background: rgb(33, 37, 41); +} + table.dataTable td.dt-control { text-align: center; cursor: pointer; } table.dataTable td.dt-control:before { - height: 1em; - width: 1em; - margin-top: -9px; display: inline-block; - color: white; - border: 0.15em solid white; - border-radius: 1em; - box-shadow: 0 0 0.2em #444; - box-sizing: content-box; - text-align: center; - text-indent: 0 !important; - font-family: "Courier New", Courier, monospace; - line-height: 1em; - content: "+"; - background-color: #31b131; + box-sizing: border-box; + content: ""; + border-top: 5px solid transparent; + border-left: 10px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid transparent; + border-right: 0px solid transparent; } table.dataTable tr.dt-hasChild td.dt-control:before { - content: "-"; - background-color: #d33333; + border-top: 10px solid rgba(0, 0, 0, 0.5); + border-left: 5px solid transparent; + border-bottom: 0px solid transparent; + border-right: 5px solid transparent; } -table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled, -table.dataTable thead > tr > td.sorting, -table.dataTable thead > tr > td.sorting_asc, -table.dataTable thead > tr > td.sorting_desc, -table.dataTable thead > tr > td.sorting_asc_disabled, -table.dataTable thead > tr > td.sorting_desc_disabled { - cursor: pointer; - position: relative; - padding-right: 26px; +html.dark table.dataTable td.dt-control:before, +:root[data-bs-theme=dark] table.dataTable td.dt-control:before { + border-left-color: rgba(255, 255, 255, 0.5); } -table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after, -table.dataTable thead > tr > td.sorting:before, -table.dataTable thead > tr > td.sorting:after, -table.dataTable thead > tr > td.sorting_asc:before, -table.dataTable thead > tr > td.sorting_asc:after, -table.dataTable thead > tr > td.sorting_desc:before, -table.dataTable thead > tr > td.sorting_desc:after, -table.dataTable thead > tr > td.sorting_asc_disabled:before, -table.dataTable thead > tr > td.sorting_asc_disabled:after, -table.dataTable thead > tr > td.sorting_desc_disabled:before, -table.dataTable thead > tr > td.sorting_desc_disabled:after { +html.dark table.dataTable tr.dt-hasChild td.dt-control:before, +:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { + border-top-color: rgba(255, 255, 255, 0.5); + border-left-color: transparent; +} + +div.dt-scroll-body thead tr, +div.dt-scroll-body tfoot tr { + height: 0; +} +div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td, +div.dt-scroll-body tfoot tr th, +div.dt-scroll-body tfoot tr td { + height: 0 !important; + padding-top: 0px !important; + padding-bottom: 0px !important; + border-top-width: 0px !important; + border-bottom-width: 0px !important; +} +div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing, +div.dt-scroll-body tfoot tr th div.dt-scroll-sizing, +div.dt-scroll-body tfoot tr td div.dt-scroll-sizing { + height: 0 !important; + overflow: hidden !important; +} + +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before { position: absolute; display: block; - opacity: 0.125; - right: 10px; - line-height: 9px; - font-size: 0.9em; -} -table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before, -table.dataTable thead > tr > td.sorting:before, -table.dataTable thead > tr > td.sorting_asc:before, -table.dataTable thead > tr > td.sorting_desc:before, -table.dataTable thead > tr > td.sorting_asc_disabled:before, -table.dataTable thead > tr > td.sorting_desc_disabled:before { bottom: 50%; - content: "▴"; + content: "▲"; + content: "▲"/""; } -table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after, -table.dataTable thead > tr > td.sorting:after, -table.dataTable thead > tr > td.sorting_asc:after, -table.dataTable thead > tr > td.sorting_desc:after, -table.dataTable thead > tr > td.sorting_asc_disabled:after, -table.dataTable thead > tr > td.sorting_desc_disabled:after { +table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + position: absolute; + display: block; top: 50%; - content: "▾"; + content: "▼"; + content: "▼"/""; } -table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after, -table.dataTable thead > tr > td.sorting_asc:before, -table.dataTable thead > tr > td.sorting_desc:after { +table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc, +table.dataTable thead > tr > td.dt-orderable-asc, +table.dataTable thead > tr > td.dt-orderable-desc, +table.dataTable thead > tr > td.dt-ordering-asc, +table.dataTable thead > tr > td.dt-ordering-desc { + position: relative; + padding-right: 30px; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order { + position: absolute; + right: 12px; + top: 0; + bottom: 0; + width: 12px; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + left: 0; + opacity: 0.125; + line-height: 9px; + font-size: 0.8em; +} +table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, +table.dataTable thead > tr > td.dt-orderable-asc, +table.dataTable thead > tr > td.dt-orderable-desc { + cursor: pointer; +} +table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover, +table.dataTable thead > tr > td.dt-orderable-asc:hover, +table.dataTable thead > tr > td.dt-orderable-desc:hover { + outline: 2px solid rgba(0, 0, 0, 0.05); + outline-offset: -2px; +} +table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { opacity: 0.6; } -table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, -table.dataTable thead > tr > td.sorting_desc_disabled:after, -table.dataTable thead > tr > td.sorting_asc_disabled:before { +table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before, +table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after, +table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before { display: none; } table.dataTable thead > tr > th:active, @@ -98,50 +155,61 @@ table.dataTable thead > tr > td:active { outline: none; } -div.dataTables_scrollBody table.dataTable thead > tr > th:before, div.dataTables_scrollBody table.dataTable thead > tr > th:after, -div.dataTables_scrollBody table.dataTable thead > tr > td:before, -div.dataTables_scrollBody table.dataTable thead > tr > td:after { - display: none; +div.dt-scroll-body > table.dataTable > thead > tr > th, +div.dt-scroll-body > table.dataTable > thead > tr > td { + overflow: hidden; } -div.dataTables_processing { +:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover, +:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover, +:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover { + outline: 2px solid rgba(255, 255, 255, 0.05); +} + +div.dt-processing { position: absolute; top: 50%; left: 50%; width: 200px; margin-left: -100px; - margin-top: -26px; + margin-top: -22px; text-align: center; padding: 2px; + z-index: 10; } -div.dataTables_processing > div:last-child { +div.dt-processing > div:last-child { position: relative; width: 80px; height: 15px; margin: 1em auto; } -div.dataTables_processing > div:last-child > div { +div.dt-processing > div:last-child > div { position: absolute; top: 0; width: 13px; height: 13px; border-radius: 50%; - background: rgba(13, 110, 253, 0.9); + background: rgb(13, 110, 253); + background: rgb(var(--dt-row-selected)); animation-timing-function: cubic-bezier(0, 1, 1, 0); } -div.dataTables_processing > div:last-child > div:nth-child(1) { +div.dt-processing > div:last-child > div:nth-child(1) { left: 8px; animation: datatables-loader-1 0.6s infinite; } -div.dataTables_processing > div:last-child > div:nth-child(2) { +div.dt-processing > div:last-child > div:nth-child(2) { left: 8px; animation: datatables-loader-2 0.6s infinite; } -div.dataTables_processing > div:last-child > div:nth-child(3) { +div.dt-processing > div:last-child > div:nth-child(3) { left: 32px; animation: datatables-loader-2 0.6s infinite; } -div.dataTables_processing > div:last-child > div:nth-child(4) { +div.dt-processing > div:last-child > div:nth-child(4) { left: 56px; animation: datatables-loader-3 0.6s infinite; } @@ -173,13 +241,16 @@ div.dataTables_processing > div:last-child > div:nth-child(4) { table.dataTable.nowrap th, table.dataTable.nowrap td { white-space: nowrap; } +table.dataTable th, +table.dataTable td { + box-sizing: border-box; +} table.dataTable th.dt-left, table.dataTable td.dt-left { text-align: left; } table.dataTable th.dt-center, -table.dataTable td.dt-center, -table.dataTable td.dataTables_empty { +table.dataTable td.dt-center { text-align: center; } table.dataTable th.dt-right, @@ -194,6 +265,16 @@ table.dataTable th.dt-nowrap, table.dataTable td.dt-nowrap { white-space: nowrap; } +table.dataTable th.dt-empty, +table.dataTable td.dt-empty { + text-align: center; + vertical-align: top; +} +table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date, +table.dataTable td.dt-type-numeric, +table.dataTable td.dt-type-date { + text-align: right; +} table.dataTable thead th, table.dataTable thead td, table.dataTable tfoot th, @@ -257,8 +338,6 @@ table.dataTable tbody td.dt-body-nowrap { table.dataTable { width: 100%; margin: 0 auto; - clear: both; - border-collapse: separate; border-spacing: 0; /* * Header and footer styles @@ -271,62 +350,78 @@ table.dataTable thead th, table.dataTable tfoot th { font-weight: bold; } -table.dataTable thead th, -table.dataTable thead td { +table.dataTable > thead > tr > th, +table.dataTable > thead > tr > td { padding: 10px; border-bottom: 1px solid rgba(0, 0, 0, 0.3); } -table.dataTable thead th:active, -table.dataTable thead td:active { +table.dataTable > thead > tr > th:active, +table.dataTable > thead > tr > td:active { outline: none; } -table.dataTable tfoot th, -table.dataTable tfoot td { - padding: 10px 10px 6px 10px; +table.dataTable > tfoot > tr > th, +table.dataTable > tfoot > tr > td { border-top: 1px solid rgba(0, 0, 0, 0.3); + padding: 10px 10px 6px 10px; } -table.dataTable tbody tr { +table.dataTable > tbody > tr { background-color: transparent; } -table.dataTable tbody tr.selected > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); - color: white; -} -table.dataTable tbody th, -table.dataTable tbody td { - padding: 8px 10px; -} -table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { - border-top: 1px solid rgba(0, 0, 0, 0.15); -} -table.dataTable.row-border tbody tr:first-child th, -table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, -table.dataTable.display tbody tr:first-child td { +table.dataTable > tbody > tr:first-child > * { border-top: none; } -table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { +table.dataTable > tbody > tr:last-child > * { + border-bottom: none; +} +table.dataTable > tbody > tr.selected > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9); + color: rgb(255, 255, 255); + color: rgb(var(--dt-row-selected-text)); +} +table.dataTable > tbody > tr.selected a { + color: rgb(9, 10, 11); + color: rgb(var(--dt-row-selected-link)); +} +table.dataTable > tbody > tr > th, +table.dataTable > tbody > tr > td { + padding: 8px 10px; +} +table.dataTable.row-border > tbody > tr > *, table.dataTable.display > tbody > tr > * { + border-top: 1px solid rgba(0, 0, 0, 0.15); +} +table.dataTable.row-border > tbody > tr:first-child > *, table.dataTable.display > tbody > tr:first-child > * { + border-top: none; +} +table.dataTable.row-border > tbody > tr.selected + tr.selected > td, table.dataTable.display > tbody > tr.selected + tr.selected > td { + border-top-color: rgba(13, 110, 253, 0.65); + border-top-color: rgba(var(--dt-row-selected), 0.65); +} +table.dataTable.cell-border > tbody > tr > * { border-top: 1px solid rgba(0, 0, 0, 0.15); border-right: 1px solid rgba(0, 0, 0, 0.15); } -table.dataTable.cell-border tbody tr th:first-child, -table.dataTable.cell-border tbody tr td:first-child { +table.dataTable.cell-border > tbody > tr > *:first-child { border-left: 1px solid rgba(0, 0, 0, 0.15); } -table.dataTable.cell-border tbody tr:first-child th, -table.dataTable.cell-border tbody tr:first-child td { - border-top: none; +table.dataTable.cell-border > tbody > tr:first-child > * { + border-top: 1px solid rgba(0, 0, 0, 0.3); } -table.dataTable.stripe > tbody > tr.odd > *, table.dataTable.display > tbody > tr.odd > * { +table.dataTable.stripe > tbody > tr:nth-child(odd) > *, table.dataTable.display > tbody > tr:nth-child(odd) > * { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.023); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.023); } -table.dataTable.stripe > tbody > tr.odd.selected > *, table.dataTable.display > tbody > tr.odd.selected > * { +table.dataTable.stripe > tbody > tr:nth-child(odd).selected > *, table.dataTable.display > tbody > tr:nth-child(odd).selected > * { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.923); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.923); } table.dataTable.hover > tbody > tr:hover > *, table.dataTable.display > tbody > tr:hover > * { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.035); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.035); } table.dataTable.hover > tbody > tr.selected:hover > *, table.dataTable.display > tbody > tr.selected:hover > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.935); + box-shadow: inset 0 0 0 9999px #0d6efd !important; + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 1) !important; } table.dataTable.order-column > tbody tr > .sorting_1, table.dataTable.order-column > tbody tr > .sorting_2, @@ -334,6 +429,7 @@ table.dataTable.order-column > tbody tr > .sorting_3, table.dataTable.display > table.dataTable.display > tbody tr > .sorting_2, table.dataTable.display > tbody tr > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019); } table.dataTable.order-column > tbody tr.selected > .sorting_1, table.dataTable.order-column > tbody tr.selected > .sorting_2, @@ -341,121 +437,143 @@ table.dataTable.order-column > tbody tr.selected > .sorting_3, table.dataTable.d table.dataTable.display > tbody tr.selected > .sorting_2, table.dataTable.display > tbody tr.selected > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919); } -table.dataTable.display > tbody > tr.odd > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.odd > .sorting_1 { +table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_1, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.054); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.054); } -table.dataTable.display > tbody > tr.odd > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.odd > .sorting_2 { +table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_2, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.047); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.047); } -table.dataTable.display > tbody > tr.odd > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.odd > .sorting_3 { +table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_3, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.039); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.039); } -table.dataTable.display > tbody > tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.odd.selected > .sorting_1 { +table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.954); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.954); } -table.dataTable.display > tbody > tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.odd.selected > .sorting_2 { +table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.947); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.947); } -table.dataTable.display > tbody > tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.odd.selected > .sorting_3 { +table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.939); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.939); } table.dataTable.display > tbody > tr.even > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019); } table.dataTable.display > tbody > tr.even > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.011); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.011); } table.dataTable.display > tbody > tr.even > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.003); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.003); } table.dataTable.display > tbody > tr.even.selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919); } table.dataTable.display > tbody > tr.even.selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.911); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.911); } table.dataTable.display > tbody > tr.even.selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.903); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.903); } table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.082); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.082); } table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.074); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.074); } table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.062); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.062); } table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.982); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.982); } table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.974); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.974); } table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.962); -} -table.dataTable.no-footer { - border-bottom: 1px solid rgba(0, 0, 0, 0.3); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.962); } table.dataTable.compact thead th, -table.dataTable.compact thead td { - padding: 4px 17px; -} +table.dataTable.compact thead td, table.dataTable.compact tfoot th, -table.dataTable.compact tfoot td { - padding: 4px; -} +table.dataTable.compact tfoot td, table.dataTable.compact tbody th, table.dataTable.compact tbody td { padding: 4px; } -table.dataTable th, -table.dataTable td { - box-sizing: content-box; -} - /* * Control feature layout */ -.dataTables_wrapper { +div.dt-container { position: relative; clear: both; } -.dataTables_wrapper { - float: left; +div.dt-container div.dt-layout-row { + display: table; + clear: both; + width: 100%; } -.dataTables_wrapper .dataTables_length select { - border: 1px solid #aaa; - border-radius: 3px; - padding: 5px; - background-color: transparent; - padding: 4px; +div.dt-container div.dt-layout-row.dt-layout-table { + display: block; } -.dataTables_wrapper .dataTables_filter { - float: right; +div.dt-container div.dt-layout-row.dt-layout-table div.dt-layout-cell { + display: block; +} +div.dt-container div.dt-layout-cell { + display: table-cell; + vertical-align: middle; + padding: 5px 0; +} +div.dt-container div.dt-layout-cell.dt-full { + text-align: center; +} +div.dt-container div.dt-layout-cell.dt-start { + text-align: left; +} +div.dt-container div.dt-layout-cell.dt-end { text-align: right; } -.dataTables_wrapper .dataTables_filter input { +div.dt-container div.dt-layout-cell:empty { + display: none; +} +div.dt-container .dt-search input { border: 1px solid #aaa; border-radius: 3px; padding: 5px; background-color: transparent; + color: inherit; margin-left: 3px; } -.dataTables_wrapper .dataTables_info { - clear: both; - float: left; - padding-top: 0.755em; +div.dt-container .dt-input { + border: 1px solid #aaa; + border-radius: 3px; + padding: 5px; + background-color: transparent; + color: inherit; } -.dataTables_wrapper .dataTables_paginate { - float: right; - text-align: right; - padding-top: 0.25em; +div.dt-container select.dt-input { + padding: 4px; } -.dataTables_wrapper .dataTables_paginate .paginate_button { +div.dt-container .dt-paging .dt-paging-button { box-sizing: border-box; display: inline-block; min-width: 1.5em; @@ -464,130 +582,191 @@ table.dataTable td { text-align: center; text-decoration: none !important; cursor: pointer; - color: #333 !important; + color: inherit !important; border: 1px solid transparent; border-radius: 2px; + background: transparent; } -.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { - color: #333 !important; +div.dt-container .dt-paging .dt-paging-button.current, div.dt-container .dt-paging .dt-paging-button.current:hover { + color: inherit !important; border: 1px solid rgba(0, 0, 0, 0.3); - background-color: rgba(230, 230, 230, 0.1); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.1)), color-stop(100%, rgba(0, 0, 0, 0.1))); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* IE10+ */ - background: -o-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* W3C */ + background-color: rgba(0, 0, 0, 0.05); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.05)), color-stop(100%, rgba(0, 0, 0, 0.05))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* IE10+ */ + background: -o-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* W3C */ } -.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { +div.dt-container .dt-paging .dt-paging-button.disabled, div.dt-container .dt-paging .dt-paging-button.disabled:hover, div.dt-container .dt-paging .dt-paging-button.disabled:active { cursor: default; - color: #666 !important; + color: rgba(0, 0, 0, 0.5) !important; border: 1px solid transparent; background: transparent; box-shadow: none; } -.dataTables_wrapper .dataTables_paginate .paginate_button:hover { +div.dt-container .dt-paging .dt-paging-button:hover { color: white !important; border: 1px solid #111; - background-color: #585858; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #585858 0%, #111 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, #585858 0%, #111 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, #585858 0%, #111 100%); - /* IE10+ */ - background: -o-linear-gradient(top, #585858 0%, #111 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, #585858 0%, #111 100%); - /* W3C */ + background-color: #111; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #585858 0%, #111 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #585858 0%, #111 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, #585858 0%, #111 100%); /* IE10+ */ + background: -o-linear-gradient(top, #585858 0%, #111 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, #585858 0%, #111 100%); /* W3C */ } -.dataTables_wrapper .dataTables_paginate .paginate_button:active { +div.dt-container .dt-paging .dt-paging-button:active { outline: none; - background-color: #2b2b2b; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* IE10+ */ - background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); - /* W3C */ + background-color: #0c0c0c; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* IE10+ */ + background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); /* W3C */ box-shadow: inset 0 0 3px #111; } -.dataTables_wrapper .dataTables_paginate .ellipsis { +div.dt-container .dt-paging .ellipsis { padding: 0 1em; } -.dataTables_wrapper .dataTables_length, -.dataTables_wrapper .dataTables_filter, -.dataTables_wrapper .dataTables_info, -.dataTables_wrapper .dataTables_processing, -.dataTables_wrapper .dataTables_paginate { - color: #333; +div.dt-container .dt-length, +div.dt-container .dt-search, +div.dt-container .dt-info, +div.dt-container .dt-processing, +div.dt-container .dt-paging { + color: inherit; } -.dataTables_wrapper .dataTables_scroll { +div.dt-container .dataTables_scroll { clear: both; } -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { +div.dt-container .dataTables_scroll div.dt-scroll-body { -webkit-overflow-scrolling: touch; } -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td { +div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > th, div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > td, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > th, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > td { vertical-align: middle; } -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing { +div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > th > div.dataTables_sizing, +div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > td > div.dataTables_sizing, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > th > div.dataTables_sizing, +div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > td > div.dataTables_sizing { height: 0; overflow: hidden; margin: 0 !important; padding: 0 !important; } -.dataTables_wrapper.no-footer .dataTables_scrollBody { +div.dt-container.dt-empty-footer tbody > tr:last-child > * { border-bottom: 1px solid rgba(0, 0, 0, 0.3); } -.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, -.dataTables_wrapper.no-footer div.dataTables_scrollBody > table { - border-bottom: none; +div.dt-container.dt-empty-footer .dt-scroll-body { + border-bottom: 1px solid rgba(0, 0, 0, 0.3); } -.dataTables_wrapper:after { - visibility: hidden; - display: block; - content: ""; - clear: both; - height: 0; +div.dt-container.dt-empty-footer .dt-scroll-body tbody > tr:last-child > * { + border-bottom: none; } @media screen and (max-width: 767px) { - .dataTables_wrapper .dataTables_info, -.dataTables_wrapper .dataTables_paginate { - float: none; - text-align: center; + div.dt-container div.dt-layout-row { + display: block; } - .dataTables_wrapper .dataTables_paginate { - margin-top: 0.5em; + div.dt-container div.dt-layout-cell { + display: block; + } + div.dt-container div.dt-layout-cell.dt-full, div.dt-container div.dt-layout-cell.dt-start, div.dt-container div.dt-layout-cell.dt-end { + text-align: center; } } @media screen and (max-width: 640px) { - .dataTables_wrapper .dataTables_length, -.dataTables_wrapper .dataTables_filter { + .dt-container .dt-length, + .dt-container .dt-search { float: none; text-align: center; } - .dataTables_wrapper .dataTables_filter { + .dt-container .dt-search { margin-top: 0.5em; } } +html.dark { + --dt-row-hover: 255, 255, 255; + --dt-row-stripe: 255, 255, 255; + --dt-column-ordering: 255, 255, 255; +} +html.dark table.dataTable > thead > tr > th, +html.dark table.dataTable > thead > tr > td { + border-bottom: 1px solid rgb(89, 91, 94); +} +html.dark table.dataTable > thead > tr > th:active, +html.dark table.dataTable > thead > tr > td:active { + outline: none; +} +html.dark table.dataTable > tfoot > tr > th, +html.dark table.dataTable > tfoot > tr > td { + border-top: 1px solid rgb(89, 91, 94); +} +html.dark table.dataTable.row-border > tbody > tr > *, html.dark table.dataTable.display > tbody > tr > * { + border-top: 1px solid rgb(64, 67, 70); +} +html.dark table.dataTable.row-border > tbody > tr:first-child > *, html.dark table.dataTable.display > tbody > tr:first-child > * { + border-top: none; +} +html.dark table.dataTable.row-border > tbody > tr.selected + tr.selected > td, html.dark table.dataTable.display > tbody > tr.selected + tr.selected > td { + border-top-color: rgba(13, 110, 253, 0.65); + border-top-color: rgba(var(--dt-row-selected), 0.65); +} +html.dark table.dataTable.cell-border > tbody > tr > th, +html.dark table.dataTable.cell-border > tbody > tr > td { + border-top: 1px solid rgb(64, 67, 70); + border-right: 1px solid rgb(64, 67, 70); +} +html.dark table.dataTable.cell-border > tbody > tr > th:first-child, +html.dark table.dataTable.cell-border > tbody > tr > td:first-child { + border-left: 1px solid rgb(64, 67, 70); +} +html.dark .dt-container.dt-empty-footer table.dataTable { + border-bottom: 1px solid rgb(89, 91, 94); +} +html.dark .dt-container .dt-search input, +html.dark .dt-container .dt-length select { + border: 1px solid rgba(255, 255, 255, 0.2); + background-color: var(--dt-html-background); +} +html.dark .dt-container .dt-paging .dt-paging-button.current, html.dark .dt-container .dt-paging .dt-paging-button.current:hover { + border: 1px solid rgb(89, 91, 94); + background: rgba(255, 255, 255, 0.15); +} +html.dark .dt-container .dt-paging .dt-paging-button.disabled, html.dark .dt-container .dt-paging .dt-paging-button.disabled:hover, html.dark .dt-container .dt-paging .dt-paging-button.disabled:active { + color: #666 !important; +} +html.dark .dt-container .dt-paging .dt-paging-button:hover { + border: 1px solid rgb(53, 53, 53); + background: rgb(53, 53, 53); +} +html.dark .dt-container .dt-paging .dt-paging-button:active { + background: #3a3a3a; +} + +/* + * Overrides for RTL support + */ +*[dir=rtl] table.dataTable thead th, +*[dir=rtl] table.dataTable thead td, +*[dir=rtl] table.dataTable tfoot th, +*[dir=rtl] table.dataTable tfoot td { + text-align: right; +} +*[dir=rtl] table.dataTable th.dt-type-numeric, *[dir=rtl] table.dataTable th.dt-type-date, +*[dir=rtl] table.dataTable td.dt-type-numeric, +*[dir=rtl] table.dataTable td.dt-type-date { + text-align: left; +} +*[dir=rtl] div.dt-container div.dt-layout-cell.dt-start { + text-align: right; +} +*[dir=rtl] div.dt-container div.dt-layout-cell.dt-end { + text-align: left; +} +*[dir=rtl] div.dt-container div.dt-search input { + margin: 0 3px 0 0; +} @keyframes dtb-spinner { @@ -626,6 +805,11 @@ div.dataTables_wrapper { div.dt-buttons { position: initial; } +div.dt-buttons .dt-button { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} div.dt-button-info { position: fixed; @@ -635,31 +819,27 @@ div.dt-button-info { margin-top: -100px; margin-left: -200px; background-color: white; - border: 2px solid #111; - box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3); - border-radius: 3px; + border-radius: 0.75em; + box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8); text-align: center; - z-index: 21; + z-index: 2003; + overflow: hidden; } div.dt-button-info h2 { - padding: 0.5em; + padding: 2rem 2rem 1rem 2rem; margin: 0; font-weight: normal; - border-bottom: 1px solid #ddd; - background-color: #f3f3f3; } div.dt-button-info > div { - padding: 1em; + padding: 1em 2em 2em 2em; } div.dtb-popover-close { position: absolute; - top: 10px; - right: 10px; + top: 6px; + right: 6px; width: 22px; height: 22px; - border: 1px solid #eaeaea; - background-color: #f9f9f9; text-align: center; border-radius: 3px; cursor: pointer; @@ -672,10 +852,13 @@ button.dtb-hide-drop { div.dt-button-collection-title { text-align: center; - padding: 0.3em 0 0.5em; + padding: 0.3em 0.5em 0.5em; margin-left: 0.5em; margin-right: 0.5em; font-size: 0.9em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } div.dt-button-collection-title:empty { @@ -698,6 +881,46 @@ span.dt-button-spacer.bar:empty { padding-left: 0; } +div.dt-button-collection .dt-button-active { + padding-right: 3em; +} +div.dt-button-collection .dt-button-active:after { + position: absolute; + top: 50%; + margin-top: -10px; + right: 1em; + display: inline-block; + content: "✓"; + color: inherit; +} +div.dt-button-collection .dt-button-active.dt-button-split { + padding-right: 0; +} +div.dt-button-collection .dt-button-active.dt-button-split:after { + display: none; +} +div.dt-button-collection .dt-button-active.dt-button-split > *:first-child { + padding-right: 3em; +} +div.dt-button-collection .dt-button-active.dt-button-split > *:first-child:after { + position: absolute; + top: 50%; + margin-top: -10px; + right: 1em; + display: inline-block; + content: "✓"; + color: inherit; +} +div.dt-button-collection .dt-button-active-a a { + padding-right: 3em; +} +div.dt-button-collection .dt-button-active-a a:after { + position: absolute; + right: 1em; + display: inline-block; + content: "✓"; + color: inherit; +} div.dt-button-collection span.dt-button-spacer { width: 100%; font-size: 0.9em; @@ -710,14 +933,22 @@ div.dt-button-collection span.dt-button-spacer:empty { } div.dt-button-collection span.dt-button-spacer.bar { border-left: none; - border-bottom: 1px solid rgba(0, 0, 0, 0.3); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); padding-left: 0; } -button.dt-button, -div.dt-button, -a.dt-button, -input.dt-button { +@media print { + table.dataTable tr > * { + box-shadow: none !important; + } +} +html.dark div.dt-button-info { + background-color: var(--dt-html-background); + border: 1px solid rgba(255, 255, 255, 0.15); +} + +div.dt-buttons > .dt-button, +div.dt-buttons > div.dt-button-split .dt-button { position: relative; display: inline-block; box-sizing: border-box; @@ -730,11 +961,10 @@ input.dt-button { cursor: pointer; font-size: 0.88em; line-height: 1.6em; - color: black; + color: inherit; white-space: nowrap; overflow: hidden; - background-color: rgba(0, 0, 0, 0.1); - /* Fallback */ + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ background: linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(230, 230, 230, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); -webkit-user-select: none; @@ -745,106 +975,87 @@ input.dt-button { outline: none; text-overflow: ellipsis; } -button.dt-button:first-child, -div.dt-button:first-child, -a.dt-button:first-child, -input.dt-button:first-child { +div.dt-buttons > .dt-button:first-child, +div.dt-buttons > div.dt-button-split .dt-button:first-child { margin-left: 0; } -button.dt-button.disabled, -div.dt-button.disabled, -a.dt-button.disabled, -input.dt-button.disabled { +div.dt-buttons > .dt-button.disabled, +div.dt-buttons > div.dt-button-split .dt-button.disabled { cursor: default; opacity: 0.4; } -button.dt-button:active:not(.disabled), -div.dt-button:active:not(.disabled), -a.dt-button:active:not(.disabled), -input.dt-button:active:not(.disabled) { - background-color: rgba(0, 0, 0, 0.1); - /* Fallback */ +div.dt-buttons > .dt-button.dt-button-active:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled) { + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ background: linear-gradient(to bottom, rgba(179, 179, 179, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(179, 179, 179, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); box-shadow: inset 1px 1px 3px #999999; } -button.dt-button:active:not(.disabled):hover:not(.disabled), -div.dt-button:active:not(.disabled):hover:not(.disabled), -a.dt-button:active:not(.disabled):hover:not(.disabled), -input.dt-button:active:not(.disabled):hover:not(.disabled) { +div.dt-buttons > .dt-button.dt-button-active:not(.disabled):hover:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled):hover:not(.disabled) { box-shadow: inset 1px 1px 3px #999999; - background-color: rgba(0, 0, 0, 0.1); - /* Fallback */ + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ background: linear-gradient(to bottom, rgba(128, 128, 128, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(128, 128, 128, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); } -button.dt-button:hover, -div.dt-button:hover, -a.dt-button:hover, -input.dt-button:hover { +div.dt-buttons > .dt-button:hover, +div.dt-buttons > div.dt-button-split .dt-button:hover { text-decoration: none; } -button.dt-button:hover:not(.disabled), -div.dt-button:hover:not(.disabled), -a.dt-button:hover:not(.disabled), -input.dt-button:hover:not(.disabled) { +div.dt-buttons > .dt-button:hover:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button:hover:not(.disabled) { border: 1px solid #666; - background-color: rgba(0, 0, 0, 0.1); - /* Fallback */ + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ background: linear-gradient(to bottom, rgba(153, 153, 153, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(153, 153, 153, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); } -button.dt-button:focus:not(.disabled), -div.dt-button:focus:not(.disabled), -a.dt-button:focus:not(.disabled), -input.dt-button:focus:not(.disabled) { - border: 1px solid #426c9e; - text-shadow: 0 1px 0 #c4def1; +div.dt-buttons > .dt-button:focus:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button:focus:not(.disabled) { + outline: 2px solid rgb(53, 132, 228); +} +div.dt-buttons > .dt-button embed, +div.dt-buttons > div.dt-button-split .dt-button embed { outline: none; - background-color: #79ace9; - /* Fallback */ - background: linear-gradient(to bottom, #d1e2f7 0%, #79ace9 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#d1e2f7", EndColorStr="#79ace9"); } -button.dt-button.active:focus:not(.disabled), -div.dt-button.active:focus:not(.disabled), -a.dt-button.active:focus:not(.disabled), -input.dt-button.active:focus:not(.disabled) { - background: linear-gradient(to bottom, #d1e2f7 0%, #79ace9 100%) !important; +div.dt-buttons > div.dt-button-split .dt-button:first-child { + border-right: 1px solid rgba(0, 0, 0, 0.15); + border-top-right-radius: 0; + border-bottom-right-radius: 0; } -button.dt-button span.dt-down-arrow, -div.dt-button span.dt-down-arrow, -a.dt-button span.dt-down-arrow, -input.dt-button span.dt-down-arrow { +div.dt-buttons > div.dt-button-split .dt-button:first-child:hover { + border-right: 1px solid #666; +} +div.dt-buttons > div.dt-button-split .dt-button:last-child { + border-left: 1px solid transparent; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +div.dt-buttons > div.dt-button-split .dt-button:last-child:hover { + border-left: 1px solid #666; +} +div.dt-buttons span.dt-button-down-arrow { position: relative; top: -2px; - color: rgba(70, 70, 70, 0.75); - font-size: 8px; + font-size: 10px; padding-left: 10px; line-height: 1em; + opacity: 0.6; } - -.dt-button embed { - outline: none; -} - -div.dt-buttons { - float: left; -} -div.dt-buttons.buttons-right { - float: right; -} - -div.dataTables_layout_cell div.dt-buttons { - float: none; -} -div.dataTables_layout_cell div.dt-buttons.buttons-right { - float: none; -} - -div.dt-btn-split-wrapper { +div.dt-buttons div.dt-button-split { display: inline-block; } +div.dt-buttons div.dt-button-split .dt-button:first-child { + margin-right: 0; +} +div.dt-buttons div.dt-button-split .dt-button:last-child { + margin-left: -1px; + padding-left: 0.75em; + padding-right: 0.75em; + z-index: 2; +} +div.dt-buttons div.dt-button-split .dt-button:last-child span { + padding-left: 0; +} div.dt-button-collection { position: absolute; @@ -853,8 +1064,7 @@ div.dt-button-collection { width: 200px; margin-top: 3px; margin-bottom: 3px; - padding: 4px 4px 2px 4px; - border: 1px solid #ccc; + padding: 0.75em 0; border: 1px solid rgba(0, 0, 0, 0.4); background-color: white; overflow: hidden; @@ -863,98 +1073,57 @@ div.dt-button-collection { box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3); box-sizing: border-box; } -div.dt-button-collection button.dt-button, -div.dt-button-collection div.dt-button, -div.dt-button-collection a.dt-button { +div.dt-button-collection .dt-button { position: relative; left: 0; right: 0; width: 100%; display: block; float: none; - margin: 4px 0 2px 0; + background: none; + margin: 0; + padding: 0.5em 1em; + border: none; + text-align: left; + cursor: pointer; + color: inherit; } -div.dt-button-collection button.dt-button:active:not(.disabled), div.dt-button-collection button.dt-button.active:not(.disabled), -div.dt-button-collection div.dt-button:active:not(.disabled), -div.dt-button-collection div.dt-button.active:not(.disabled), -div.dt-button-collection a.dt-button:active:not(.disabled), -div.dt-button-collection a.dt-button.active:not(.disabled) { - background-color: #dadada; - /* Fallback */ - background: linear-gradient(to bottom, #f0f0f0 0%, #dadada 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#f0f0f0", EndColorStr="#dadada"); - box-shadow: inset 1px 1px 3px #666; +div.dt-button-collection .dt-button.dt-button-active { + background: none; + box-shadow: none; } -div.dt-button-collection button.dt-button:first-child, -div.dt-button-collection div.dt-button:first-child, -div.dt-button-collection a.dt-button:first-child { - margin-top: 0; - border-top-left-radius: 3px; - border-top-right-radius: 3px; +div.dt-button-collection .dt-button.disabled { + cursor: default; + opacity: 0.4; } -div.dt-button-collection button.dt-button:last-child, -div.dt-button-collection div.dt-button:last-child, -div.dt-button-collection a.dt-button:last-child { - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; +div.dt-button-collection .dt-button:hover:not(.disabled) { + border: none; + background: rgba(153, 153, 153, 0.1); + box-shadow: none; } -div.dt-button-collection div.dt-btn-split-wrapper { +div.dt-button-collection div.dt-button-split { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-start; align-content: flex-start; align-items: stretch; - margin: 4px 0 2px 0; } -div.dt-button-collection div.dt-btn-split-wrapper button.dt-button { +div.dt-button-collection div.dt-button-split button.dt-button { margin: 0; display: inline-block; width: 0; flex-grow: 1; flex-shrink: 0; flex-basis: 50px; - border-radius: 0; } -div.dt-button-collection div.dt-btn-split-wrapper button.dt-btn-split-drop { - min-width: 20px; - flex-grow: 0; - flex-shrink: 0; - flex-basis: 0; +div.dt-button-collection div.dt-button-split button.dt-button-split-drop { + min-width: 33px; + flex: 0; } -div.dt-button-collection div.dt-btn-split-wrapper:first-child { - margin-top: 0; -} -div.dt-button-collection div.dt-btn-split-wrapper:first-child button.dt-button { - border-top-left-radius: 3px; -} -div.dt-button-collection div.dt-btn-split-wrapper:first-child button.dt-btn-split-drop { - border-top-right-radius: 3px; -} -div.dt-button-collection div.dt-btn-split-wrapper:last-child button.dt-button { - border-bottom-left-radius: 3px; -} -div.dt-button-collection div.dt-btn-split-wrapper:last-child button.dt-btn-split-drop { - border-bottom-right-radius: 3px; -} -div.dt-button-collection div.dt-btn-split-wrapper:active:not(.disabled) button.dt-button, div.dt-button-collection div.dt-btn-split-wrapper.active:not(.disabled) button.dt-button { - background-color: #dadada; - /* Fallback */ - background: linear-gradient(to bottom, #f0f0f0 0%, #dadada 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#f0f0f0", EndColorStr="#dadada"); - box-shadow: inset 0px 0px 4px #666; -} -div.dt-button-collection div.dt-btn-split-wrapper:active:not(.disabled) button.dt-btn-split-drop, div.dt-button-collection div.dt-btn-split-wrapper.active:not(.disabled) button.dt-btn-split-drop { - box-shadow: none; -} -div.dt-button-collection.fixed .dt-button:first-child { - margin-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -div.dt-button-collection.fixed .dt-button:last-child { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; +div.dt-button-collection.fixed .dt-button { + border-radius: 0.25em; + background: rgba(255, 255, 255, 0.1); } div.dt-button-collection.fixed { position: fixed; @@ -964,6 +1133,7 @@ div.dt-button-collection.fixed { margin-left: -75px; border-radius: 5px; background-color: white; + padding: 0.5em; } div.dt-button-collection.fixed.two-column { margin-left: -200px; @@ -1086,27 +1256,15 @@ div.dt-button-background { left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.7); - /* Fallback */ - background: radial-gradient(ellipse farthest-corner at center, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); - /* W3C Markup, IE10 Release Preview */ + background: rgba(0, 0, 0, 0.7); /* Fallback */ + background: radial-gradient(ellipse farthest-corner at center, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); /* W3C Markup, IE10 Release Preview */ z-index: 2001; } -@media screen and (max-width: 640px) { - div.dt-buttons { - float: none !important; - text-align: center; - } -} -button.dt-button.processing, -div.dt-button.processing, -a.dt-button.processing { +.dt-button.processing { color: rgba(0, 0, 0, 0.2); } -button.dt-button.processing:after, -div.dt-button.processing:after, -a.dt-button.processing:after { +.dt-button.processing:after { position: absolute; top: 50%; left: 50%; @@ -1116,7 +1274,7 @@ a.dt-button.processing:after { box-sizing: border-box; display: block; content: " "; - border: 2px solid #282828; + border: 2px solid rgb(40, 40, 40); border-radius: 50%; border-left-color: transparent; border-right-color: transparent; @@ -1127,453 +1285,697 @@ a.dt-button.processing:after { -moz-animation: dtb-spinner 1500ms infinite linear; } -button.dt-btn-split-drop { - margin-left: calc(-1px - 0.333em); - padding-bottom: calc(0.5em - 1px); - border-radius: 0px 1px 1px 0px; - color: rgba(70, 70, 70, 0.9); - border-left: none; +@media screen and (max-width: 640px) { + div.dt-buttons { + float: none !important; + text-align: center; + } } -button.dt-btn-split-drop span.dt-btn-split-drop-arrow { - position: relative; - top: -1px; - left: -2px; - font-size: 8px; +html.dark div.dt-buttons > .dt-button, +html.dark div.dt-buttons > div.dt-button-split .dt-button { + border: 1px solid rgb(89, 91, 94); + background: rgba(255, 255, 255, 0.15); } -button.dt-btn-split-drop:hover { - z-index: 2; +html.dark div.dt-buttons > .dt-button.dt-button-active:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled) { + background: rgba(179, 179, 179, 0.15); + box-shadow: inset 1px 1px 2px black; } - -button.buttons-split { - border-right: 1px solid rgba(70, 70, 70, 0); - border-radius: 1px 0px 0px 1px; +html.dark div.dt-buttons > .dt-button.dt-button-active:not(.disabled):hover:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled):hover:not(.disabled) { + background: rgba(128, 128, 128, 0.15); + box-shadow: inset 1px 1px 3px black; } - -button.dt-btn-split-drop-button { - background-color: white; +html.dark div.dt-buttons > .dt-button:hover:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button:hover:not(.disabled) { + background: rgba(179, 179, 179, 0.15); } -button.dt-btn-split-drop-button:hover { - background-color: white; +html.dark div.dt-buttons > .dt-button:focus:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button:focus:not(.disabled) { + outline: 2px solid rgb(110, 168, 254); +} +html.dark div.dt-buttons > div.dt-button-split .dt-button:first-child { + border-right: 1px solid rgba(255, 255, 255, 0.1); +} +html.dark div.dt-buttons > div.dt-button-split .dt-button:first-child:hover { + border-right: 1px solid rgb(89, 91, 94); +} +html.dark div.dt-buttons > div.dt-button-split .dt-button:last-child:hover { + border-left: 1px solid rgb(89, 91, 94); +} +html.dark div.dt-button-collection { + border: 1px solid rgba(255, 255, 255, 0.15); + background-color: rgb(33, 37, 41); + box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8); } -table.DTCR_clonedTable.dataTable { +body.dtcr-dragging { + overflow-x: hidden; +} + +table.dtcr-cloned.dataTable { position: absolute !important; background-color: rgba(255, 255, 255, 0.7); z-index: 202; + border-radius: 4px; } -div.DTCR_pointer { - width: 1px; - background-color: #0259C4; - z-index: 201; +table.dataTable tbody tr td.dtcr-moving { + background-color: rgba(127, 127, 127, 0.15); +} +table.dataTable tbody tr td.dtcr-moving-first { + border-left: 1px solid #0259C4; +} +table.dataTable tbody tr td.dtcr-moving-last { + border-right: 1px solid #0259C4; +} + +html.dark table.dtcr-cloned.dataTable { + background-color: rgba(33, 33, 33, 0.9); } -table.fixedHeader-floating { - background-color: white; -} - -table.fixedHeader-floating.no-footer { - border-bottom-width: 0; -} - -table.fixedHeader-locked { - position: absolute !important; - background-color: white; -} - -@media print { - table.fixedHeader-floating { - display: none; - } -} - - -div.dtsp-topRow { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - border: 2px solid rgba(0, 0, 0, 0); - border-radius: 3px; - justify-content: space-around; - align-content: flex-start; - align-items: flex-start; - min-height: 37px; -} -div.dtsp-topRow input.dtsp-search { - text-overflow: ellipsis; - min-width: 50px; - flex-basis: 90px; - max-width: none; -} -div.dtsp-topRow input.dtsp-search::placeholder { - color: black; -} -div.dtsp-topRow div.dtsp-subRow1 { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - flex: 1 1 auto; -} -div.dtsp-topRow div.dtsp-subRow1 div.dtsp-searchCont { - position: relative; - width: 100%; -} -div.dtsp-topRow div.dtsp-subRow1 div.dtsp-searchCont input.dtsp-disabledButton { - padding-top: 10px; - padding-bottom: 10px; - background-color: transparent; -} -div.dtsp-topRow div.dtsp-subRow1 input { - padding-right: 2em; - width: 100% !important; - box-sizing: border-box; - font-size: 1em; -} -div.dtsp-topRow div.dtsp-subRow1 button.dtsp-searchIcon { +div.dt-datetime { position: absolute; - top: 0; - right: 0; - bottom: 0; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAABbmlDQ1BpY2MAACiRdZE7SwNBFIU/EyWikRRaiFhsoWKhEBREO42FTZAQFYza7G5eQhKX3QQJtoKNhWAh2vgq/AfaCrYKgqAIIhb+Al+NhPVOEkiQZJbZ+3FmzmXmDHjCGTPrtAYhm8vb0bmQthxb0Xzv+PHRxRSabjrWTCQSpun4eaRF1YdR1av5voajM55wTGhpF54wLTsvPC0c3sxbineFe8y0Hhc+ER6x5YDCt0o3KvymOFXhL8X2YnQWPKqnlqpjo47NtJ0VHhYeyGYKZvU86ib+RG5pQWqfzH4coswRQsOgwDoZ8oxKzUlmjX3Bsm+eDfGY8rcoYosjRVq8I6IWpGtCalL0hHwZiir3/3k6yfGxSnd/CNpeXfdzEHz7UNpz3d9T1y2dgfcFrnM1/4bkNPkt+l5NGziGwDZc3tQ04wCudqD32dJtvSx5ZXqSSfi4gK4YdN9Dx2olq+o650+wuCVPdAeHRzAk+wNrfw8JaBFXEnV+AAAACXBIWXMAAA9hAAAPYQGoP6dpAAABMUlEQVQoU6XRr0vDQRjH8akoM4iIjqGoOIZ5oIjB5XWxajaYDGLSIhhNYjcPRDSJwbQNw+L+BNGgYYo/5pT5/shz8vDlBgMPXux7z3N3z+25VOofYyCyd4ZYCavI4gXPsRp9LqiDdrEMH+8wv8Vh8gBfWclFPOEUN3hAHjlMoRa7wTzBS5xgKLFglPkZLjDic6HyDsEMNvGR2Nxifoci3tEI+X770JU0XmPXIlax+LTPh83fFox1X6kxyzdjm9UcdXi9S+Vti6svfyNULhNR9TVsYNhW6Ff9KKCNR7/Zv6eeaQ+6+qcdpu9BqGlp1HFgud+FYdzzUcUExu0Q/cdzHGEFetIlXKPjK/sbqYoOftMiS+j9jzEJPd1Wt+5+kdR/9EM9ucIC5jCbyPc01Q32kfsBppYz3hYFcCwAAAAASUVORK5CYII=") !important; - background-repeat: no-repeat; - background-position: center; - background-size: 12px; + background-color: white; + z-index: 2050; + border: 1px solid #ccc; + box-shadow: 0 5px 15px -5px rgba(0, 0, 0, 0.5); + padding: 6px 20px; + width: 275px; + border-radius: 5px; } -div.dtsp-topRow div.dtsp-subRow2 { - white-space: nowrap; - flex: 0 0 auto; -} -div.dtsp-topRow button.dtsp-nameButton { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAABcGlDQ1BpY2MAACiRdZHNSwJBGMYftTDS8FCHkA57sOigIAXRMQzyYh3UIKvL7rirwe66zK6IdA26dBA6RF36OvQf1DXoWhAERRAR9B/0dQnZ3nEFJXSG2ffHs/O8zDwD+DM6M+yBJGCYDs+mU9JaYV0KviNMM4QoEjKzreXcUh59x88jfKI+JESv/vt6jlBRtRngGyKeYxZ3iBeIMzXHErxHPMbKcpH4hDjO6YDEt0JXPH4TXPL4SzDPZxcBv+gplbpY6WJW5gbxNHHM0KusfR5xk7BqruaoRmlNwEYWaaQgQUEVW9DhIEHVpMx6+5It3woq5GH0tVAHJ0cJZfLGSa1SV5WqRrpKU0dd5P4/T1ubnfG6h1PA4Kvrfk4CwX2g2XDd31PXbZ4BgRfg2uz4K5TT/DfpjY4WOwYiO8DlTUdTDoCrXWD82ZK53JICtPyaBnxcACMFYPQeGN7wsmr/x/kTkN+mJ7oDDo+AKdof2fwDCBRoDkL8UccAAAAJcEhZcwAAD2EAAA9hAag/p2kAAAK2SURBVFgJ7ZY9j41BFICvryCExrJBQ6HyEYVEIREaUZDQIRoR2ViJKCioxV+gkVXYTVZEQiEUhG2EQnxUCh0FKolY4ut5XnM2cyfva3Pt5m7EPcmzZ2bemTNnzjkzd1utnvQi0IvAfxiBy5z5FoxO89kPY+8mbMjtzs47RXs5/WVpbAG6bWExt5PuIibvhVkwmC+ck3eK9ln6/fAddFojYzBVuYSBpcnIEvRaqOw2RcaN18FPuJH0JvRUxbT3wWf4ltiKPgfVidWlbGZgPozDFfgAC+EA/K2EI4cwcAJ+gPaeQ+VQU2SOMMGcPgPl/m/V2p50rrbRsRgt9Iv5h6xtpP22Bz7Ce1C+gFFxfKzOmShcU+Qmyh2w3w8rIJfddHTck66EukL/xPhj+JM8rHNmFys0Pg4v0up3aFNlwR9NYyodd3OL/C64zpsymcTFcf6ElM4YzjAWKYrJkaq8kE/yUYNP4BoYvS1QRo+hNtF5xfkTUjoTheukSFFMjlTFm6PjceOca/SMpKfeCR1L6Uzk/y2WIkVhNFJlJAZhP+hYns7b9D3IPuhY5mYrIv8OrQJvR5NYyNaW4jsU8pSGNySiVx4o5tXq3JkoXE/mg5R/M8dGJCJpKhaDcjBRdbI/Rm8g69c122om33BHmj2CHoV5qa9jUXBraJ+G1fAVjIBO1klc87ro1K4JZ/K35SWW3TwcyDd6TecqnAEd8cGq2+w84xvBm1n3vS0izKkkwh5XNC/GmFPqqAtPF89AOScKuemaNzoTV1SD5dtSbmLf1/RV+tC0WTgcj6R7HEtrVGWaqu/lYDZ/2pvxQ/kIyw/gFByHC9AHw910hv1aUUumyd8yy0QfhmEkfiNod0Xusct68J1qc8Tdux0Z97Q+hsDb+AYGYEbF/4Guw2Q/qDPqZG/zXgT+3Qj8AtKnfWhFwmuAAAAAAElFTkSuQmCC") !important; - background-repeat: no-repeat; - background-position: center; - background-size: 23px; - vertical-align: bottom; -} -div.dtsp-topRow button.dtsp-countButton { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABcGlDQ1BpY2MAACiRdZHNSwJBGMYftTDS8FCHkA57sOigIAXRMQzyYh3UIKvL7rirwe66zK6IdA26dBA6RF36OvQf1DXoWhAERRAR9B/0dQnZ3nEFJXSG2ffHs/O8zDwD+DM6M+yBJGCYDs+mU9JaYV0KviNMM4QoEjKzreXcUh59x88jfKI+JESv/vt6jlBRtRngGyKeYxZ3iBeIMzXHErxHPMbKcpH4hDjO6YDEt0JXPH4TXPL4SzDPZxcBv+gplbpY6WJW5gbxNHHM0KusfR5xk7BqruaoRmlNwEYWaaQgQUEVW9DhIEHVpMx6+5It3woq5GH0tVAHJ0cJZfLGSa1SV5WqRrpKU0dd5P4/T1ubnfG6h1PA4Kvrfk4CwX2g2XDd31PXbZ4BgRfg2uz4K5TT/DfpjY4WOwYiO8DlTUdTDoCrXWD82ZK53JICtPyaBnxcACMFYPQeGN7wsmr/x/kTkN+mJ7oDDo+AKdof2fwDCBRoDkL8UccAAAAJcEhZcwAAD2EAAA9hAag/p2kAAAG5SURBVEgN3VU9LwVBFF0fiYhofUSlEQkKhU7z/oBCQkIiGr9BgUbhVzy9BAnhFyjV/AYFiU5ICM7ZN+c5Zud5dm3lJmfmzrkz9+7cu3c3y/6jjOBSF8CxXS7FmTkbwqIJjDpJvTcmsJ4K3KPZUpyZsx0sxoB9J6mnAkyC7wGuuCFIipNtEcpcWExgXpOBc78vgj6N+QO4NVsjwdFM59tUIDxDrHMBOeIQ34C5ZDregXuAQm4YcI68nN9B3wr2PcwPAIPkN2EqtJH6b+QZm1ajjTx7BqwAr26Lb+C2Kvpbt0Mb2HAJ7NrGFGfmXO3DeA4UshDfQAVmH0gaUFg852TTTDvlxwBlCtxy9zXyBhQFaq0wMmIdRebrfgosA3zb2hKnqG0oqchp4QbuR8X0TjzABhbdOT8jnQ/atcgqpnfwOA7yqZyTU587ZkIGdesLTt2EkynOnbreMUUKMI/dA4B/QVOcO13CQh+5wWCgDwo/75u59odB/wjmfhbgvACcAOyZPHihMWAoIwxyCLgf1oxfgjzVbgBXSTzIN+f0pg6s5DkcesLMRpsBrgE2XO3CN64JFP7JtUeKHX4CKtRRXFZ+7dEAAAAASUVORK5CYII=") !important; - background-repeat: no-repeat; - background-position: center; - background-size: 18px; - vertical-align: bottom; -} -div.dtsp-topRow button.dtsp-collapseButton span.dtsp-caret { +div.dt-datetime.inline { position: relative; - top: 2px; + box-shadow: none; +} +div.dt-datetime div.dt-datetime-title { + text-align: center; + padding: 5px 0px 3px; +} +div.dt-datetime div.dt-datetime-buttons { + text-align: center; +} +div.dt-datetime div.dt-datetime-buttons a { display: inline-block; + padding: 0 0.5em 0.5em 0.5em; + margin: 0; + font-size: 0.9em; } -div.dtsp-topRow button.dtsp-collapseButton.dtsp-rotated { - transform: rotate(180deg); +div.dt-datetime div.dt-datetime-buttons a:hover { + text-decoration: underline; } - -div.dtsp-topRow.dtsp-bordered { - border: 2px solid #f0f0f0; - border-radius: 3px; -} - -div.dtsp-topRow.dtsp-bordered:hover { - background-color: #f0f0f0; - opacity: 0.6; - border: 2px solid #cfcfcf; - border-radius: 3px; - cursor: pointer !important; -} - -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane table thead th, -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane table thead td { - width: 100% !important; -} - -div.dt-button-collection { - z-index: 2002; -} - -div.dt-button-collection.dtb-collection-closeable div.dtsp-titleRow { - padding-right: 25px; -} - -div.dtsp-columns-1 { - max-width: 100%; - min-width: 100%; - margin: 0px !important; -} - -div.dtsp-columns-2 { - max-width: 49%; - min-width: 49%; - margin: 0px !important; -} - -div.dtsp-columns-3 { - max-width: 32%; - min-width: 32%; - margin: 0px !important; -} - -div.dtsp-columns-4 { - max-width: 24%; - min-width: 24%; - margin: 0px !important; -} - -div.dtsp-columns-5 { - max-width: 19%; - min-width: 19%; - margin: 0px !important; -} - -div.dtsp-columns-6 { - max-width: 16%; - min-width: 16%; - margin: 0px !important; -} - -div.dtsp-columns-7 { - max-width: 14%; - min-width: 14%; - margin: 0px !important; -} - -div.dtsp-columns-8 { - max-width: 12%; - min-width: 12%; - margin: 0px !important; -} - -div.dtsp-columns-9 { - max-width: 10.5%; - min-width: 10.5%; - margin: 0px !important; -} - -div.dtsp-narrow { - flex-direction: column !important; -} -div.dtsp-narrow div.dtsp-subRow1, -div.dtsp-narrow div.dtsp-subRow2 { +div.dt-datetime table { + border-spacing: 0; + margin: 12px 0; width: 100%; } -div.dtsp-narrow div.dtsp-subRow2 button { - margin: 0 !important; - width: 25% !important; +div.dt-datetime table.dt-datetime-table-nospace { + margin-top: -12px; } - -div.dt-button-collection { - float: none; +div.dt-datetime table th { + font-size: 0.8em; + color: #777; + font-weight: normal; + width: 14.285714286%; + padding: 0 0 4px 0; + text-align: center; } - -div.dtsp-panesContainer { - margin-bottom: 1em; -} -div.dtsp-panesContainer div.dataTables_wrapper { - width: 100%; -} -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_layout_cell { +div.dt-datetime table td { + font-size: 0.9em; + color: #444; padding: 0; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollHead { - display: none !important; +div.dt-datetime table td.selectable { + text-align: center; + background: #f5f5f5; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody { - background: white !important; - border-bottom: none; +div.dt-datetime table td.selectable.disabled { + color: #aaa; + background: white; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody thead { - display: none; +div.dt-datetime table td.selectable.disabled button:hover { + color: #aaa; + background: white; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody table { - table-layout: fixed; +div.dt-datetime table td.selectable.now { + background-color: #ddd; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody table tr > th, -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody table tr > td { - padding: 5px 10px; +div.dt-datetime table td.selectable.now button { + font-weight: bold; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody td.dtsp-nameColumn { - width: 100% !important; +div.dt-datetime table td.selectable.selected button { + background: #4E6CA3; + color: white; + border-radius: 2px; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont { +div.dt-datetime table td.selectable button:hover { + background: #ff8000; + color: white; + border-radius: 2px; +} +div.dt-datetime table td.dt-datetime-week { + font-size: 0.7em; +} +div.dt-datetime table button { width: 100%; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-content: flex-start; - align-items: flex-start; + box-sizing: border-box; + border: none; + background: transparent; + font-size: inherit; + color: inherit; + text-align: center; + padding: 4px 0; + cursor: pointer; + margin: 0; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-name, -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill { - cursor: default; +div.dt-datetime table button span { + display: inline-block; + min-width: 14px; + text-align: right; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-name { - text-overflow: ellipsis; - overflow: hidden; +div.dt-datetime table.weekNumber th { + width: 12.5%; +} +div.dt-datetime div.dt-datetime-calendar table { + margin-top: 0; +} +div.dt-datetime div.dt-datetime-label { + position: relative; + display: inline-block; + height: 30px; + padding: 5px 6px; + border: 1px solid transparent; + box-sizing: border-box; + cursor: pointer; +} +div.dt-datetime div.dt-datetime-label:hover { + border: 1px solid #ddd; + border-radius: 2px; + background-color: #f5f5f5; +} +div.dt-datetime div.dt-datetime-label select { + position: absolute; + top: 6px; + left: 0; + cursor: pointer; + opacity: 0; +} +div.dt-datetime.horizontal { + width: 550px; +} +div.dt-datetime.horizontal div.dt-datetime-date, +div.dt-datetime.horizontal div.dt-datetime-time { + width: 48%; +} +div.dt-datetime.horizontal div.dt-datetime-time { + margin-left: 4%; +} +div.dt-datetime div.dt-datetime-date { + position: relative; + float: left; + width: 100%; +} +div.dt-datetime div.dt-datetime-time { + position: relative; + float: left; + width: 100%; + text-align: center; +} +div.dt-datetime div.dt-datetime-time > span { + vertical-align: middle; +} +div.dt-datetime div.dt-datetime-time th { + text-align: left; +} +div.dt-datetime div.dt-datetime-time div.dt-datetime-timeblock { display: inline-block; vertical-align: middle; - white-space: nowrap; - flex-grow: 1; - text-align: left; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill { - display: inline-block; - background-color: #cfcfcf; - text-align: center; - border: 1px solid #cfcfcf; - border-radius: 10px; - width: auto; - min-width: 30px; - color: black; - font-size: 0.9em; - padding: 0 4px; -} -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill:empty { - display: none; -} - -div.dtsp-panesContainer { - clear: both; - padding-left: 0; - padding-right: 0; - text-align: center; -} -div.dtsp-panesContainer div.dtsp-searchPanes { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: space-between; - align-content: flex-start; - align-items: stretch; - clear: both; - text-align: left; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane { - flex-grow: 1; - flex-shrink: 0; - font-size: 0.9em; - margin-top: 15px !important; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper { - flex: 1; +div.dt-datetime div.dt-datetime-iconLeft, +div.dt-datetime div.dt-datetime-iconRight { + width: 30px; + height: 30px; + background-position: center; + background-repeat: no-repeat; + opacity: 0.3; + overflow: hidden; box-sizing: border-box; + border: 1px solid transparent; } -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper div.dataTables_filter { - display: none; -} -div.dtsp-panesContainer div.dtsp-title { - float: left; - padding: 10px 0; -} -div.dtsp-panesContainer button.dtsp-clearAll, -div.dtsp-panesContainer button.dtsp-collapseAll, -div.dtsp-panesContainer button.dtsp-showAll { - float: right; - padding: 10px; -} - -div.dtsp-hidden { - display: none !important; -} - -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper { - border: 2px solid #f0f0f0; - border-radius: 4px; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper:hover { - border: 2px solid #cfcfcf; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dtsp-selected { - border: 2px solid #3276b1; - border-radius: 4px; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dtsp-selected:hover { - border: 2px solid #286092; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dtsp-topRow div.dtsp-searchCont input.dtsp-search { - border: none; - padding-left: 3px; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane input.dtsp-paneInputButton, -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane button.dtsp-paneButton { - height: 35px; - width: 35px; - min-width: 0; - display: inline-block; - margin: 2px; - border: 0px solid transparent; - background-color: transparent; - margin-bottom: 0px; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane input.dtsp-paneInputButton:hover, -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane button.dtsp-paneButton:hover { - background-color: #f0f0f0; +div.dt-datetime div.dt-datetime-iconLeft:hover, +div.dt-datetime div.dt-datetime-iconRight:hover { + border: 1px solid #ccc; border-radius: 2px; - cursor: pointer; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane button.dtsp-paneButton { + background-color: #f0f0f0; opacity: 0.6; } -div.dtsp-panesContainer button.dtsp-clearAll, -div.dtsp-panesContainer button.dtsp-collapseAll, -div.dtsp-panesContainer button.dtsp-showAll { - border: 1px solid transparent; - background-color: transparent; -} -div.dtsp-panesContainer button.dtsp-clearAll:hover, -div.dtsp-panesContainer button.dtsp-collapseAll:hover, -div.dtsp-panesContainer button.dtsp-showAll:hover { - background-color: #f0f0f0; - border-radius: 2px; +div.dt-datetime div.dt-datetime-iconLeft button, +div.dt-datetime div.dt-datetime-iconRight button { + border: none; + background: transparent; + text-indent: 30px; + height: 100%; + width: 100%; cursor: pointer; } -div.dtsp-panesContainer button.dtsp-disabledButton { - cursor: default !important; - color: #7c7c7c; +div.dt-datetime div.dt-datetime-iconLeft { + position: absolute; + top: 5px; + left: 5px; } -div.dtsp-panesContainer button.dtsp-disabledButton:hover { - background-color: transparent; +div.dt-datetime div.dt-datetime-iconLeft button { + position: relative; + z-index: 1; } -div.dtsp-panesContainer button.dtsp-disabledButton:focus { - outline: none; +div.dt-datetime div.dt-datetime-iconLeft:after { + position: absolute; + top: 7px; + left: 10px; + display: block; + content: ""; + border-top: 7px solid transparent; + border-right: 7px solid black; + border-bottom: 7px solid transparent; +} +div.dt-datetime div.dt-datetime-iconRight { + position: absolute; + top: 5px; + right: 5px; +} +div.dt-datetime div.dt-datetime-iconRight button { + position: relative; + z-index: 1; +} +div.dt-datetime div.dt-datetime-iconRight:after { + position: absolute; + top: 7px; + left: 12px; + display: block; + content: ""; + border-top: 7px solid transparent; + border-left: 7px solid black; + border-bottom: 7px solid transparent; } -div.dtsp-topRow.dtsp-bordered:hover button.dtsp-disabledButton { - cursor: pointer !important; +div.dt-datetime-error { + clear: both; + padding: 0 1em; + max-width: 240px; + font-size: 11px; + line-height: 1.25em; + text-align: center; + color: #b11f1f; +} + +html.dark input.dt-datetime { + color-scheme: dark; +} +html.dark div.dt-datetime { + border: 1px solid #595b5e; + background-color: #212529; + box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8); +} +html.dark div.dt-datetime table th { + color: #ccc; +} +html.dark div.dt-datetime table td { + color: #eee; +} +html.dark div.dt-datetime table td.selectable { + background: #373c41; +} +html.dark div.dt-datetime table td.selectable.disabled { + color: #aaa; + background: #171b1f; +} +html.dark div.dt-datetime table td.selectable.disabled button:hover { + color: #aaa; + background: #171b1f; +} +html.dark div.dt-datetime table td.selectable.now { + background: #4b5055; +} +html.dark div.dt-datetime table td.selectable.selected button { + background: #6ea8fe; + color: black; +} +html.dark div.dt-datetime table td.selectable button:hover { + background: #ff8000; + color: black; +} +html.dark div.dt-datetime div.dt-datetime-label:hover { + border: 1px solid transparent; + background-color: rgba(255, 255, 255, 0.1); +} +html.dark div.dt-datetime div.dt-datetime-iconLeft:hover, +html.dark div.dt-datetime div.dt-datetime-iconRight:hover, +html.dark div.dt-datetime div.dt-datetime-iconUp:hover, +html.dark div.dt-datetime div.dt-datetime-iconDown:hover { + border: 1px solid transparent; + background-color: rgba(255, 255, 255, 0.1); +} +html.dark div.dt-datetime div.dt-datetime-iconLeft:after { + border-right-color: white; +} +html.dark div.dt-datetime div.dt-datetime-iconRight:after { + border-left-color: white; +} +html.dark div.dt-datetime select { + color-scheme: dark; +} +html.dark div.dt-datetime-error { + color: #b11f1f; +} + +table.dataTable thead tr > .dtfc-fixed-start, +table.dataTable thead tr > .dtfc-fixed-end, +table.dataTable tfoot tr > .dtfc-fixed-start, +table.dataTable tfoot tr > .dtfc-fixed-end { + top: 0; + bottom: 0; + z-index: 3; + background-color: white; +} +table.dataTable tbody tr > .dtfc-fixed-start, +table.dataTable tbody tr > .dtfc-fixed-end { + z-index: 1; + background-color: white; +} +table.dataTable tr > .dtfc-fixed-left::after, +table.dataTable tr > .dtfc-fixed-right::after { + position: absolute; + top: 0; + bottom: 0; + width: 10px; + transition: box-shadow 0.3s; + content: ""; pointer-events: none; } -div.dtsp-topRow.dtsp-bordered:hover input.dtsp-paneInputButton { - pointer-events: none; +table.dataTable tr > .dtfc-fixed-left::after { + right: 0; + transform: translateX(100%); +} +table.dataTable tr > .dtfc-fixed-right::after { + left: 0; + transform: translateX(-80%); +} +table.dataTable.dtfc-scrolling-left tr > .dtfc-fixed-left::after { + box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.2); +} +table.dataTable.dtfc-scrolling-right tr > .dtfc-fixed-right::after { + box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.2); +} +table.dataTable.dtfc-scrolling-right tr > .dtfc-fixed-right + .dtfc-fixed-right::after { + box-shadow: none; +} + +div.dt-scroll, +div.dtfh-floatingparent { + position: relative; +} +div.dt-scroll div.dtfc-top-blocker, +div.dt-scroll div.dtfc-bottom-blocker, +div.dtfh-floatingparent div.dtfc-top-blocker, +div.dtfh-floatingparent div.dtfc-bottom-blocker { + position: absolute; + background-color: white; +} + +html.dark table.dataTable thead tr > .dtfc-fixed-start, +html.dark table.dataTable thead tr > .dtfc-fixed-end, +html.dark table.dataTable tfoot tr > .dtfc-fixed-start, +html.dark table.dataTable tfoot tr > .dtfc-fixed-end { + background-color: var(--dt-html-background); +} +html.dark table.dataTable tbody tr > .dtfc-fixed-start, +html.dark table.dataTable tbody tr > .dtfc-fixed-end { + background-color: var(--dt-html-background); +} +html.dark table.dataTable.dtfc-scrolling-left tbody > tr > .dtfc-fixed-left::after { + box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.3); +} +html.dark table.dataTable.dtfc-scrolling-right tbody > tr > .dtfc-fixed-right::after { + box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.3); +} +html.dark table.dataTable.dtfc-scrolling-right tbody > tr > .dtfc-fixed-right + .dtfc-fixed-right::after { + box-shadow: none; +} +html.dark div.dtfc-top-blocker, +html.dark div.dtfc-bottom-blocker { + background-color: var(--dt-html-background); +} + + +table.dataTable.dtr-inline.collapsed > tbody > tr > td.child, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.child, +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty { + cursor: default !important; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before { + display: none !important; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control { + cursor: pointer; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before { + margin-right: 0.5em; + display: inline-block; + box-sizing: border-box; + content: ""; + border-top: 5px solid transparent; + border-left: 10px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid transparent; + border-right: 0px solid transparent; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control.arrow-right::before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control.arrow-right::before { + border-top: 5px solid transparent; + border-left: 0px solid transparent; + border-bottom: 5px solid transparent; + border-right: 10px solid rgba(0, 0, 0, 0.5); +} +table.dataTable.dtr-inline.collapsed > tbody > tr.dtr-expanded > td.dtr-control:before, +table.dataTable.dtr-inline.collapsed > tbody > tr.dtr-expanded > th.dtr-control:before { + border-top: 10px solid rgba(0, 0, 0, 0.5); + border-left: 5px solid transparent; + border-bottom: 0px solid transparent; + border-right: 5px solid transparent; +} +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control, +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control { + padding-left: 0.333em; +} +table.dataTable.dtr-column > tbody > tr > td.dtr-control, +table.dataTable.dtr-column > tbody > tr > th.dtr-control, +table.dataTable.dtr-column > tbody > tr > td.control, +table.dataTable.dtr-column > tbody > tr > th.control { + cursor: pointer; +} +table.dataTable.dtr-column > tbody > tr > td.dtr-control:before, +table.dataTable.dtr-column > tbody > tr > th.dtr-control:before, +table.dataTable.dtr-column > tbody > tr > td.control:before, +table.dataTable.dtr-column > tbody > tr > th.control:before { + display: inline-block; + box-sizing: border-box; + content: ""; + border-top: 5px solid transparent; + border-left: 10px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid transparent; + border-right: 0px solid transparent; +} +table.dataTable.dtr-column > tbody > tr > td.dtr-control.arrow-right::before, +table.dataTable.dtr-column > tbody > tr > th.dtr-control.arrow-right::before, +table.dataTable.dtr-column > tbody > tr > td.control.arrow-right::before, +table.dataTable.dtr-column > tbody > tr > th.control.arrow-right::before { + border-top: 5px solid transparent; + border-left: 0px solid transparent; + border-bottom: 5px solid transparent; + border-right: 10px solid rgba(0, 0, 0, 0.5); +} +table.dataTable.dtr-column > tbody > tr.dtr-expanded td.dtr-control:before, +table.dataTable.dtr-column > tbody > tr.dtr-expanded th.dtr-control:before, +table.dataTable.dtr-column > tbody > tr.dtr-expanded td.control:before, +table.dataTable.dtr-column > tbody > tr.dtr-expanded th.control:before { + border-top: 10px solid rgba(0, 0, 0, 0.5); + border-left: 5px solid transparent; + border-bottom: 0px solid transparent; + border-right: 5px solid transparent; +} +table.dataTable > tbody > tr.child { + padding: 0.5em 1em; +} +table.dataTable > tbody > tr.child:hover { + background: transparent !important; +} +table.dataTable > tbody > tr.child ul.dtr-details { + display: inline-block; + list-style-type: none; + margin: 0; + padding: 0; +} +table.dataTable > tbody > tr.child ul.dtr-details > li { + border-bottom: 1px solid #efefef; + padding: 0.5em 0; +} +table.dataTable > tbody > tr.child ul.dtr-details > li:first-child { + padding-top: 0; +} +table.dataTable > tbody > tr.child ul.dtr-details > li:last-child { + padding-bottom: 0; + border-bottom: none; +} +table.dataTable > tbody > tr.child span.dtr-title { + display: inline-block; + min-width: 75px; + font-weight: bold; +} + +div.dtr-modal { + position: fixed; + box-sizing: border-box; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: 100; + padding: 10em 1em; +} +div.dtr-modal div.dtr-modal-display { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 50%; + height: fit-content; + max-height: 75%; + overflow: auto; + margin: auto; + z-index: 102; + overflow: auto; + background-color: #f5f5f7; + border: 1px solid black; + border-radius: 0.5em; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6); +} +div.dtr-modal div.dtr-modal-content { + position: relative; + padding: 2.5em; +} +div.dtr-modal div.dtr-modal-content h2 { + margin-top: 0; +} +div.dtr-modal div.dtr-modal-close { + position: absolute; + top: 6px; + right: 6px; + width: 22px; + height: 22px; + text-align: center; + border-radius: 3px; + cursor: pointer; + z-index: 12; +} +div.dtr-modal div.dtr-modal-background { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 101; + background: rgba(0, 0, 0, 0.6); } @media screen and (max-width: 767px) { - div.dtsp-columns-4, -div.dtsp-columns-5, -div.dtsp-columns-6 { - max-width: 31% !important; - min-width: 31% !important; + div.dtr-modal div.dtr-modal-display { + width: 95%; } } -@media screen and (max-width: 640px) { - div.dtsp-searchPanes { - flex-direction: column !important; - } - - div.dtsp-searchPane { - max-width: 98% !important; - min-width: 98% !important; - } +html.dark table.dataTable > tbody > tr > td.dtr-control:before, +html[data-bs-theme=dark] table.dataTable > tbody > tr > td.dtr-control:before { + border-left-color: rgba(255, 255, 255, 0.5) !important; +} +html.dark table.dataTable > tbody > tr > td.dtr-control.arrow-right::before, +html[data-bs-theme=dark] table.dataTable > tbody > tr > td.dtr-control.arrow-right::before { + border-right-color: rgba(255, 255, 255, 0.5) !important; +} +html.dark table.dataTable > tbody > tr.dtr-expanded > td.dtr-control:before, +html.dark table.dataTable > tbody > tr.dtr-expanded > th.dtr-control:before, +html[data-bs-theme=dark] table.dataTable > tbody > tr.dtr-expanded > td.dtr-control:before, +html[data-bs-theme=dark] table.dataTable > tbody > tr.dtr-expanded > th.dtr-control:before { + border-top-color: rgba(255, 255, 255, 0.5) !important; + border-left-color: transparent !important; + border-right-color: transparent !important; +} +html.dark table.dataTable > tbody > tr.child ul.dtr-details > li, +html[data-bs-theme=dark] table.dataTable > tbody > tr.child ul.dtr-details > li { + border-bottom-color: rgb(64, 67, 70); +} +html.dark div.dtr-modal div.dtr-modal-display, +html[data-bs-theme=dark] div.dtr-modal div.dtr-modal-display { + background-color: rgb(33, 37, 41); + border: 1px solid rgba(255, 255, 255, 0.15); } -tfoot input { - width: 100%; - padding: 3px; - box-sizing: border-box; - } -tfoot { - display: table-header-group; + +table.dataTable tr.dtrg-group th { + background-color: rgba(0, 0, 0, 0.1); + text-align: left; } -.dataTables_length { -float: right; -text-align: right; +table.dataTable tr.dtrg-group.dtrg-level-0 th { + font-weight: bold; } -.dataTables_filter, .dataTables_info { display: none; } + +table.dataTable tr.dtrg-group.dtrg-level-1 th, +table.dataTable tr.dtrg-group.dtrg-level-2 th, +table.dataTable tr.dtrg-group.dtrg-level-3 th, +table.dataTable tr.dtrg-group.dtrg-level-4 th, +table.dataTable tr.dtrg-group.dtrg-level-5 th { + background-color: rgba(0, 0, 0, 0.05); + padding-top: 0.25em; + padding-bottom: 0.25em; + padding-left: 2em; + font-size: 0.9em; +} + +table.dataTable tr.dtrg-group.dtrg-level-2 th { + background-color: rgba(0, 0, 0, 0.01); + padding-left: 2.5em; +} + +table.dataTable tr.dtrg-group.dtrg-level-3 th { + background-color: rgba(0, 0, 0, 0.01); + padding-left: 3em; +} + +table.dataTable tr.dtrg-group.dtrg-level-4 th { + background-color: rgba(0, 0, 0, 0.01); + padding-left: 3.5em; +} + +table.dataTable tr.dtrg-group.dtrg-level-5 th { + background-color: rgba(0, 0, 0, 0.01); + padding-left: 4em; +} + +html.dark table.dataTable tr.dtrg-group th { + background-color: rgba(255, 255, 255, 0.1); +} +html.dark table.dataTable tr.dtrg-group.dtrg-level-1 th { + background-color: rgba(255, 255, 255, 0.05); +} +html.dark table.dataTable tr.dtrg-group.dtrg-level-2 th, +html.dark table.dataTable tr.dtrg-group.dtrg-level-3 th, +html.dark table.dataTable tr.dtrg-group.dtrg-level-4 th, +html.dark table.dataTable tr.dtrg-group.dtrg-level-5 th { + background-color: rgba(255, 255, 255, 0.01); +} + + diff --git a/org.idempiere.zk.datatable/src/web/js/datatables/datatables.js b/org.idempiere.zk.datatable/src/web/js/datatables/datatables.js index 73aa34b030..cdde37b5f3 100644 --- a/org.idempiere.zk.datatable/src/web/js/datatables/datatables.js +++ b/org.idempiere.zk.datatable/src/web/js/datatables/datatables.js @@ -4,37 +4,10740 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.12.1/b-2.2.3/b-colvis-2.2.3/cr-1.5.6/fh-3.2.4/sp-2.0.2 + * https://datatables.net/download/#dt/jq-3.7.0/dt-2.0.8/b-3.0.2/b-colvis-3.0.2/cr-2.0.3/date-1.5.2/fc-5.0.1/r-3.0.2/rg-1.5.0 * * Included libraries: - * DataTables 1.12.1, Buttons 2.2.3, Column visibility 2.2.3, ColReorder 1.5.6, FixedHeader 3.2.4, SearchPanes 2.0.2 + * jQuery 3 3.7.0, DataTables 2.0.8, Buttons 3.0.2, Column visibility 3.0.2, ColReorder 2.0.3, DateTime 1.5.2, FixedColumns 5.0.1, Responsive 3.0.2, RowGroup 1.5.0 */ -/*! DataTables 1.12.1 - * ©2008-2022 SpryMedia Ltd - datatables.net/license +/*! + * jQuery JavaScript Library v3.7.0 + * https://jquery.com/ + * + * Copyright OpenJS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2023-05-11T18:29Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket trac-14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 + // Plus for old WebKit, typeof returns "function" for HTML collections + // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) + return typeof obj === "function" && typeof obj.nodeType !== "number" && + typeof obj.item !== "function"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var version = "3.7.0", + + rhtmlSuffix = /HTML$/i, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + + // Retrieve the text value of an array of DOM nodes + text: function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += jQuery.text( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + return elem.textContent; + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + isXMLDoc: function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + // Assume HTML when documentElement doesn't yet exist, such as inside + // document fragments. + return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var pop = arr.pop; + + +var sort = arr.sort; + + +var splice = arr.splice; + + +var whitespace = "[\\x20\\t\\r\\n\\f]"; + + +var rtrimCSS = new RegExp( + "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", + "g" +); + + + + +// Note: an element does not contain itself +jQuery.contains = function( a, b ) { + var bup = b && b.parentNode; + + return a === bup || !!( bup && bup.nodeType === 1 && ( + + // Support: IE 9 - 11+ + // IE doesn't have `contains` on SVG. + a.contains ? + a.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); +}; + + + + +// CSS string/identifier serialization +// https://drafts.csswg.org/cssom/#common-serializing-idioms +var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + +function fcssescape( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; +} + +jQuery.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + + + + +var preferredDoc = document, + pushNative = push; + +( function() { + +var i, + Expr, + outermostContext, + sortInput, + hasDuplicate, + push = pushNative, + + // Local document vars + document, + documentElement, + documentIsHTML, + rbuggyQSA, + matches, + + // Instance-specific data + expando = jQuery.expando, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + + "loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( + "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + bool: new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + needsContext: new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + if ( nonHex ) { + + // Strip the backslash prefix from a non-hex escape sequence + return nonHex; + } + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + return high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // Used for iframes; see `setDocument`. + // Support: IE 9 - 11+, Edge 12 - 18+ + // Removing the function wrapper causes a "Permission Denied" + // error in IE/Edge. + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && nodeName( elem, "fieldset" ); + }, + { dir: "parentNode", next: "legend" } + ); + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android <=4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { + apply: function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + }, + call: function( target ) { + pushNative.apply( target, slice.call( arguments, 1 ) ); + } + }; +} + +function find( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE 9 only + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + push.call( results, elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE 9 only + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + find.contains( context, elem ) && + elem.id === m ) { + + push.call( results, elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when + // strict-comparing two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( newContext != context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = jQuery.escapeSelector( nid ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties + // (see https://github.com/jquery/sizzle/issues/157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by jQuery selector module + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + return nodeName( elem, "input" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && + elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11+ + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a jQuery selector context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [node] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +function setDocument( node ) { + var subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + documentElement = document.documentElement; + documentIsHTML = !jQuery.isXMLDoc( document ); + + // Support: iOS 7 only, IE 9 - 11+ + // Older browsers didn't support unprefixed `matches`. + matches = documentElement.matches || + documentElement.webkitMatchesSelector || + documentElement.msMatchesSelector; + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (see trac-13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 9 - 11+, Edge 12 - 18+ + subWindow.addEventListener( "unload", unloadHandler ); + } + + // Support: IE <10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + documentElement.appendChild( el ).id = jQuery.expando; + return !document.getElementsByName || + !document.getElementsByName( jQuery.expando ).length; + } ); + + // Support: IE 9 only + // Check to see if it's possible to do matchesSelector + // on a disconnected node. + support.disconnectedMatch = assert( function( el ) { + return matches.call( el, "*" ); + } ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // IE/Edge don't support the :scope pseudo-class. + support.scope = assert( function() { + return document.querySelectorAll( ":scope" ); + } ); + + // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only + // Make sure the `:has()` argument is parsed unforgivingly. + // We include `*` in the test to detect buggy implementations that are + // _selectively_ forgiving (specifically when the list includes at least + // one valid selector). + // Note that we treat complete lack of support for `:has()` as if it were + // spec-compliant support, which is fine because use of `:has()` in such + // environments will fail in the qSA path and fall back to jQuery traversal + // anyway. + support.cssHas = assert( function() { + try { + document.querySelector( ":has(*,:jqfake)" ); + return false; + } catch ( e ) { + return true; + } + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter.ID = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find.ID = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter.ID = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find.ID = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find.TAG = function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else { + return context.querySelectorAll( tag ); + } + }; + + // Class + Expr.find.CLASS = function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + rbuggyQSA = []; + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + documentElement.appendChild( el ).innerHTML = + "" + + ""; + + // Support: iOS <=7 - 8 only + // Boolean attributes and "value" are not treated correctly in some XML documents + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: iOS <=7 - 8 only + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: iOS 8 only + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE 9 - 11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + documentElement.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + } ); + + if ( !support.cssHas ) { + + // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+ + // Our regular `try-catch` mechanism fails to detect natively-unsupported + // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`) + // in browsers that parse the `:has()` argument as a forgiving selector list. + // https://drafts.csswg.org/selectors/#relational now requires the argument + // to be parsed unforgivingly, but browsers have not yet fully adjusted. + rbuggyQSA.push( ":has" ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a === document || a.ownerDocument == preferredDoc && + find.contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b === document || b.ownerDocument == preferredDoc && + find.contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + }; + + return document; +} + +find.matches = function( expr, elements ) { + return find( expr, null, null, elements ); +}; + +find.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return find( expr, document, null, [ elem ] ).length > 0; +}; + +find.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return jQuery.contains( context, elem ); +}; + + +find.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (see trac-13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + if ( val !== undefined ) { + return val; + } + + return elem.getAttribute( name ); +}; + +find.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +jQuery.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + // + // Support: Android <=4.0+ + // Testing for detecting duplicates is unpredictable so instead assume we can't + // depend on duplicate detection in all browsers without a stable sort. + hasDuplicate = !support.sortStable; + sortInput = !support.sortStable && slice.call( results, 0 ); + sort.call( results, sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + splice.call( results, duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +jQuery.fn.uniqueSort = function() { + return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) ); +}; + +Expr = jQuery.expr = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + ATTR: function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ) + .replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + CHILD: function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + find.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) + ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + find.error( match[ 0 ] ); + } + + return match; + }, + + PSEUDO: function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr.CHILD.test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + TAG: function( nodeNameSelector ) { + var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return nodeName( elem, expectedNodeName ); + }; + }, + + CLASS: function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + ")" + className + + "(" + whitespace + "|$)" ) ) && + classCache( className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + ATTR: function( name, operator, check ) { + return function( elem ) { + var result = find.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + if ( operator === "=" ) { + return result === check; + } + if ( operator === "!=" ) { + return result !== check; + } + if ( operator === "^=" ) { + return check && result.indexOf( check ) === 0; + } + if ( operator === "*=" ) { + return check && result.indexOf( check ) > -1; + } + if ( operator === "$=" ) { + return check && result.slice( -check.length ) === check; + } + if ( operator === "~=" ) { + return ( " " + result.replace( rwhitespace, " " ) + " " ) + .indexOf( check ) > -1; + } + if ( operator === "|=" ) { + return result === check || result.slice( 0, check.length + 1 ) === check + "-"; + } + + return false; + }; + }, + + CHILD: function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || ( parent[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + outerCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + PSEUDO: function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // https://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + find.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as jQuery does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + not: markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrimCSS, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element + // (see https://github.com/jquery/sizzle/issues/299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + has: markFunction( function( selector ) { + return function( elem ) { + return find( selector, elem ).length > 0; + }; + } ), + + contains: markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // https://www.w3.org/TR/selectors/#lang-pseudo + lang: markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + find.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + target: function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + root: function( elem ) { + return elem === documentElement; + }, + + focus: function( elem ) { + return elem === safeActiveElement() && + document.hasFocus() && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + enabled: createDisabledPseudo( false ), + disabled: createDisabledPseudo( true ), + + checked: function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + return ( nodeName( elem, "input" ) && !!elem.checked ) || + ( nodeName( elem, "option" ) && !!elem.selected ); + }, + + selected: function( elem ) { + + // Support: IE <=11+ + // Accessing the selectedIndex property + // forces the browser to treat the default option as + // selected when in an optgroup. + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + empty: function( elem ) { + + // https://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + parent: function( elem ) { + return !Expr.pseudos.empty( elem ); + }, + + // Element/input types + header: function( elem ) { + return rheader.test( elem.nodeName ); + }, + + input: function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + button: function( elem ) { + return nodeName( elem, "input" ) && elem.type === "button" || + nodeName( elem, "button" ); + }, + + text: function( elem ) { + var attr; + return nodeName( elem, "input" ) && elem.type === "text" && + + // Support: IE <10 only + // New HTML5 attribute values (e.g., "search") appear + // with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + first: createPositionalPseudo( function() { + return [ 0 ]; + } ), + + last: createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + even: createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + odd: createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + lt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i; + + if ( argument < 0 ) { + i = argument + length; + } else if ( argument > length ) { + i = length; + } else { + i = argument; + } + + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + gt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos.nth = Expr.pseudos.eq; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rleadingCombinator.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrimCSS, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + if ( parseOnly ) { + return soFar.length; + } + + return soFar ? + find.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + if ( skip && nodeName( elem, skip ) ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = outerCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + outerCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + find( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, matcherOut, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || + multipleContexts( selector || "*", + context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems; + + if ( matcher ) { + + // If we have a postFinder, or filtered seed, or non-seed postFilter + // or preexisting results, + matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results; + + // Find primary matches + matcher( matcherIn, matcherOut, context, xml ); + } else { + matcherOut = matcherIn; + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element + // (see https://github.com/jquery/sizzle/issues/299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrimCSS, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find.TAG( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: iOS <=7 - 9 only + // Tolerate NodeList properties (IE: "length"; Safari: ) matching + // elements by id. (see trac-14142) + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + push.call( results, elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + jQuery.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +function compile( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +} + +/** + * A low-level selection function that works with jQuery's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with jQuery selector compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find.ID( + token.matches[ 0 ].replace( runescape, funescape ), + context + ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && + testContext( context.parentNode ) || context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +} + +// One-time assignments + +// Support: Android <=4.0 - 4.1+ +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Initialize against the default document +setDocument(); + +// Support: Android <=4.0 - 4.1+ +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +jQuery.find = find; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.unique = jQuery.uniqueSort; + +// These have always been private, but they used to be documented +// as part of Sizzle so let's maintain them in the 3.x line +// for backwards compatibility purposes. +find.compile = compile; +find.select = select; +find.setDocument = setDocument; + +find.escape = jQuery.escapeSelector; +find.getText = jQuery.text; +find.isXML = jQuery.isXMLDoc; +find.selectors = jQuery.expr; +find.support = jQuery.support; +find.uniqueSort = jQuery.uniqueSort; + + /* eslint-enable */ + +} )(); + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (trac-9521) + // Strict HTML recognition (trac-11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to jQuery#find + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.error ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the error, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getErrorHook ) { + process.error = jQuery.Deferred.getErrorHook(); + + // The deprecated alias of the above. While the name suggests + // returning the stack, not an error instance, jQuery just passes + // it directly to `console.warn` so both will work; an instance + // just better cooperates with source maps. + } else if ( jQuery.Deferred.getStackHook ) { + process.error = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the primary Deferred + primary = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + primary.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( primary.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return primary.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); + } + + return primary.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error +// captured before the async barrier to get the original error cause +// which may otherwise be hidden. +jQuery.Deferred.exceptionHook = function( error, asyncError ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, + error.stack, asyncError ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See trac-6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (trac-9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see trac-8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (trac-14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (trac-11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (trac-14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (trac-13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (trac-15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (trac-12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (trac-13208) + // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (trac-13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", true ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, isSetup ) { + + // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add + if ( !isSetup ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + if ( !saved ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + this[ type ](); + result = dataPriv.get( this, type ); + dataPriv.set( this, type, false ); + + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + + return result; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering + // the native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved ) { + + // ...and capture the result + dataPriv.set( this, type, jQuery.event.trigger( + saved[ 0 ], + saved.slice( 1 ), + this + ) ); + + // Abort handling of the native event by all jQuery handlers while allowing + // native handlers on the same element to run. On target, this is achieved + // by stopping immediate propagation just on the jQuery event. However, + // the native event is re-wrapped by a jQuery one on each level of the + // propagation so the only way to stop it for jQuery is to stop it for + // everyone via native `stopPropagation()`. This is not a problem for + // focus/blur which don't bubble, but it does also stop click on checkboxes + // and radios. We accept this limitation. + event.stopPropagation(); + event.isImmediatePropagationStopped = returnTrue; + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (trac-504, trac-13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + which: true +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + + function focusMappedHandler( nativeEvent ) { + if ( document.documentMode ) { + + // Support: IE 11+ + // Attach a single focusin/focusout handler on the document while someone wants + // focus/blur. This is because the former are synchronous in IE while the latter + // are async. In other browsers, all those handlers are invoked synchronously. + + // `handle` from private data would already wrap the event, but we need + // to change the `type` here. + var handle = dataPriv.get( this, "handle" ), + event = jQuery.event.fix( nativeEvent ); + event.type = nativeEvent.type === "focusin" ? "focus" : "blur"; + event.isSimulated = true; + + // First, handle focusin/focusout + handle( nativeEvent ); + + // ...then, handle focus/blur + // + // focus/blur don't bubble while focusin/focusout do; simulate the former by only + // invoking the handler at the lower level. + if ( event.target === event.currentTarget ) { + + // The setup part calls `leverageNative`, which, in turn, calls + // `jQuery.event.add`, so event handle will already have been set + // by this point. + handle( event ); + } + } else { + + // For non-IE browsers, attach a single capturing handler on the document + // while someone wants focusin/focusout. + jQuery.event.simulate( delegateType, nativeEvent.target, + jQuery.event.fix( nativeEvent ) ); + } + } + + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + var attaches; + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, true ); + + if ( document.documentMode ) { + + // Support: IE 9 - 11+ + // We use the same native handler for focusin & focus (and focusout & blur) + // so we need to coordinate setup & teardown parts between those events. + // Use `delegateType` as the key as `type` is already used by `leverageNative`. + attaches = dataPriv.get( this, delegateType ); + if ( !attaches ) { + this.addEventListener( delegateType, focusMappedHandler ); + } + dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 ); + } else { + + // Return false to allow normal processing in the caller + return false; + } + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + teardown: function() { + var attaches; + + if ( document.documentMode ) { + attaches = dataPriv.get( this, delegateType ) - 1; + if ( !attaches ) { + this.removeEventListener( delegateType, focusMappedHandler ); + dataPriv.remove( this, delegateType ); + } else { + dataPriv.set( this, delegateType, attaches ); + } + } else { + + // Return false to indicate standard teardown should be applied + return false; + } + }, + + // Suppress native focus or blur if we're currently inside + // a leveraged native-event stack + _default: function( event ) { + return dataPriv.get( event.target, type ); + }, + + delegateType: delegateType + }; + + // Support: Firefox <=44 + // Firefox doesn't have focus(in | out) events + // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 + // + // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 + // focus(in | out) events fire after focus & blur events, + // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order + // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 + // + // Support: IE 9 - 11+ + // To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch, + // attach a single handler for both events in IE. + jQuery.event.special[ delegateType ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ); + + // Support: IE 9 - 11+ + // We use the same native handler for focusin & focus (and focusout & blur) + // so we need to coordinate setup & teardown parts between those events. + // Use `delegateType` as the key as `type` is already used by `leverageNative`. + if ( !attaches ) { + if ( document.documentMode ) { + this.addEventListener( delegateType, focusMappedHandler ); + } else { + doc.addEventListener( type, focusMappedHandler, true ); + } + } + dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ) - 1; + + if ( !attaches ) { + if ( document.documentMode ) { + this.removeEventListener( delegateType, focusMappedHandler ); + } else { + doc.removeEventListener( type, focusMappedHandler, true ); + } + dataPriv.remove( dataHolder, delegateType ); + } else { + dataPriv.set( dataHolder, delegateType, attaches ); + } + } + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (trac-8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + + // Unwrap a CDATA section containing script contents. This shouldn't be + // needed as in XML documents they're already not visible when + // inspecting element contents and in HTML documents they have no + // meaning but we're preserving that logic for backwards compatibility. + // This will be removed completely in 4.0. See gh-4904. + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew jQuery#find here for performance reasons: + // https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var rcustomProp = /^--/; + + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (trac-8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + // + // Support: Firefox 70+ + // Only Firefox includes border widths + // in computed dimensions. (gh-4529) + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; + tr.style.cssText = "border:1px solid"; + + // Support: Chrome 86+ + // Height set through cssText does not get applied. + // Computed height then comes back as 0. + tr.style.height = "1px"; + trChild.style.height = "9px"; + + // Support: Android 8 Chrome 86+ + // In our bodyBackground.html iframe, + // display for all div elements is set to "inline", + // which causes a problem only in Android 8 Chrome 86. + // Ensuring the div is display: block + // gets around this issue. + trChild.style.display = "block"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + + parseInt( trStyle.borderTopWidth, 10 ) + + parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + isCustomProp = rcustomProp.test( name ), + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, trac-12537) + // .css('--customProperty) (gh-3144) + if ( computed ) { + + // Support: IE <=9 - 11+ + // IE only supports `"float"` in `getPropertyValue`; in computed styles + // it's only available as `"cssFloat"`. We no longer modify properties + // sent to `.css()` apart from camelCasing, so we need to check both. + // Normally, this would create difference in behavior: if + // `getPropertyValue` returns an empty string, the value returned + // by `.css()` would be `undefined`. This is usually the case for + // disconnected elements. However, in IE even disconnected elements + // with no styles return `"none"` for `getPropertyValue( "float" )` + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( isCustomProp && ret ) { + + // Support: Firefox 105+, Chrome <=105+ + // Spec requires trimming whitespace for custom properties (gh-4926). + // Firefox only trims leading whitespace. Chrome just collapses + // both leading & trailing whitespace to a single space. + // + // Fall back to `undefined` if empty string returned. + // This collapses a missing definition with property defined + // and set to an empty string but there's no standard API + // allowing us to differentiate them without a performance penalty + // and returning `undefined` aligns with older jQuery. + // + // rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED + // as whitespace while CSS does not, but this is not a problem + // because CSS preprocessing replaces them with U+000A LINE FEED + // (which *is* CSS whitespace) + // https://www.w3.org/TR/css-syntax-3/#input-preprocessing + ret = ret.replace( rtrimCSS, "$1" ) || undefined; + } + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0, + marginDelta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + // Count margin delta separately to only add it after scroll gutter adjustment. + // This is needed to make negative margins work with `outerHeight( true )` (gh-3982). + if ( box === "margin" ) { + marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta + marginDelta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + animationIterationCount: true, + aspectRatio: true, + borderImageSlice: true, + columnCount: true, + flexGrow: true, + flexShrink: true, + fontWeight: true, + gridArea: true, + gridColumn: true, + gridColumnEnd: true, + gridColumnStart: true, + gridRow: true, + gridRowEnd: true, + gridRowStart: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + scale: true, + widows: true, + zIndex: true, + zoom: true, + + // SVG-related + fillOpacity: true, + floodOpacity: true, + stopOpacity: true, + strokeMiterlimit: true, + strokeOpacity: true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (trac-7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug trac-9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (trac-7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (trac-12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // Use proper attribute retrieval (trac-12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classNames = classesToArray( value ); + + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + if ( cur.indexOf( " " + className + " " ) < 0 ) { + cur += className + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); + } + + return this; + }, + + removeClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classNames = classesToArray( value ); + + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + + // This expression is here for better compressibility (see addClass) + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + // Remove *all* instances + while ( cur.indexOf( " " + className + " " ) > -1 ) { + cur = cur.replace( " " + className + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var classNames, className, i, self, + type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + classNames = classesToArray( value ); + + return this.each( function() { + if ( isValidValue ) { + + // Toggle individual class names + self = jQuery( this ); + + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (trac-14686, trac-14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (trac-2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} + + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; +}; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (trac-9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (trac-6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ).filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ).map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // trac-7653, trac-8125, trac-8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (trac-10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + +originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes trac-9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (trac-10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket trac-12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (trac-15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // trac-9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script but not if jsonp + if ( !isSuccess && + jQuery.inArray( "script", s.dataTypes ) > -1 && + jQuery.inArray( "json", s.dataTypes ) < 0 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (trac-11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // trac-1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see trac-8605, trac-14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // trac-14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( "