/** * Analyzes if a set of rules is applicable for an object. * * A rule should at least contain an `action`, which should be executed later if the rule applies to the analyzed object, * and a `date` configuration (see {@link sol.common.SordUtils#getValue}) on which all checks will be performed. * * Additionally it can specify a `shift` and a `recur` attribute (for poosible configurations see {@link sol.common_monitoring.as.MonitorUtils#evalDateUnitConfig MonitorUtils}). * While `shift` can be a positive or a negative value, `recure` has to be positive. * Also optional is the `execution` attribute, which saves the last execution eiter to a map field or an index field. In case the rule is 'reoccuring' it is mandatory, * because the analyzer needs determine when the next reoccurrence should be. * * `date` only: the analyzer just checks, if the date is in the past * * `shift`: the analyzer checks, if the date plus/minus a value is already exceeded. * * `recur`: the analyzer checks, if the date (or the last execution) plus a value is exceeded. * * `shift` and `recur` can be combined. * * If `date` or `shift` rules hit, now next run will be calculated, if they miss, the analyzer determines the date for the next check. * `recur` rules always calculate a next run date (no breaking condition so far). * * Each kind of rule returns it's specified action when it hits. * * var analyzer = sol.create("sol.common_monitoring.as.analyzers.RuleAnalyzer", { * rules: [ * ... rule objects ... * ] * }); * * #Example rules: * * Starts a workflow (in the context of the user "John Doe"), if the date in the MY_DATE field is in the past: * * { * action: { type: "WORKFLOW", templateId: "MyWorkflowTemplate", user: "John Doe" }, * date: { type: "GRP", key: "MY_DATE" }, * } * * If you don't want to filter by date, instead of defining `date`, you can define `noAdditionalDateCheck: true`. This will execute the action for every sord found by the collector. * * Creates a reminder if the date in the MY_DATE field is less then two months in the future (date minus 2 month is before now) and saves the current date to a map field: * * { * action: { type: "REMINDER", user: "Administrator" }, * date: { type: "GRP", key: "MY_DATE" }, * shift: { value: -2, unit: "M" }, * execution: { type: "MAP", key: "MY_DATE_EXECUTION" } * } * * Starts a workflow, if the date from the map field (see execution - or if not already set, the `date` itself) plus the value from the MY_REOCCURRENCE field in days ("D") is in the past, * and saves the date of the last sheduled execution a map field (therefor all recurring events will be executed, even if several checks have been missed). * * { * action: { type: "WORKFLOW", templateId: "MyWorkflowTemplate" }, * date: { type: "GRP", key: "MY_DATE" }, * recur: { type: "GRP", key: "MY_REOCCURRENCE", unit: "D" }, * execution: { type: "MAP", key: "MY_DATE_EXECUTION" } * } * * By default, the action is only performed if the date is before the filing date. If the action should be performed anyway, the parameter `ignoreFilingDate` can be set. * * { * action: { type: "WORKFLOW", templateId: "MyWorkflowTemplate" }, * date: { type: "GRP", key: "MY_DATE" }, * ignoreFilingDate: true, * execution: { type: "MAP", key: "MY_DATE_EXECUTION" } * } * * @eloas * * @requires sol.common.JsonUtils * @requires sol.common.SordUtils * @requires sol.common.DateUtils * @requires sol.common_monitoring.as.MonitorUtils */ sol.define("sol.common_monitoring.as.analyzers.RuleAnalyzer", { requiredConfig: ["rules"], /** * @cfg {Object[]} rules */ initialize: function (config) { var me = this; me.$super("sol.Base", "initialize", [config]); me._todayIso = sol.common.DateUtils.dateToIso(new Date()); }, /** * Analyzes an object utilizing the set of rules set during initialization. * @param {de.elo.ix.client.Sord} sord * @return {Object[]} */ analyze: function (sord) { var me = this, results = [], max = me.rules.length, i, rule, result, hitCount, missCount; me.logger.enter("analyze", { objId: sord.id, name: String(sord.name), rulesCount: me.rules.length }); for (i = 0; i < max; i++) { rule = me.rules[i]; result = me.analyzeRule(sord, rule); if (result) { results.push(result); } } hitCount = me.countHits(results); missCount = max - hitCount; me.logger.exit("analyze", { hits: hitCount, misses: missCount, result: results }); return results; }, /** * @private * Analyze an individual rule. * @param {de.elo.ix.client.Sord} sord * @param {Object} rule * @return {Object} */ analyzeRule: function (sord, rule) { var me = this, result = {}, localRuleCopy = JSON.parse(JSON.stringify(rule)), isoDatefromSord, isoDate, sordFilingDate, lastExecution, sanitizedIsoDate; if (localRuleCopy.date) { localRuleCopy.date.fillString = "00000000000000"; isoDatefromSord = sol.common.SordUtils.getValue(sord, localRuleCopy.date); isoDate = isoDatefromSord; } else if (localRuleCopy.noAdditionalDateCheck === true) { isoDate = me._todayIso; } if (isoDatefromSord && localRuleCopy.execution && (localRuleCopy.execution.type === "MAP")) { // enhance rule.execution to enable new reminder after the sord date, which should be checked, was altered localRuleCopy.execution.key = localRuleCopy.execution.key + "_" + isoDatefromSord; } if (!isoDate) { me.logger.debug(["skip rule: date has no value", localRuleCopy.date]); return null; } sordFilingDate = sol.common.DateUtils.dateToIso(sol.common.DateUtils.isoToDate(sord.IDateIso), { startOfDay: true }); lastExecution = me.checkExecution(sord, localRuleCopy.execution); // An empty value or the value `0` for the recurrence of an event (e.g. `0 years`) should also to be interpreted as a one-time event. if (lastExecution && (!localRuleCopy.recur || !localRuleCopy.recur.value)) { me.logger.debug(["skip rule: was already executed (execution={0})", lastExecution]); return null; } me.logger.debug("analyze rule: " + sol.common.JsonUtils.stringifyAll(localRuleCopy)); if (localRuleCopy.shift) { isoDate = me.shiftIso(sord, isoDate, localRuleCopy.shift); } // An empty value or the value `0` for the recurrence of an event (e.g. `0 years`) should also to be interpreted as a one-time event. if (localRuleCopy.recur && localRuleCopy.recur.value) { if (!localRuleCopy.execution) { throw "reoccuring rules need to define an 'execution' property"; } if (lastExecution) { isoDate = lastExecution; do { // prevent useless reminders from before the element was in the repository isoDate = me.shiftIso(sord, isoDate, localRuleCopy.recur); } while (isoDate < sordFilingDate); } result.nextRun = me.shiftIso(sord, isoDate, localRuleCopy.recur); lastExecution = isoDate; } me.logger.debug(["check date: {0}", isoDate]); if (isoDate && (isoDate <= me._todayIso)) { me.updateExecution(sord, localRuleCopy.execution, lastExecution); sanitizedIsoDate = me.sanitizeIsoDate(isoDate, sordFilingDate); if (localRuleCopy.ignoreFilingDate) { result.action = localRuleCopy.action; me.logger.debug("check was successful", localRuleCopy); } else if (sanitizedIsoDate >= sordFilingDate) { result.action = localRuleCopy.action; me.logger.debug("check was successful", localRuleCopy); } else { me.logger.debug("skip action because date is before sord filing date"); } } else { result.nextRun = isoDate; me.logger.debug("check was not successful", localRuleCopy); } if (result.action) { me.logger.debug(["found action: {0}", sol.common.JsonUtils.stringifyAll(result.action)]); } if (result.nextRun) { me.logger.debug(["next run scheduled for '{0}'", result.nextRun]); } return result; }, /** * @private * Shifts an ISO date. Uses {@link sol.common.DateUtils DateUtils} and {@link sol.common_monitoring.as.MonitorUtils MonitorUtils} internally * @param {de.elo.ix.client.Sord} sord * @param {String} isoDate * @param {Object} params See {@link sol.common_monitoring.as.MonitorUtils#evalDateUnitConfig evalDateUnitConfig} * @return {String} */ shiftIso: function (sord, isoDate, params) { var shiftCfg, date, shiftedIsoDate; shiftCfg = sol.common_monitoring.as.MonitorUtils.evalDateUnitConfig(sord, params); date = sol.common.DateUtils.isoToDate(isoDate + ""); date = sol.common.DateUtils.shift(date, shiftCfg.value, { unit: shiftCfg.unit }); shiftedIsoDate = sol.common.DateUtils.dateToIso(date); return shiftedIsoDate; }, /** * @private * Retrieves the value of an earlier execution depending on a given configuration. * @param {de.elo.ix.client.Sord} sord * @param {Object} execution * @return {String} */ checkExecution: function (sord, execution) { var executionDateIso, shortDateExecution; if (execution) { executionDateIso = sol.common.SordUtils.getValue(sord, execution, { fillString: "00000000000000" }); if (!executionDateIso) { // check if there is an old execution entry with a short date shortDateExecution = sol.common.ObjectUtils.clone(execution); if (shortDateExecution.key.substr(shortDateExecution.key.length - 6) == "000000") { shortDateExecution.key = shortDateExecution.key.substring(0, shortDateExecution.key.length - 6); executionDateIso = sol.common.SordUtils.getValue(sord, shortDateExecution); } } return executionDateIso; } return null; }, /** * @private * Updates the value of the current execution depending on a given configuration. * @param {de.elo.ix.client.Sord} sord * @param {Object} execution * @param {String} date An ISO date to set according to `execution` */ updateExecution: function (sord, execution, date) { var me = this, mapitems; if (!execution) { return; } execution.value = date || me._todayIso; mapitems = sol.common.SordUtils.updateSord(sord, [execution], { fillString: "00000000000000" }); if (mapitems && mapitems.length > 0) { ixConnect.ix().checkinMap(MapDomainC.DOMAIN_SORD, sord.id, sord.id, mapitems, LockC.NO); } }, /** * @private * Counts the results, that where a hit. A hit is marked by a result, containing an action. * @param {Object[]} results * @return {Number} */ countHits: function (results) { var hitCount = 0, i, max; for (i = 0, max = results.length; i < max; i++) { if (results[i].action) { hitCount++; } } return hitCount; }, /** * @private * Fills isoDate to the correct amount of digigt to prevent the fail of a check. * @param {String} isoDate * @param {String} verificationDate * @return {String} */ sanitizeIsoDate: function (isoDate, verificationDate) { if (String(isoDate).length === 8 && String(verificationDate).length === 14) { isoDate += "000000"; } return isoDate; } });