diff --git a/migration/i7.1/oracle/201912042002_IDEMPIERE-4087.sql b/migration/i7.1/oracle/201912042002_IDEMPIERE-4087.sql new file mode 100644 index 0000000000..4109de3f12 --- /dev/null +++ b/migration/i7.1/oracle/201912042002_IDEMPIERE-4087.sql @@ -0,0 +1,15 @@ +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- IDEMPIERE-4087 Copy the link to this issue virtual search columns +-- Dec 4, 2019, 8:01:25 PM CET +UPDATE AD_Field SET Help='You can define virtual columns (not stored in the database). If defined, the Column name is the synonym of the SQL expression defined here. The SQL expression must be valid.
+Example: "Updated-Created" would list the age of the entry in days.
+It is not recommended to add complex queries in virtual columns as the impact on the database performance can be too expensive.
+However, you can use the prefix @SQLFIND= for virtual columns that can be used for queries and reports, they have less impact on the database, but as a field they don''t show values.
+Additionally, the prefix @SQL= allows to define a virtual UI column, this is calculated on the fly and can use context variables in the query, virtual UI columns are shown in grid just on the current row, they are not searchable, and not shown in reports.', AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2019-12-04 20:46:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=11264 +; + +SELECT register_migration_script('201912042002_IDEMPIERE-4087.sql') FROM dual +; + diff --git a/migration/i7.1/postgresql/201912042002_IDEMPIERE-4087.sql b/migration/i7.1/postgresql/201912042002_IDEMPIERE-4087.sql new file mode 100644 index 0000000000..ebd6aeeb10 --- /dev/null +++ b/migration/i7.1/postgresql/201912042002_IDEMPIERE-4087.sql @@ -0,0 +1,12 @@ +-- IDEMPIERE-4087 Copy the link to this issue virtual search columns +-- Dec 4, 2019, 8:01:25 PM CET +UPDATE AD_Field SET Help='You can define virtual columns (not stored in the database). If defined, the Column name is the synonym of the SQL expression defined here. The SQL expression must be valid.
+Example: "Updated-Created" would list the age of the entry in days.
+It is not recommended to add complex queries in virtual columns as the impact on the database performance can be too expensive.
+However, you can use the prefix @SQLFIND= for virtual columns that can be used for queries and reports, they have less impact on the database, but as a field they don''t show values.
+Additionally, the prefix @SQL= allows to define a virtual UI column, this is calculated on the fly and can use context variables in the query, virtual UI columns are shown in grid just on the current row, they are not searchable, and not shown in reports.', AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2019-12-04 20:46:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=11264 +; + +SELECT register_migration_script('201912042002_IDEMPIERE-4087.sql') FROM dual +; + diff --git a/org.adempiere.base/src/org/compiere/model/GridField.java b/org.adempiere.base/src/org/compiere/model/GridField.java index 16feabb144..20c50863c0 100644 --- a/org.adempiere.base/src/org/compiere/model/GridField.java +++ b/org.adempiere.base/src/org/compiere/model/GridField.java @@ -83,7 +83,7 @@ public class GridField /** * */ - private static final long serialVersionUID = -5923967271000455417L; + private static final long serialVersionUID = 496387784464611123L; /** * Field Constructor. @@ -1323,7 +1323,7 @@ public class GridField if (m_vo.ColumnSQL != null && m_vo.ColumnSQL.length() > 0) { String query; - if (m_vo.ColumnSQL.startsWith("@SQL=")) + if (m_vo.ColumnSQL.startsWith("@SQL=") || m_vo.ColumnSQL.startsWith("@SQLFIND=")) query = "NULL"; else query = m_vo.ColumnSQL; @@ -1334,6 +1334,26 @@ public class GridField } return m_vo.ColumnName; } // getColumnSQL + + /** + * Get Column Name or SQL for search queries + * @return column name + */ + public String getSearchColumnSQL() + { + if (m_vo.ColumnSQL != null && m_vo.ColumnSQL.length() > 0) + { + String query; + if (m_vo.ColumnSQL.startsWith("@SQL=")) + query = "NULL"; + else if (m_vo.ColumnSQL.startsWith("@SQLFIND=")) + query = m_vo.ColumnSQL.substring(9); + else + query = m_vo.ColumnSQL; + return query; + } + return m_vo.ColumnName; + } // getSearchColumnSQL /** * Is Virtual Column @@ -1362,6 +1382,15 @@ public class GridField return (m_vo.ColumnSQL != null && m_vo.ColumnSQL.length() > 0 && m_vo.ColumnSQL.startsWith("@SQL=")); } // isVirtualUIColumn + /** + * Is Virtual search Column + * @return column is virtual search + */ + public boolean isVirtualSearchColumn() + { + return (m_vo.ColumnSQL != null && m_vo.ColumnSQL.length() > 0 && m_vo.ColumnSQL.startsWith("@SQLFIND=")); + } // isVirtualDBColumn + /** * Get Header * @return header diff --git a/org.adempiere.base/src/org/compiere/model/GridFieldVO.java b/org.adempiere.base/src/org/compiere/model/GridFieldVO.java index fc05f6a27c..a6e56c6dbc 100644 --- a/org.adempiere.base/src/org/compiere/model/GridFieldVO.java +++ b/org.adempiere.base/src/org/compiere/model/GridFieldVO.java @@ -202,7 +202,7 @@ public class GridFieldVO implements Serializable vo.ValidationCode = rs.getString(i); else if (columnName.equalsIgnoreCase("ColumnSQL")) { vo.ColumnSQL = rs.getString(i); - if (vo.ColumnSQL != null && !vo.ColumnSQL.startsWith("@SQL=") && vo.ColumnSQL.contains("@")) { + if (vo.ColumnSQL != null && !vo.ColumnSQL.startsWith("@SQL=") && !vo.ColumnSQL.startsWith("@SQLFIND=") && vo.ColumnSQL.contains("@")) { // NOTE: cannot use window context because this is set globally on the query, not per record vo.ColumnSQL = Env.parseContext(ctx, -1, vo.ColumnSQL, false, true); } diff --git a/org.adempiere.base/src/org/compiere/model/LookupDisplayColumn.java b/org.adempiere.base/src/org/compiere/model/LookupDisplayColumn.java index 774115b854..b328a1eab8 100644 --- a/org.adempiere.base/src/org/compiere/model/LookupDisplayColumn.java +++ b/org.adempiere.base/src/org/compiere/model/LookupDisplayColumn.java @@ -67,7 +67,7 @@ public class LookupDisplayColumn implements Serializable IsTranslated = isTranslated; DisplayType = ad_Reference_ID; AD_Reference_ID = ad_Reference_Value_ID; - if (columnSQL != null && columnSQL.length() > 0 && columnSQL.startsWith("@SQL=")) + if (columnSQL != null && columnSQL.length() > 0 && (columnSQL.startsWith("@SQL=") || columnSQL.startsWith("@SQLFIND="))) ColumnSQL = "NULL"; else ColumnSQL = columnSQL; diff --git a/org.adempiere.base/src/org/compiere/model/MColumn.java b/org.adempiere.base/src/org/compiere/model/MColumn.java index a8997afa2b..8f7d2fae6e 100644 --- a/org.adempiere.base/src/org/compiere/model/MColumn.java +++ b/org.adempiere.base/src/org/compiere/model/MColumn.java @@ -52,7 +52,7 @@ public class MColumn extends X_AD_Column /** * */ - private static final long serialVersionUID = 7215660422231054443L; + private static final long serialVersionUID = -6905852892037761285L; public static MColumn get (Properties ctx, int AD_Column_ID) { @@ -228,6 +228,16 @@ public class MColumn extends X_AD_Column String s = getColumnSQL(); return s != null && s.length() > 0 && s.startsWith("@SQL="); } // isVirtualUIColumn + + /** + * Is Virtual Search Column + * @return true if virtual search column + */ + public boolean isVirtualSearchColumn() + { + String s = getColumnSQL(); + return s != null && s.length() > 0 && s.startsWith("@SQLFIND="); + } // isVirtualSearchColumn /** * Is the Column Encrypted? @@ -362,7 +372,7 @@ public class MColumn extends X_AD_Column setIsMandatory(false); if (isUpdateable()) setIsUpdateable(false); - if (isVirtualUIColumn() && isIdentifier()) + if ((isVirtualUIColumn() || isVirtualSearchColumn()) && isIdentifier()) setIsIdentifier(false); } // Updateable @@ -1240,10 +1250,18 @@ public class MColumn extends X_AD_Column } public String getColumnSQL(boolean nullForUI) { + return getColumnSQL(nullForUI, true); + } + + public String getColumnSQL(boolean nullForUI, boolean nullForSearch) { String query = getColumnSQL(); if (query != null && query.length() > 0) { if (query.startsWith("@SQL=") && nullForUI) query = "NULL"; + else if (query.startsWith("@SQLFIND=") && nullForSearch) + query = "NULL"; + else if (query.startsWith("@SQLFIND=") && !nullForSearch) + query = query.substring(9); } return query; } diff --git a/org.adempiere.base/src/org/compiere/model/MField.java b/org.adempiere.base/src/org/compiere/model/MField.java index cea2cdf80c..305475b916 100644 --- a/org.adempiere.base/src/org/compiere/model/MField.java +++ b/org.adempiere.base/src/org/compiere/model/MField.java @@ -149,6 +149,13 @@ public class MField extends X_AD_Field setAD_Val_Rule_ID(0); setIsToolbarButton(null); } + if (isDisplayed()) { + MColumn column = (MColumn) getAD_Column(); + if (column.isVirtualSearchColumn()) { + setIsDisplayed(false); + setIsDisplayedGrid(false); + } + } return true; } // beforeSave diff --git a/org.adempiere.base/src/org/compiere/model/MLookupFactory.java b/org.adempiere.base/src/org/compiere/model/MLookupFactory.java index e8d00c8547..fc62fa2ca3 100644 --- a/org.adempiere.base/src/org/compiere/model/MLookupFactory.java +++ b/org.adempiere.base/src/org/compiere/model/MLookupFactory.java @@ -435,7 +435,7 @@ public class MLookupFactory ZoomWindowPO = rs.getInt(9); //AD_Table_ID = rs.getInt(10); displayColumnSQL = rs.getString(11); - if (displayColumnSQL != null && displayColumnSQL.length() > 0 && displayColumnSQL.startsWith("@SQL=")) + if (displayColumnSQL != null && displayColumnSQL.length() > 0 && (displayColumnSQL.startsWith("@SQL=") || displayColumnSQL.startsWith("@SQLFIND="))) displayColumnSQL = "NULL"; if (displayColumnSQL != null && displayColumnSQL.contains("@") && displayColumnSQL.startsWith("@SQL=")) displayColumnSQL = Env.parseContext(Env.getCtx(), -1, displayColumnSQL, false, true); @@ -668,7 +668,7 @@ public class MLookupFactory embedSQL.append(TableNameAlias).append(".Value||'-'||"); MColumn columnDisplay = new MColumn(Env.getCtx(), columnDisplay_ID, null); - if (columnDisplay.isVirtualUIColumn()) + if (columnDisplay.isVirtualUIColumn() || columnDisplay.isVirtualSearchColumn()) { s_log.warning("Virtual UI Column must not be used as display"); return null; diff --git a/org.adempiere.base/src/org/compiere/model/MQuery.java b/org.adempiere.base/src/org/compiere/model/MQuery.java index a9a15c5370..4bb6479fd6 100644 --- a/org.adempiere.base/src/org/compiere/model/MQuery.java +++ b/org.adempiere.base/src/org/compiere/model/MQuery.java @@ -1183,7 +1183,7 @@ class Restriction implements Serializable MTable table = MTable.get(Env.getCtx(), tableName); if (table != null) { for (MColumn col : table.getColumns(false)) { - String colSQL = col.getColumnSQL(true); + String colSQL = col.getColumnSQL(true, false); if (colSQL != null && colSQL.contains("@")) colSQL = Env.parseContext(Env.getCtx(), -1, colSQL, false, true); if (colSQL != null && ColumnName.equals(colSQL.trim())) { diff --git a/org.adempiere.base/src/org/compiere/model/POInfo.java b/org.adempiere.base/src/org/compiere/model/POInfo.java index 3f0dfa26c8..871981c7db 100644 --- a/org.adempiere.base/src/org/compiere/model/POInfo.java +++ b/org.adempiere.base/src/org/compiere/model/POInfo.java @@ -48,7 +48,7 @@ public class POInfo implements Serializable /** * */ - private static final long serialVersionUID = 3496403499343293597L; + private static final long serialVersionUID = -6346988499971159874L; /** Used by Remote FinReport */ /** @@ -199,7 +199,7 @@ public class POInfo implements Serializable // m_AccessLevel = rs.getString(18); String ColumnSQL = rs.getString(19); - if (ColumnSQL != null && ColumnSQL.length() > 0 && ColumnSQL.startsWith("@SQL=")) + if (ColumnSQL != null && ColumnSQL.length() > 0 && (ColumnSQL.startsWith("@SQL=") || ColumnSQL.startsWith("@SQLFIND="))) ColumnSQL = "NULL"; if (ColumnSQL != null && ColumnSQL.contains("@")) ColumnSQL = Env.parseContext(Env.getCtx(), -1, ColumnSQL, false, true); @@ -378,7 +378,7 @@ public class POInfo implements Serializable if (index < 0 || index >= m_columns.length) return null; if (m_columns[index].ColumnSQL != null && m_columns[index].ColumnSQL.length() > 0) { - if (m_columns[index].ColumnSQL.startsWith("@SQL=")) + if (m_columns[index].ColumnSQL.startsWith("@SQL=") || m_columns[index].ColumnSQL.startsWith("@SQLFIND=")) return "NULL AS " + m_columns[index].ColumnName; return m_columns[index].ColumnSQL + " AS " + m_columns[index].ColumnName; } @@ -425,6 +425,20 @@ public class POInfo implements Serializable && m_columns[index].ColumnSQL.length() > 0 && m_columns[index].ColumnSQL.startsWith("@SQL="); } // isVirtualUIColumn + + /** + * Is Column Virtual Search? + * @param index index + * @return true if column is virtual search + */ + public boolean isVirtualSearchColumn (int index) + { + if (index < 0 || index >= m_columns.length) + return true; + return m_columns[index].ColumnSQL != null + && m_columns[index].ColumnSQL.length() > 0 + && m_columns[index].ColumnSQL.startsWith("@SQLFIND="); + } // isVirtualSearchColumn /** * Get Column Label diff --git a/org.adempiere.base/src/org/compiere/print/DataEngine.java b/org.adempiere.base/src/org/compiere/print/DataEngine.java index 0e19747aa2..83a89d3021 100644 --- a/org.adempiere.base/src/org/compiere/print/DataEngine.java +++ b/org.adempiere.base/src/org/compiere/print/DataEngine.java @@ -310,6 +310,8 @@ public class DataEngine int AD_Column_ID = rs.getInt(1); String ColumnName = rs.getString(2); String ColumnSQL = rs.getString(24); + if (ColumnSQL != null && ColumnSQL.length() > 0 && ColumnSQL.startsWith("@SQLFIND=")) + ColumnSQL = ColumnSQL.substring(9); if (ColumnSQL != null && ColumnSQL.length() > 0 && ColumnSQL.startsWith("@SQL=")) ColumnSQL = "NULL"; if (ColumnSQL != null && ColumnSQL.contains("@")) diff --git a/org.adempiere.base/src/org/compiere/process/FactReconciliation.java b/org.adempiere.base/src/org/compiere/process/FactReconciliation.java index 9e28d559c6..db8684bf98 100644 --- a/org.adempiere.base/src/org/compiere/process/FactReconciliation.java +++ b/org.adempiere.base/src/org/compiere/process/FactReconciliation.java @@ -72,7 +72,7 @@ public class FactReconciliation extends SvrProcess pstmt.setTimestamp(3, p_DateAcct_From); pstmt.setTimestamp(4, p_DateAcct_To); int count = pstmt.executeUpdate(); - String result = Msg.getMsg(getCtx(),"@Created@") + ": " + count + ", "; + String result = Msg.getMsg(getCtx(),"Created") + ": " + count + ", "; if (log.isLoggable(Level.FINE))log.log(Level.FINE, result); @@ -88,7 +88,7 @@ public class FactReconciliation extends SvrProcess pstmt = DB.prepareStatement(sql, get_TrxName()); pstmt.setInt(1, getAD_PInstance_ID()); count = pstmt.executeUpdate(); - result = Msg.getMsg(getCtx(), "@Deleted@") + ": " + count; + result = Msg.getMsg(getCtx(), "Deleted") + ": " + count; if (log.isLoggable(Level.FINE))log.log(Level.FINE, result); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WSearchEditor.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WSearchEditor.java index 33dcc62e3a..9dea1829aa 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WSearchEditor.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WSearchEditor.java @@ -286,7 +286,7 @@ public class WSearchEditor extends WEditor implements ContextMenuListener, Value } } - private void actionRefresh(Object value) + protected void actionRefresh(Object value) { // boolean mandatory = isMandatory(); // AEnv.actionRefresh(lookup, value, mandatory); @@ -345,7 +345,7 @@ public class WSearchEditor extends WEditor implements ContextMenuListener, Value // } - private void actionText(String text) + protected void actionText(String text) { // Nothing entered if (text == null || text.length() == 0 || text.equals("%")) @@ -411,7 +411,7 @@ public class WSearchEditor extends WEditor implements ContextMenuListener, Value } // actionText - private void resetButtonState() { + protected void resetButtonState() { getComponent().getButton().setEnabled(true); if (ThemeManager.isUseFontIconForImage()) getComponent().getButton().setIconSclass(imageUrl); @@ -421,7 +421,7 @@ public class WSearchEditor extends WEditor implements ContextMenuListener, Value } - private void actionCombo (Object value) + protected void actionCombo (Object value) { if (log.isLoggable(Level.FINE)) log.fine("Value=" + value); @@ -465,7 +465,7 @@ public class WSearchEditor extends WEditor implements ContextMenuListener, Value * Action - Special Quick Entry Screen * @param newRecord true if new record should be created */ - private void actionQuickEntry (boolean newRecord) + protected void actionQuickEntry (boolean newRecord) { if(!getComponent().isEnabled()) return; @@ -538,7 +538,7 @@ public class WSearchEditor extends WEditor implements ContextMenuListener, Value } } // actionQuickEntry - private void actionButton(String queryValue) + protected void actionButton(String queryValue) { if (lookup == null) return; // leave button disabled 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 195f38653e..446505fdd2 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 @@ -768,7 +768,7 @@ public class FindWindow extends Window implements EventListener, ValueCha if (mField.isSelectionColumn()) { gridFieldList.add(mField); // isSelectionColumn } else { - if (isDisplayed && mField.getDisplayType() != DisplayType.Button && !mField.getColumnName().equals("AD_Client_ID")) + if ((isDisplayed || mField.isVirtualSearchColumn()) && mField.getDisplayType() != DisplayType.Button && !mField.getColumnName().equals("AD_Client_ID")) moreFieldList.add(mField); } } // for all target tab fields @@ -1633,7 +1633,7 @@ public class FindWindow extends Window implements EventListener, ValueCha if (field == null || field.isVirtualUIColumn()) continue; boolean isProductCategoryField = isProductCategoryField(field.getColumnName()); - String ColumnSQL = field.getColumnSQL(false); + String ColumnSQL = field.getSearchColumnSQL(); // Left brackets Listbox listLeftBracket = (Listbox)row.getFellow("listLeftBracket"+row.getId()); String lBrackets = listLeftBracket.getSelectedItem().getValue().toString(); @@ -1866,7 +1866,7 @@ public class FindWindow extends Window implements EventListener, ValueCha GridField field = getTargetMField(ColumnName); if (field.isVirtualUIColumn()) continue; - StringBuilder ColumnSQL = new StringBuilder(field.getColumnSQL(false)); + StringBuilder ColumnSQL = new StringBuilder(field.getSearchColumnSQL()); m_query.addRangeRestriction(ColumnSQL.toString(), value, valueTo, ColumnName, wed.getDisplay(), wedTo.getDisplay(), true, 0); appendCode(code, ColumnName, MQuery.BETWEEN, value.toString(), valueTo.toString(), "AND", "", ""); @@ -1881,7 +1881,7 @@ public class FindWindow extends Window implements EventListener, ValueCha GridField field = getTargetMField(ColumnName); boolean isProductCategoryField = isProductCategoryField(field.getColumnName()); - StringBuilder ColumnSQL = new StringBuilder(field.getColumnSQL(false)); + StringBuilder ColumnSQL = new StringBuilder(field.getSearchColumnSQL()); // add encryption here if the field is encrypted. if (field.isEncrypted()) { @@ -1947,7 +1947,7 @@ public class FindWindow extends Window implements EventListener, ValueCha } GridField field = getTargetMField(ColumnName); - StringBuilder ColumnSQL = new StringBuilder(field.getColumnSQL(false)); + StringBuilder ColumnSQL = new StringBuilder(field.getSearchColumnSQL()); // m_query.addRestriction(ColumnSQL.toString(), MQuery.LESS_EQUAL, valueTo, ColumnName, wed.getDisplay()); appendCode(code, ColumnName, MQuery.LESS_EQUAL, valueTo.toString(), "", "AND", "", ""); @@ -2379,7 +2379,7 @@ public class FindWindow extends Window implements EventListener, ValueCha private String getSubCategoryWhereClause(GridField field, int productCategoryId) { //if a node with this id is found later in the search we have a loop in the tree int subTreeRootParentId = 0; - StringBuilder retString = new StringBuilder(field.getColumnSQL(false)).append(" IN ("); + StringBuilder retString = new StringBuilder(field.getSearchColumnSQL()).append(" IN ("); String sql = "SELECT M_Product_Category_ID, M_Product_Category_Parent_ID FROM M_Product_Category WHERE AD_Client_ID=? AND IsActive='Y'"; final Vector categories = new Vector(100); PreparedStatement pstmt = null; @@ -2709,4 +2709,4 @@ public class FindWindow extends Window implements EventListener, ValueCha } // setDtatusDB /** END DEVCOFFEE **/ -} // FindPanel \ No newline at end of file +} // FindPanel