From 7432c2c7dcc14a84779d4508299e9857c235edc4 Mon Sep 17 00:00:00 2001
From: "S.Listl" <S.Listl@SLISTL.aditosoftware.local>
Date: Tue, 7 Apr 2020 13:58:49 +0200
Subject: [PATCH] JditoFilter_lib and Date_lib improvements

---
 process/Date_lib/process.js        | 166 ++++++++++++++++++++++++++++-
 process/JditoFilter_lib/process.js |  58 ++++++----
 2 files changed, 203 insertions(+), 21 deletions(-)

diff --git a/process/Date_lib/process.js b/process/Date_lib/process.js
index 18a7b2b188..9b6a804039 100644
--- a/process/Date_lib/process.js
+++ b/process/Date_lib/process.js
@@ -163,4 +163,168 @@ DateUtils.validateNotInFuture = function (pDate)
 DateUtils.getCurrentYear = function ()
 {
     return parseInt(datetime.toDate(vars.get("$sys.date"), "yyyy"));
-}
\ No newline at end of file
+}
+
+
+/**
+ * Object for handling durations. It consists of years, months, days and milliseconds. Because hours, minutes and seconds have fixed lengths, these
+ * are all added to the milliseconds part, but years and months can have different amounts of days, so they are stored separately.
+ * 
+ * @param {Number} [pYears=0] years
+ * @param {Number} [pMonths=0] months
+ * @param {Number} [pDays=0] days
+ * @param {Number} [pMillis=0] milliseconds
+ */
+function Duration (pYears, pMonths, pDays, pMillis)
+{
+    this.years = pYears !== undefined ? Number(pYears) : 0;
+    this.months = pMonths !== undefined ? Number(pMonths) : 0;
+    //because of daylight saving time, a day is not always 24h
+    this.days = pDays !== undefined ? Number(pDays) : 0;
+    this.milliseconds = pMillis !== undefined ? Number(pMillis) : 0;
+}
+
+/**
+ * creates a new Duration object, this function can also take hours, minutes and seconds (they are automatically converted to milliseconds)
+ * 
+ * @param {Number} [pYears=0] years
+ * @param {Number} [pMonths=0] months
+ * @param {Number} [pDays=0] days
+ * @param {Number} [pHours=0] hours
+ * @param {Number} [pMinutes=0] minutes
+ * @param {Number} [pSeconds=0] seconds
+ * @return {Duration} a new Duration object
+ */
+Duration.of = function (pYears, pMonths, pDays, pHours, pMinutes, pSeconds)
+{
+    if (pHours == undefined)
+        pHours = 0;
+    if (pMinutes == undefined)
+        pMinutes = 0;
+    if (pSeconds == undefined)
+        pSeconds = 0;
+    
+    pMinutes += pHours * 60;
+    pSeconds += pMinutes * 60;
+    
+    return new Duration(pYears, pMonths, pDays, pSeconds * 1000);
+}
+
+/**
+ * creates a new Duration object only with hours, minutes and seconds
+ * 
+ * @param {Number} [pHours=0] hours
+ * @param {Number} [pMinutes=0] minutes
+ * @param {Number} [pSeconds=0] seconds
+ * @return {Duration} a new Duration object
+ */
+Duration.ofTime = function (pHours, pMinutes, pSeconds)
+{
+    return Duration.of(undefined, undefined, undefined, pHours, pMinutes, pSeconds);
+}
+
+/**
+ * parses a ISO-8601 duration string into a Duration object
+ * 
+ * @param {String} pIso8601Duration string representation of the duration
+ * @return {Duration} new duration object
+ */
+Duration.parse = function (pIso8601Duration)
+{
+    var iso8601DurationRegex = /([-+]?)P(?:([-+]?[0-9]+)Y)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)W)?(?:([-+]?[0-9]+)D)?(?:T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+(?:[.,][0-9]{0,9})?)S)?)?/;
+    var matches = pIso8601Duration.match(iso8601DurationRegex);
+    
+    var sign = matches[1] === "-" ? -1 : 1;
+    
+    var years = matches[2] === undefined ? 0 : _toNumberCommaSafe(matches[2]) * sign;
+    var months = matches[3] === undefined ? 0 : _toNumberCommaSafe(matches[3]) * sign;
+    var weeks = matches[4] === undefined ? 0 : _toNumberCommaSafe(matches[4]) * sign;
+    var days = matches[5] === undefined ? 0 : _toNumberCommaSafe(matches[5]) * sign;
+    if (weeks > 0)
+        days += weeks * 7;
+    
+    var hours = matches[6] === undefined ? 0 : _toNumberCommaSafe(matches[6]) * sign;
+    var minutes = matches[7] === undefined ? 0 : _toNumberCommaSafe(matches[7]) * sign;
+    var seconds = matches[8] === undefined ? 0 : _toNumberCommaSafe(matches[8]) * sign;
+    
+    return Duration.of(years, months, days, hours, minutes, seconds);
+    
+    //converts a string to a number, accepting a comma as decimal separator (for example, Number("2,3") would normally return NaN)
+    function _toNumberCommaSafe (pNumberString)
+    {
+        return Number(pNumberString.replace(",", "."));
+    }
+}
+
+/**
+ * makes a ISO-8601 string representing the duration
+ * 
+ * @return {String} the duration as string
+ */
+Duration.prototype.getIso8601Representation = function ()
+{
+    var iso8601Duration = "P"
+        + (this.years ? this.years + "Y" : "")
+        + (this.months ? this.months + "M" : "")
+        + (this.days ? this.days + "D" : "");
+    
+    if (this.milliseconds)
+    {
+        var millis = this.milliseconds;
+        var seconds = Math.floor(millis / 1000);
+        millis = millis % 1000;
+        var minutes = Math.floor(seconds / 60);
+        seconds = seconds % 60;
+        var hours = Math.floor(minutes / 60);
+        minutes = minutes % 60;
+        
+        iso8601Duration += "T"
+            + (hours ? hours + "H" : "")
+            + (minutes ? minutes + "M" : "")
+            + (seconds ? seconds + (millis ? "." + millis : "") + "S" : "");
+    }
+    
+    //there must always be at least one element
+    if (iso8601Duration === "P")
+        iso8601Duration += "0D";
+    
+    return iso8601Duration;
+}
+
+/**
+ * inverts the direction of the duration
+ * 
+ * @return {Duration} current object
+ */
+Duration.prototype.invert = function ()
+{
+    this.years = -this.years;
+    this.months = -this.months;
+    this.days = -this.days;
+    this.milliseconds = -this.milliseconds;
+    
+    return this;
+}
+
+/**
+ * Adds the duration to the given Date object (note that the duration can also be negative). This will alter the given object.
+ *
+ * @param {Date} pDate date to change, any value that is not of type "Date" will be converted to a Date
+ * @return {Date} the Date object
+ */
+Duration.prototype.addToDate = function (pDate)
+{
+    if (!(pDate instanceof Date))
+        pDate = new Date(pDate);
+        
+    if (this.years !== 0)
+        pDate.setFullYear(pDate.getFullYear() + this.years);
+    if (this.months !== 0)
+        pDate.setMonth(pDate.getMonth() + this.months);
+    if (this.days !== 0)
+        pDate.setDate(pDate.getDate() + this.days);
+    if (this.milliseconds !== 0)
+        pDate.setTime(pDate.getTime() + this.milliseconds);
+    
+    return pDate;
+}
diff --git a/process/JditoFilter_lib/process.js b/process/JditoFilter_lib/process.js
index e00af4d021..9e6137b3eb 100644
--- a/process/JditoFilter_lib/process.js
+++ b/process/JditoFilter_lib/process.js
@@ -2,10 +2,6 @@ import("system.tools");
 import("system.logging");
 import("Sql_lib");
 
