dhis2-devs team mailing list archive
-
dhis2-devs team
-
Mailing list archive
-
Message #38007
[Branch ~dhis2-devs-core/dhis2/trunk] Rev 19419: moved services related to rules engine to dhis-web-commons-resources
------------------------------------------------------------
revno: 19419
committer: Abyot Asalefew Gizaw <abyota@xxxxxxxxx>
branch nick: dhis2
timestamp: Tue 2015-06-16 13:22:51 +0200
message:
moved services related to rules engine to dhis-web-commons-resources
modified:
dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js
dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js
--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk
Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js 2015-06-14 13:07:10 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js 2015-06-16 11:22:51 +0000
@@ -1773,469 +1773,4 @@
return event;
}
};
-})
-
-/* service for building variables based on the data in users fields */
-.service('VariableService', function($rootScope,$q,TrackerRuleVariableFactory,$filter,orderByFilter,$log){
- return {
- getVariables: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) {
- var thePromisedVariables = $q.defer();
- var variables = {};
-
- var pushVariable = function(variablename, variableValue, variableType, variablefound) {
- //First clean away single or double quotation marks at the start and end of the variable name.
- variableValue = $filter('trimquotes')(variableValue);
-
- //Append single quotation marks in case the variable is of text type:
- if(variableType === 'string') {
- variableValue = "'" + variableValue + "'";
- }
- else if(variableType === 'date') {
- variableValue = "'" + variableValue + "'";
- }
- else if(variableType === 'bool' || variableType === 'trueOnly') {
- if(eval(variableValue)) {
- variableValue = true;
- }
- else {
- variableValue = false;
- }
- }
- else if(variableType === "int" || variableType === "number") {
- variableValue = Number(variableValue);
- }
- else{
- $log.warn("unknown datatype:" + variableType);
- }
-
-
- //Make sure that the variableValue does not contain a dollar sign anywhere
- //- this would potentially mess up later use of the variable:
-// if(angular.isDefined(variableValue)
-// && variableValue !== null
-// && variableValue.indexOf("$") !== -1 ) {
-// variableValue = variableValue.replace(/\\$/,"");
-// }
-
- //TODO:
- //Also clean away instructions that might be erroneusly evalutated in javascript
-
- variables[variablename] = {
- variableValue:variableValue,
- variableType:variableType,
- hasValue:variablefound
- };
- };
-
- TrackerRuleVariableFactory.getProgramRuleVariables(programid).then(function(programVariables){
-
- // The following section will need a different implementation for event capture:
- var allEventsSorted = [];
- var currentEvent = executingEvent;
- var eventsSortedPerProgramStage = [];
-
- for(var key in allEventsByStage){
- if(allEventsByStage.hasOwnProperty(key)){
- eventsSortedPerProgramStage[key] = [];
- angular.forEach(allEventsByStage[key], function(event){
- allEventsSorted.push(event);
- eventsSortedPerProgramStage[key].push(event);
- });
- eventsSortedPerProgramStage[key] = orderByFilter(eventsSortedPerProgramStage[key], '-sortingDate').reverse();
- }
- }
- allEventsSorted = orderByFilter(allEventsSorted, '-sortingDate').reverse();
-
- var allDes = allDataElements;
-// angular.forEach($scope.programStages, function(programStage){
-// angular.forEach(programStage.programStageDataElements, function(dataElement) {
-// allDes[dataElement.dataElement.id] = dataElement;
-// });
-// });
- //End of region that neeeds specific implementation for event capture
-
- angular.forEach(programVariables, function(programVariable) {
- var valueFound = false;
- if(programVariable.programRuleVariableSourceType === "DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE"){
- if(programVariable.programStage) {
- angular.forEach(eventsSortedPerProgramStage[programVariable.programStage.id], function(event) {
- if(angular.isDefined(event[programVariable.dataElement.id])
- && event[programVariable.dataElement.id] !== null ){
- valueFound = true;
- pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound );
- }
- });
- } else {
- $log.warn("Variable id:'" + programVariable.id + "' name:'" + programVariable.name
- + "' does not have a programstage defined,"
- + " despite that the variable has sourcetype DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE" );
- }
-
- }
- else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_NEWEST_EVENT_PROGRAM"){
- angular.forEach(allEventsSorted, function(event) {
- if(angular.isDefined(event[programVariable.dataElement.id])
- && event[programVariable.dataElement.id] !== null ){
- valueFound = true;
- pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound );
- }
- });
- }
- else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_CURRENT_EVENT"){
- if(angular.isDefined(currentEvent[programVariable.dataElement.id])
- && currentEvent[programVariable.dataElement.id] !== null ){
- valueFound = true;
- pushVariable(programVariable.name, currentEvent[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound );
- }
- }
- else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_PREVIOUS_EVENT"){
- //Only continue checking for a value if there is more than one event.
- if(allEventsSorted && allEventsSorted.length > 1) {
- var previousvalue = null;
- var currentEventPassed = false;
- for(var i = 0; i < allEventsSorted.length; i++) {
- //Store the values as we iterate through the stages
- //If the event[i] is not the current event, it is older(previous). Store the previous value if it exists
- if(!currentEventPassed && allEventsSorted[i] !== currentEvent &&
- angular.isDefined(allEventsSorted[i][programVariable.dataElement.id])) {
- previousvalue = allEventsSorted[i][programVariable.dataElement.id];
- valueFound = true;
- }
- else if(allEventsSorted[i] === currentEvent) {
- //We have iterated to the newest event - store the last collected variable value - if any is found:
- if(valueFound) {
- pushVariable(programVariable.name, previousvalue, allDes[programVariable.dataElement.id].dataElement.type, valueFound );
- }
- //Set currentEventPassed, ending the iteration:
- currentEventPassed = true;
- }
- }
- }
- }
- else if(programVariable.programRuleVariableSourceType === "TEI_ATTRIBUTE"){
- angular.forEach(selectedEntity.attributes , function(attribute) {
- if(!valueFound) {
- if(attribute.attribute === programVariable.trackedEntityAttribute.id) {
- valueFound = true;
- pushVariable(programVariable.name, attribute.value, attribute.type, valueFound );
- }
- }
- });
- }
- else if(programVariable.programRuleVariableSourceType === "CALCULATED_VALUE"){
- //We won't assign the calculated variables at this step. The rules execution will calculate and assign the variable.
- }
- else if(programVariable.programRuleVariableSourceType === "NUMBEROFEVENTS_PROGRAMSTAGE"){
- var numberOfEvents = 0;
- if( programVariable.programStage && eventsSortedPerProgramStage[programVariable.programStage.id] ) {
- numberOfEvents = eventsSortedPerProgramStage[programVariable.programStage.id].length;
- }
- valueFound = true;
- pushVariable(programVariable.name, numberOfEvents, 'int', valueFound );
- }
- else {
- //Missing handing of ruletype
- $log.warn("Unknown programRuleVariableSourceType:" + programVariable.programRuleVariableSourceType);
- }
-
-
- if(!valueFound){
- //If there is still no value found, assign default value:
- if(programVariable.dataElement) {
- var dataElement = allDes[programVariable.dataElement.id];
- if( dataElement ) {
- pushVariable(programVariable.name, "", dataElement.dataElement.type );
- }
- else {
- $log.warn("Variable #{" + programVariable.name + "} is linked to a dataelement that is not part of the program");
- pushVariable(programVariable.name, "", "string" );
- }
- }
- else {
- pushVariable(programVariable.name, "", "string" );
- }
- }
- });
-
- //add context variables:
- //last parameter "valuefound" is always true for event date
- pushVariable('eventdate', executingEvent.eventDate, 'date', true );
-
- thePromisedVariables.resolve(variables);
- });
-
- return thePromisedVariables.promise;
- }
- };
-})
-
-
-
-/* service for executing tracker rules and broadcasting results */
-.service('TrackerRulesExecutionService', function(TrackerRulesFactory,VariableService, $rootScope, $log, $filter, orderByFilter){
- return {
- executeRules: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) {
- //When debugging rules, the caller should provide a variable for wether or not the rules is being debugged.
- //hard coding this for now:
- var debug = true;
- var verbose = true;
-
- var variablesHash = {};
-
- var replaceVariables = function(expression) {
- //replaces the variables in an expression with actual variable values.
- //First check if the expression contains variables at all(any dollar signs):
- if(expression.indexOf('#') !== -1) {
- //Find every variable name in the expression;
- var variablespresent = expression.match(/#{\w+}/g);
- //Replace each matched variable:
- angular.forEach(variablespresent, function(variablepresent) {
- //First strip away any dollar signs from the variable name:
- variablepresent = variablepresent.replace("#{","").replace("}","");
-
- if(angular.isDefined(variablesHash[variablepresent])) {
- //Replace all occurrences of the variable name(hence using regex replacement):
- expression = expression.replace(new RegExp("#{" + variablepresent + "}", 'g'),
- variablesHash[variablepresent].variableValue);
- }
- else {
- $log.warn("Expression " + expression + " conains variable " + variablepresent
- + " - but this variable is not defined." );
- }
-
- });
- }
- return expression;
- };
-
- var runDhisFunctions = function(expression) {
- //Called from "runExpression". Only proceed with this logic in case there seems to be dhis function calls: "dhis." is present.
- if(angular.isDefined(expression) && expression.indexOf("dhis.") !== -1){
- var dhisFunctions = [{name:"dhis.daysbetween",parameters:2},
- {name:"dhis.floor",parameters:1},
- {name:"dhis.modulus",parameters:2},
- {name:"dhis.hasValue",parameters:1},
- {name:"dhis.concatenate"}];
-
- angular.forEach(dhisFunctions, function(dhisFunction){
- //Replace each * with a regex that matches each parameter, allowing commas only inside single quotation marks.
- var regularExFunctionCall = new RegExp(dhisFunction.name.replace(".","\\.") + "\\([^\\)]*\\)",'g');
- var callsToThisFunction = expression.match(regularExFunctionCall);
- angular.forEach(callsToThisFunction, function(callToThisFunction){
- //Remove the function name and paranthesis:
- var justparameters = callToThisFunction.replace(/(^[^\(]+\()|\)$/g,"");
- //Then split into single parameters:
- var parameters = justparameters.match(/(('[^']+')|([^,]+))/g);
-
- //Show error if no parameters is given and the function requires parameters,
- //or if the number of parameters is wrong.
- if(angular.isDefined(dhisFunction.parameters)){
- //But we are only checking parameters where the dhisFunction actually has a defined set of parameters(concatenate, for example, does not have a fixed number);
- if((!angular.isDefined(parameters) && dhisFunction.parameters > 0)
- || parameters.length !== dhisFunction.parameters){
- $log.warn(dhisFunction.name + " was called with the incorrect number of parameters");
- }
- }
-
- //In case the function call is nested, the parameter itself contains an expression, run the expression.
- if(angular.isDefined(parameters)) {
- for (var i = 0; i < parameters.length; i++) {
- parameters[i] = runExpression(parameters[i],dhisFunction.name,"parameter:" + i);
- }
- }
-
- //Special block for dhis.weeksBetween(*,*) - add such a block for all other dhis functions.
- if(dhisFunction.name === "dhis.daysbetween")
- {
- var firstdate = $filter('trimquotes')(parameters[0]);
- var seconddate = $filter('trimquotes')(parameters[1]);
- firstdate = moment(firstdate);
- seconddate = moment(seconddate);
- //Replace the end evaluation of the dhis function:
- expression = expression.replace(callToThisFunction, seconddate.diff(firstdate,'days'));
- }
- else if(dhisFunction.name === "dhis.floor")
- {
- var floored = Math.floor(parameters[0]);
- //Replace the end evaluation of the dhis function:
- expression = expression.replace(callToThisFunction, floored);
- }
- else if(dhisFunction.name === "dhis.modulus")
- {
- var dividend = Number(parameters[0]);
- var divisor = Number(parameters[1]);
- var rest = dividend % divisor;
- //Replace the end evaluation of the dhis function:
- expression = expression.replace(callToThisFunction, rest);
- }
- else if(dhisFunction.name === "dhis.hasValue")
- {
- //"evaluate" hasvalue to true or false:
- if(variablesHash[parameters[0]].hasValue){
- expression = expression.replace(callToThisFunction, 'true');
- } else {
- expression = expression.replace(callToThisFunction, 'false');
- }
- }
- else if(dhisFunction.name === "dhis.concatenate")
- {
- var returnString = "'";
- for (var i = 0; i < parameters.length; i++) {
- returnString += parameters[i];
- }
- returnString += "'";
- expression = expression.replace(callToThisFunction, returnString);
- }
- });
- });
- }
-
- return expression;
- };
-
- var runExpression = function(expression, beforereplacement, identifier ){
- //determine if expression is true, and actions should be effectuated
- //If DEBUG mode, use try catch and report errors. If not, omit the heavy try-catch loop.:
- var answer = false;
- if(debug) {
- try{
-
- var dhisfunctionsevaluated = runDhisFunctions(expression);
- answer = eval(dhisfunctionsevaluated);
-
- if(verbose)
- {
- $log.info("Expression with id " + identifier + " was successfully run. Original condition was: " + beforereplacement + " - Evaluation ended up as:" + expression + " - Result of evaluation was:" + answer);
- }
- }
- catch(e)
- {
- $log.warn("Expression with id " + identifier + " could not be run. Original condition was: " + beforereplacement + " - Evaluation ended up as:" + expression + " - error message:" + e);
- }
- }
- else {
- //Just run the expression. This is much faster than the debug route: http://jsperf.com/try-catch-block-loop-performance-comparison
- var dhisfunctionsevaluated = runDhisFunctions(expression);
- answer = eval(dhisfunctionsevaluated);
- }
- return answer;
- };
-
-
- VariableService.getVariables(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity).then(function(variablesReceived){
- TrackerRulesFactory.getProgramStageRules(programid, executingEvent.programStage).then(function(rules){
- //But run rules in priority - lowest number first(priority null is last)
- rules = orderByFilter(rules, 'priority');
-
- variablesHash = variablesReceived;
-
- if(angular.isObject(rules) && angular.isArray(rules)){
- //The program has rules, and we want to run them.
- //Prepare repository unless it is already prepared:
- if(angular.isUndefined( $rootScope.ruleeffects ) ) {
- $rootScope.ruleeffects = {};
- }
-
- if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event] )){
- $rootScope.ruleeffects[executingEvent.event] = {};
- }
-
- var updatedEffectsExits = false;
-
- angular.forEach(rules, function(rule) {
- var ruleEffective = false;
-
- var expression = rule.condition;
- //Go through and populate variables with actual values, but only if there actually is any replacements to be made(one or more "$" is present)
- if(expression) {
- if(expression.indexOf('#') !== -1) {
- expression = replaceVariables(expression);
- }
- //run expression:
- ruleEffective = runExpression(expression, rule.condition, "rule:" + rule.id);
- } else {
- $log.warn("Rule id:'" + rule.id + "'' and name:'" + rule.name + "' had no condition specified. Please check rule configuration.");
- }
-
- angular.forEach(rule.actions, function(action){
- //In case the effect-hash is not populated, add entries
- if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event][action.id] )){
- $rootScope.ruleeffects[executingEvent.event][action.id] = {
- id:action.id,
- location:action.location,
- action:action.programRuleActionType,
- dataElement:action.dataElement,
- content:action.content,
- data:action.data,
- ineffect:false
- };
- }
-
- //In case the rule is effective and contains specific data,
- //the effect be refreshed from the variables list.
- //If the rule is not effective we can skip this step
- if(ruleEffective && action.data)
- {
- //The key data might be containing a dollar sign denoting that the key data is a variable.
- //To make a lookup in variables hash, we must make a lookup without the dollar sign in the variable name
- //The first strategy is to make a direct lookup. In case the "data" expression is more complex, we have to do more replacement and evaluation.
-
- var nameWithoutBrackets = action.data.replace('#{','').replace('}','');
- if(angular.isDefined(variablesHash[nameWithoutBrackets]))
- {
- //The variable exists, and is replaced with its corresponding value
- $rootScope.ruleeffects[executingEvent.event][action.id].data =
- variablesHash[nameWithoutBrackets].variableValue;
- }
- else if(action.data.indexOf('#') !== -1)
- {
- //Since the value couldnt be looked up directly, and contains a dollar sign, the expression was more complex
- //Now we will have to make a thorough replacement and separate evaluation to find the correct value:
- $rootScope.ruleeffects[executingEvent.event][action.id].data = replaceVariables(action.data);
- //In a scenario where the data contains a complex expression, evaluate the expression to compile(calculate) the result:
- $rootScope.ruleeffects[executingEvent.event][action.id].data = runExpression($rootScope.ruleeffects[executingEvent.event][action.id].data, action.data, "action:" + action.id);
- }
- }
-
- //Update the rule effectiveness if it changed in this evaluation;
- if($rootScope.ruleeffects[executingEvent.event][action.id].ineffect !== ruleEffective)
- {
- //There is a change in the rule outcome, we need to update the effect object.
- updatedEffectsExits = true;
- $rootScope.ruleeffects[executingEvent.event][action.id].ineffect = ruleEffective;
- }
-
- //In case the rule is of type "assign variable" and the rule is effective,
- //the variable data result needs to be applied to the correct variable:
- if($rootScope.ruleeffects[executingEvent.event][action.id].action === "ASSIGNVARIABLE" && $rootScope.ruleeffects[executingEvent.event][action.id].ineffect){
- //from earlier evaluation, the data portion of the ruleeffect now contains the value of the variable to be assign.
- //the content portion of the ruleeffect defines the name for the variable, when dollar is removed:
- var variabletoassign = $rootScope.ruleeffects[executingEvent.event][action.id].content.replace("#{","").replace("}","");
-
- if(!angular.isDefined(variablesHash[variabletoassign])){
- $log.warn("Variable " + variabletoassign + " was not defined.");
- }
-
- //Even if the variable is not defined: we assign it:
- if(variablesHash[variabletoassign].variableValue !== $rootScope.ruleeffects[executingEvent.event][action.id].data){
- //If the variable was actually updated, we assume that there is an updated ruleeffect somewhere:
- updatedEffectsExits = true;
- //Then we assign the new value:
- variablesHash[variabletoassign].variableValue = $rootScope.ruleeffects[executingEvent.event][action.id].data;
- }
- }
- });
- });
-
- //Broadcast rules finished if there was any actual changes to the event.
- if(updatedEffectsExits){
- $rootScope.$broadcast("ruleeffectsupdated", { event: executingEvent.event });
- }
- }
-
- return true;
- });
- });
- }
- };
});
=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js 2015-06-15 15:29:59 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js 2015-06-16 11:22:51 +0000
@@ -707,4 +707,467 @@
return colExists;
}
};
+})
+
+/* service for building variables based on the data in users fields */
+.service('VariableService', function($rootScope,$q,TrackerRuleVariableFactory,$filter,orderByFilter,$log){
+ return {
+ getVariables: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) {
+ var thePromisedVariables = $q.defer();
+ var variables = {};
+
+ var pushVariable = function(variablename, variableValue, variableType, variablefound) {
+ //First clean away single or double quotation marks at the start and end of the variable name.
+ variableValue = $filter('trimquotes')(variableValue);
+
+ //Append single quotation marks in case the variable is of text type:
+ if(variableType === 'string') {
+ variableValue = "'" + variableValue + "'";
+ }
+ else if(variableType === 'date') {
+ variableValue = "'" + variableValue + "'";
+ }
+ else if(variableType === 'bool' || variableType === 'trueOnly') {
+ if(eval(variableValue)) {
+ variableValue = true;
+ }
+ else {
+ variableValue = false;
+ }
+ }
+ else if(variableType === "int" || variableType === "number") {
+ variableValue = Number(variableValue);
+ }
+ else{
+ $log.warn("unknown datatype:" + variableType);
+ }
+
+
+ //Make sure that the variableValue does not contain a dollar sign anywhere
+ //- this would potentially mess up later use of the variable:
+// if(angular.isDefined(variableValue)
+// && variableValue !== null
+// && variableValue.indexOf("$") !== -1 ) {
+// variableValue = variableValue.replace(/\\$/,"");
+// }
+
+ //TODO:
+ //Also clean away instructions that might be erroneusly evalutated in javascript
+
+ variables[variablename] = {
+ variableValue:variableValue,
+ variableType:variableType,
+ hasValue:variablefound
+ };
+ };
+
+ TrackerRuleVariableFactory.getProgramRuleVariables(programid).then(function(programVariables){
+
+ // The following section will need a different implementation for event capture:
+ var allEventsSorted = [];
+ var currentEvent = executingEvent;
+ var eventsSortedPerProgramStage = [];
+
+ for(var key in allEventsByStage){
+ if(allEventsByStage.hasOwnProperty(key)){
+ eventsSortedPerProgramStage[key] = [];
+ angular.forEach(allEventsByStage[key], function(event){
+ allEventsSorted.push(event);
+ eventsSortedPerProgramStage[key].push(event);
+ });
+ eventsSortedPerProgramStage[key] = orderByFilter(eventsSortedPerProgramStage[key], '-sortingDate').reverse();
+ }
+ }
+ allEventsSorted = orderByFilter(allEventsSorted, '-sortingDate').reverse();
+
+ var allDes = allDataElements;
+// angular.forEach($scope.programStages, function(programStage){
+// angular.forEach(programStage.programStageDataElements, function(dataElement) {
+// allDes[dataElement.dataElement.id] = dataElement;
+// });
+// });
+ //End of region that neeeds specific implementation for event capture
+
+ angular.forEach(programVariables, function(programVariable) {
+ var valueFound = false;
+ if(programVariable.programRuleVariableSourceType === "DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE"){
+ if(programVariable.programStage) {
+ angular.forEach(eventsSortedPerProgramStage[programVariable.programStage.id], function(event) {
+ if(angular.isDefined(event[programVariable.dataElement.id])
+ && event[programVariable.dataElement.id] !== null ){
+ valueFound = true;
+ pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound );
+ }
+ });
+ } else {
+ $log.warn("Variable id:'" + programVariable.id + "' name:'" + programVariable.name
+ + "' does not have a programstage defined,"
+ + " despite that the variable has sourcetype DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE" );
+ }
+
+ }
+ else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_NEWEST_EVENT_PROGRAM"){
+ angular.forEach(allEventsSorted, function(event) {
+ if(angular.isDefined(event[programVariable.dataElement.id])
+ && event[programVariable.dataElement.id] !== null ){
+ valueFound = true;
+ pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound );
+ }
+ });
+ }
+ else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_CURRENT_EVENT"){
+ if(angular.isDefined(currentEvent[programVariable.dataElement.id])
+ && currentEvent[programVariable.dataElement.id] !== null ){
+ valueFound = true;
+ pushVariable(programVariable.name, currentEvent[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound );
+ }
+ }
+ else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_PREVIOUS_EVENT"){
+ //Only continue checking for a value if there is more than one event.
+ if(allEventsSorted && allEventsSorted.length > 1) {
+ var previousvalue = null;
+ var currentEventPassed = false;
+ for(var i = 0; i < allEventsSorted.length; i++) {
+ //Store the values as we iterate through the stages
+ //If the event[i] is not the current event, it is older(previous). Store the previous value if it exists
+ if(!currentEventPassed && allEventsSorted[i] !== currentEvent &&
+ angular.isDefined(allEventsSorted[i][programVariable.dataElement.id])) {
+ previousvalue = allEventsSorted[i][programVariable.dataElement.id];
+ valueFound = true;
+ }
+ else if(allEventsSorted[i] === currentEvent) {
+ //We have iterated to the newest event - store the last collected variable value - if any is found:
+ if(valueFound) {
+ pushVariable(programVariable.name, previousvalue, allDes[programVariable.dataElement.id].dataElement.type, valueFound );
+ }
+ //Set currentEventPassed, ending the iteration:
+ currentEventPassed = true;
+ }
+ }
+ }
+ }
+ else if(programVariable.programRuleVariableSourceType === "TEI_ATTRIBUTE"){
+ angular.forEach(selectedEntity.attributes , function(attribute) {
+ if(!valueFound) {
+ if(attribute.attribute === programVariable.trackedEntityAttribute.id) {
+ valueFound = true;
+ pushVariable(programVariable.name, attribute.value, attribute.type, valueFound );
+ }
+ }
+ });
+ }
+ else if(programVariable.programRuleVariableSourceType === "CALCULATED_VALUE"){
+ //We won't assign the calculated variables at this step. The rules execution will calculate and assign the variable.
+ }
+ else if(programVariable.programRuleVariableSourceType === "NUMBEROFEVENTS_PROGRAMSTAGE"){
+ var numberOfEvents = 0;
+ if( programVariable.programStage && eventsSortedPerProgramStage[programVariable.programStage.id] ) {
+ numberOfEvents = eventsSortedPerProgramStage[programVariable.programStage.id].length;
+ }
+ valueFound = true;
+ pushVariable(programVariable.name, numberOfEvents, 'int', valueFound );
+ }
+ else {
+ //Missing handing of ruletype
+ $log.warn("Unknown programRuleVariableSourceType:" + programVariable.programRuleVariableSourceType);
+ }
+
+
+ if(!valueFound){
+ //If there is still no value found, assign default value:
+ if(programVariable.dataElement) {
+ var dataElement = allDes[programVariable.dataElement.id];
+ if( dataElement ) {
+ pushVariable(programVariable.name, "", dataElement.dataElement.type );
+ }
+ else {
+ $log.warn("Variable #{" + programVariable.name + "} is linked to a dataelement that is not part of the program");
+ pushVariable(programVariable.name, "", "string" );
+ }
+ }
+ else {
+ pushVariable(programVariable.name, "", "string" );
+ }
+ }
+ });
+
+ //add context variables:
+ //last parameter "valuefound" is always true for event date
+ pushVariable('eventdate', executingEvent.eventDate, 'date', true );
+
+ thePromisedVariables.resolve(variables);
+ });
+
+ return thePromisedVariables.promise;
+ }
+ };
+})
+
+/* service for executing tracker rules and broadcasting results */
+.service('TrackerRulesExecutionService', function(TrackerRulesFactory,VariableService, $rootScope, $log, $filter, orderByFilter){
+ return {
+ executeRules: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) {
+ //When debugging rules, the caller should provide a variable for wether or not the rules is being debugged.
+ //hard coding this for now:
+ var debug = true;
+ var verbose = true;
+
+ var variablesHash = {};
+
+ var replaceVariables = function(expression) {
+ //replaces the variables in an expression with actual variable values.
+ //First check if the expression contains variables at all(any dollar signs):
+ if(expression.indexOf('#') !== -1) {
+ //Find every variable name in the expression;
+ var variablespresent = expression.match(/#{\w+}/g);
+ //Replace each matched variable:
+ angular.forEach(variablespresent, function(variablepresent) {
+ //First strip away any dollar signs from the variable name:
+ variablepresent = variablepresent.replace("#{","").replace("}","");
+
+ if(angular.isDefined(variablesHash[variablepresent])) {
+ //Replace all occurrences of the variable name(hence using regex replacement):
+ expression = expression.replace(new RegExp("#{" + variablepresent + "}", 'g'),
+ variablesHash[variablepresent].variableValue);
+ }
+ else {
+ $log.warn("Expression " + expression + " conains variable " + variablepresent
+ + " - but this variable is not defined." );
+ }
+
+ });
+ }
+ return expression;
+ };
+
+ var runDhisFunctions = function(expression) {
+ //Called from "runExpression". Only proceed with this logic in case there seems to be dhis function calls: "dhis." is present.
+ if(angular.isDefined(expression) && expression.indexOf("dhis.") !== -1){
+ var dhisFunctions = [{name:"dhis.daysbetween",parameters:2},
+ {name:"dhis.floor",parameters:1},
+ {name:"dhis.modulus",parameters:2},
+ {name:"dhis.hasValue",parameters:1},
+ {name:"dhis.concatenate"}];
+
+ angular.forEach(dhisFunctions, function(dhisFunction){
+ //Replace each * with a regex that matches each parameter, allowing commas only inside single quotation marks.
+ var regularExFunctionCall = new RegExp(dhisFunction.name.replace(".","\\.") + "\\([^\\)]*\\)",'g');
+ var callsToThisFunction = expression.match(regularExFunctionCall);
+ angular.forEach(callsToThisFunction, function(callToThisFunction){
+ //Remove the function name and paranthesis:
+ var justparameters = callToThisFunction.replace(/(^[^\(]+\()|\)$/g,"");
+ //Then split into single parameters:
+ var parameters = justparameters.match(/(('[^']+')|([^,]+))/g);
+
+ //Show error if no parameters is given and the function requires parameters,
+ //or if the number of parameters is wrong.
+ if(angular.isDefined(dhisFunction.parameters)){
+ //But we are only checking parameters where the dhisFunction actually has a defined set of parameters(concatenate, for example, does not have a fixed number);
+ if((!angular.isDefined(parameters) && dhisFunction.parameters > 0)
+ || parameters.length !== dhisFunction.parameters){
+ $log.warn(dhisFunction.name + " was called with the incorrect number of parameters");
+ }
+ }
+
+ //In case the function call is nested, the parameter itself contains an expression, run the expression.
+ if(angular.isDefined(parameters)) {
+ for (var i = 0; i < parameters.length; i++) {
+ parameters[i] = runExpression(parameters[i],dhisFunction.name,"parameter:" + i);
+ }
+ }
+
+ //Special block for dhis.weeksBetween(*,*) - add such a block for all other dhis functions.
+ if(dhisFunction.name === "dhis.daysbetween")
+ {
+ var firstdate = $filter('trimquotes')(parameters[0]);
+ var seconddate = $filter('trimquotes')(parameters[1]);
+ firstdate = moment(firstdate);
+ seconddate = moment(seconddate);
+ //Replace the end evaluation of the dhis function:
+ expression = expression.replace(callToThisFunction, seconddate.diff(firstdate,'days'));
+ }
+ else if(dhisFunction.name === "dhis.floor")
+ {
+ var floored = Math.floor(parameters[0]);
+ //Replace the end evaluation of the dhis function:
+ expression = expression.replace(callToThisFunction, floored);
+ }
+ else if(dhisFunction.name === "dhis.modulus")
+ {
+ var dividend = Number(parameters[0]);
+ var divisor = Number(parameters[1]);
+ var rest = dividend % divisor;
+ //Replace the end evaluation of the dhis function:
+ expression = expression.replace(callToThisFunction, rest);
+ }
+ else if(dhisFunction.name === "dhis.hasValue")
+ {
+ //"evaluate" hasvalue to true or false:
+ if(variablesHash[parameters[0]].hasValue){
+ expression = expression.replace(callToThisFunction, 'true');
+ } else {
+ expression = expression.replace(callToThisFunction, 'false');
+ }
+ }
+ else if(dhisFunction.name === "dhis.concatenate")
+ {
+ var returnString = "'";
+ for (var i = 0; i < parameters.length; i++) {
+ returnString += parameters[i];
+ }
+ returnString += "'";
+ expression = expression.replace(callToThisFunction, returnString);
+ }
+ });
+ });
+ }
+
+ return expression;
+ };
+
+ var runExpression = function(expression, beforereplacement, identifier ){
+ //determine if expression is true, and actions should be effectuated
+ //If DEBUG mode, use try catch and report errors. If not, omit the heavy try-catch loop.:
+ var answer = false;
+ if(debug) {
+ try{
+
+ var dhisfunctionsevaluated = runDhisFunctions(expression);
+ answer = eval(dhisfunctionsevaluated);
+
+ if(verbose)
+ {
+ $log.info("Expression with id " + identifier + " was successfully run. Original condition was: " + beforereplacement + " - Evaluation ended up as:" + expression + " - Result of evaluation was:" + answer);
+ }
+ }
+ catch(e)
+ {
+ $log.warn("Expression with id " + identifier + " could not be run. Original condition was: " + beforereplacement + " - Evaluation ended up as:" + expression + " - error message:" + e);
+ }
+ }
+ else {
+ //Just run the expression. This is much faster than the debug route: http://jsperf.com/try-catch-block-loop-performance-comparison
+ var dhisfunctionsevaluated = runDhisFunctions(expression);
+ answer = eval(dhisfunctionsevaluated);
+ }
+ return answer;
+ };
+
+
+ VariableService.getVariables(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity).then(function(variablesReceived){
+ TrackerRulesFactory.getProgramStageRules(programid, executingEvent.programStage).then(function(rules){
+ //But run rules in priority - lowest number first(priority null is last)
+ rules = orderByFilter(rules, 'priority');
+
+ variablesHash = variablesReceived;
+
+ if(angular.isObject(rules) && angular.isArray(rules)){
+ //The program has rules, and we want to run them.
+ //Prepare repository unless it is already prepared:
+ if(angular.isUndefined( $rootScope.ruleeffects ) ) {
+ $rootScope.ruleeffects = {};
+ }
+
+ if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event] )){
+ $rootScope.ruleeffects[executingEvent.event] = {};
+ }
+
+ var updatedEffectsExits = false;
+
+ angular.forEach(rules, function(rule) {
+ var ruleEffective = false;
+
+ var expression = rule.condition;
+ //Go through and populate variables with actual values, but only if there actually is any replacements to be made(one or more "$" is present)
+ if(expression) {
+ if(expression.indexOf('#') !== -1) {
+ expression = replaceVariables(expression);
+ }
+ //run expression:
+ ruleEffective = runExpression(expression, rule.condition, "rule:" + rule.id);
+ } else {
+ $log.warn("Rule id:'" + rule.id + "'' and name:'" + rule.name + "' had no condition specified. Please check rule configuration.");
+ }
+
+ angular.forEach(rule.actions, function(action){
+ //In case the effect-hash is not populated, add entries
+ if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event][action.id] )){
+ $rootScope.ruleeffects[executingEvent.event][action.id] = {
+ id:action.id,
+ location:action.location,
+ action:action.programRuleActionType,
+ dataElement:action.dataElement,
+ content:action.content,
+ data:action.data,
+ ineffect:false
+ };
+ }
+
+ //In case the rule is effective and contains specific data,
+ //the effect be refreshed from the variables list.
+ //If the rule is not effective we can skip this step
+ if(ruleEffective && action.data)
+ {
+ //The key data might be containing a dollar sign denoting that the key data is a variable.
+ //To make a lookup in variables hash, we must make a lookup without the dollar sign in the variable name
+ //The first strategy is to make a direct lookup. In case the "data" expression is more complex, we have to do more replacement and evaluation.
+
+ var nameWithoutBrackets = action.data.replace('#{','').replace('}','');
+ if(angular.isDefined(variablesHash[nameWithoutBrackets]))
+ {
+ //The variable exists, and is replaced with its corresponding value
+ $rootScope.ruleeffects[executingEvent.event][action.id].data =
+ variablesHash[nameWithoutBrackets].variableValue;
+ }
+ else if(action.data.indexOf('#') !== -1)
+ {
+ //Since the value couldnt be looked up directly, and contains a dollar sign, the expression was more complex
+ //Now we will have to make a thorough replacement and separate evaluation to find the correct value:
+ $rootScope.ruleeffects[executingEvent.event][action.id].data = replaceVariables(action.data);
+ //In a scenario where the data contains a complex expression, evaluate the expression to compile(calculate) the result:
+ $rootScope.ruleeffects[executingEvent.event][action.id].data = runExpression($rootScope.ruleeffects[executingEvent.event][action.id].data, action.data, "action:" + action.id);
+ }
+ }
+
+ //Update the rule effectiveness if it changed in this evaluation;
+ if($rootScope.ruleeffects[executingEvent.event][action.id].ineffect !== ruleEffective)
+ {
+ //There is a change in the rule outcome, we need to update the effect object.
+ updatedEffectsExits = true;
+ $rootScope.ruleeffects[executingEvent.event][action.id].ineffect = ruleEffective;
+ }
+
+ //In case the rule is of type "assign variable" and the rule is effective,
+ //the variable data result needs to be applied to the correct variable:
+ if($rootScope.ruleeffects[executingEvent.event][action.id].action === "ASSIGNVARIABLE" && $rootScope.ruleeffects[executingEvent.event][action.id].ineffect){
+ //from earlier evaluation, the data portion of the ruleeffect now contains the value of the variable to be assign.
+ //the content portion of the ruleeffect defines the name for the variable, when dollar is removed:
+ var variabletoassign = $rootScope.ruleeffects[executingEvent.event][action.id].content.replace("#{","").replace("}","");
+
+ if(!angular.isDefined(variablesHash[variabletoassign])){
+ $log.warn("Variable " + variabletoassign + " was not defined.");
+ }
+
+ //Even if the variable is not defined: we assign it:
+ if(variablesHash[variabletoassign].variableValue !== $rootScope.ruleeffects[executingEvent.event][action.id].data){
+ //If the variable was actually updated, we assume that there is an updated ruleeffect somewhere:
+ updatedEffectsExits = true;
+ //Then we assign the new value:
+ variablesHash[variabletoassign].variableValue = $rootScope.ruleeffects[executingEvent.event][action.id].data;
+ }
+ }
+ });
+ });
+
+ //Broadcast rules finished if there was any actual changes to the event.
+ if(updatedEffectsExits){
+ $rootScope.$broadcast("ruleeffectsupdated", { event: executingEvent.event });
+ }
+ }
+
+ return true;
+ });
+ });
+ }
+ };
});