-//private scope to make only JditoFilterUtils public
-var JditoFilterUtils = (function ()
-{
-
 /**
  * object for filtering records
  * 
@@ -16,7 +12,7 @@ var JditoFilterUtils = (function ()
  * 
  * @private
  */
-function JditoFilterHelper (pColumns, pFilter, pCustomCheckFns, pCheckFnThisArg) 
+function JditoFilter (pColumns, pFilter, pCustomCheckFns, pCheckFnThisArg) 
 {
     var columnMap = {};
     for (let i = 0, l = pColumns.length; i < l; i++)
@@ -25,12 +21,30 @@ function JditoFilterHelper (pColumns, pFilter, pCustomCheckFns, pCheckFnThisArg)
         if (col)
             columnMap[col] = {
                 index : i,
-                checkFn : pCustomCheckFns[col]//or nothing when there is no custom function
+                checkFn : (pCustomCheckFns ? pCustomCheckFns[col] : null)
             };
     }
     this.columnMap = columnMap;
+    
+    if (pFilter && pFilter.childs.length !== 0)
+        _removeEmptyGroups(pFilter.childs);
+    
     this.filter = pFilter;
     this.checkFnThisArg = pCheckFnThisArg || null;
+    
+    function _removeEmptyGroups (pCurrentArray)
+    {
+        for (let i = 0; i < pCurrentArray.length; i++)
+        {
+            let row = pCurrentArray[i];
+            if (row.type == "group")
+            {
+                _removeEmptyGroups(row.childs);
+                if (row.childs.length === 0)
+                    pCurrentArray.splice(i--, 1);
+            }
+        }
+    }
 }
 
 /**
@@ -40,9 +54,9 @@ function JditoFilterHelper (pColumns, pFilter, pCustomCheckFns, pCheckFnThisArg)
  * 
  * @return {boolean} true, if it matches the condition
  */
-JditoFilterHelper.prototype.checkRecord = function (pRow)
+JditoFilter.prototype.checkRecord = function (pRow)
 {
-    if (this.filter.length == 0)
+    if (!this.filter || this.filter.childs.length === 0)
         return true;
     
     var regexFlags = JditoFilterUtils.isUserIgnoreCase() ? "i" : undefined;
@@ -100,11 +114,17 @@ JditoFilterHelper.prototype.checkRecord = function (pRow)
                 return pRowValue == "";
             case "ISNOTNULL":
                 return pRowValue != "";
+            case "TIMEFRAME_EQUAL":
+            case "TIMEFRAME_COMING":
+            case "TIMEFRAME_PAST":
         }
     }
 }
 
-
+JditoFilter.prototype.filterRecords = function (pRecords)
+{
+    return pRecords.filter(this.checkRecord, this);
+}
 
 /**
  * Provides functions for using the filter with jdito recordcontainers
@@ -154,12 +174,7 @@ JditoFilterUtils.filterRecords = function (pColumns, pRecords, pFilter, pCustomC
     if (!pFilter)
         return pRecords;
     
-    var filter = new JditoFilterHelper(pColumns, pFilter, pCustomCheckFns || {}, pCheckFnThisArg);
-    
-    return pRecords.filter(function (row)
-        {
-            return this.checkRecord(row);
-        }, filter);
+    return new JditoFilter(pColumns, pFilter, pCustomCheckFns, pCheckFnThisArg).filterRecords(pRecords);
 }
 
 /**
@@ -178,7 +193,8 @@ JditoFilterUtils.filterRecords = function (pColumns, pRecords, pFilter, pCustomC
  *              var cond = new SqlBuilder();
  *              ...
  *              return cond;
- *          }
+ *          },
+ *          FIELD4 : null //if you set the value here to null of false, the field will be ignored and not added to the condition
  *      }
  *      
  * @example
@@ -215,7 +231,13 @@ JditoFilterUtils.getSqlCondition = function (pFilter, pTable, pTableAlias, pColu
         if (pFilter.type == "row")
         {
             if (pFilter.name in pColumnOrFnMap)
+            {
                 pFilter.name = pColumnOrFnMap[pFilter.name];
+                
+                //possibility to explicitly set the value to null/false so that the field is ignored
+                if (pFilter.name === null || pFilter.name === false)
+                    return;
+            }
             else if (pTable && pTableAlias)
                 pFilter.name = [pTable, pFilter.name, pTableAlias];
             else if (pTable)
@@ -302,7 +324,3 @@ JditoFilterUtils.isUserIgnoreCase = function ()
     var ignoreCase = user ? user[tools.PARAMS][tools.SELECTION_IGNORECASE] : "";
     return ignoreCase == "" || /true/i.test(ignoreCase);
 }
-
-    return JditoFilterUtils; //return only functions that should be public
-    
-})();
-- 
GitLab