=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/event-capture.js' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/event-capture.js 2015-06-18 15:15:29 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/event-capture.js 2015-06-21 22:18:40 +0000 @@ -485,7 +485,7 @@ function getProgramIndicators( programIndicators ) { - return checkAndGetD2Objects( programIndicators, 'programIndicators', '../api/programIndicators', 'fields=id,name,code,shortName,expression,displayDescription,rootDate,description,valueType,DisplayName,program[id]'); + return checkAndGetD2Objects( programIndicators, 'programIndicators', '../api/programIndicators', 'fields=id,name,code,shortName,expression,displayDescription,rootDate,description,valueType,DisplayName,filter,program[id]'); } function getMetaProgramRules( programs ) === modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/services.js' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/services.js 2015-06-18 15:15:29 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/services.js 2015-06-21 22:18:40 +0000 @@ -174,55 +174,6 @@ }; }) -/* factory to fetch and process programValidations */ -.factory('MetaDataFactory', function($q, $rootScope, ECStorageService) { - - return { - get: function(store, uid){ - - var def = $q.defer(); - - ECStorageService.currentStore.open().done(function(){ - ECStorageService.currentStore.get(store, uid).done(function(pv){ - $rootScope.$apply(function(){ - def.resolve(pv); - }); - }); - }); - return def.promise; - }, - getByProgram: function(store, program){ - var def = $q.defer(); - var obj = []; - - ECStorageService.currentStore.open().done(function(){ - ECStorageService.currentStore.getAll(store, program).done(function(pvs){ - angular.forEach(pvs, function(pv){ - if(pv.program.id === program){ - obj.push(pv); - } - }); - $rootScope.$apply(function(){ - def.resolve(obj); - }); - }); - }); - return def.promise; - }, - getAll: function(store){ - var def = $q.defer(); - ECStorageService.currentStore.open().done(function(){ - ECStorageService.currentStore.getAll(store).done(function(pvs){ - $rootScope.$apply(function(){ - def.resolve(pvs); - }); - }); - }); - return def.promise; - } - }; -}) - /* factory for handling events */ .factory('DHIS2EventFactory', function($http, $q, ECStorageService, $rootScope) { === modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/dashboard/dashboard-controller.js' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/dashboard/dashboard-controller.js 2015-06-11 18:44:52 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/dashboard/dashboard-controller.js 2015-06-21 22:18:40 +0000 @@ -78,6 +78,11 @@ $rootScope.dashboardWidgets.push($rootScope.enrollmentWidget); $scope.dashboardStatus[widget.title] = angular.copy(widget); break; + case 'indicators': + $rootScope.indicatorWidget = widget; + $rootScope.dashboardWidgets.push($rootScope.indicatorWidget); + $scope.dashboardStatus[widget.title] = angular.copy(widget); + break; case 'dataentry': $rootScope.dataentryWidget = widget; $rootScope.dashboardWidgets.push($rootScope.dataentryWidget); === modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/dataentry/dataentry-controller.js' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/dataentry/dataentry-controller.js 2015-06-15 15:29:59 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/dataentry/dataentry-controller.js 2015-06-21 22:18:40 +0000 @@ -117,10 +117,10 @@ //If the events is displayed in a table, it is necessary to run the rules for all visible events. if($scope.currentStage.displayEventsInTable) { angular.forEach($scope.currentStageEvents, function(event) { - TrackerRulesExecutionService.executeRules($scope.selectedProgramId,event,$scope.eventsByStage,$scope.prStDes,$scope.selectedTei); + TrackerRulesExecutionService.executeRules($scope.selectedProgramId,event,$scope.eventsByStage,$scope.prStDes,$scope.selectedTei,$scope.selectedEnrollment); }); } else { - TrackerRulesExecutionService.executeRules($scope.selectedProgramId,$scope.currentEvent,$scope.eventsByStage,$scope.prStDes,$scope.selectedTei); + TrackerRulesExecutionService.executeRules($scope.selectedProgramId,$scope.currentEvent,$scope.eventsByStage,$scope.prStDes,$scope.selectedTei,$scope.selectedEnrollment); } }; === modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/i18n_app.properties' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/i18n_app.properties 2015-06-15 15:29:59 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/i18n_app.properties 2015-06-21 22:18:40 +0000 @@ -138,6 +138,7 @@ search_from_existing=Search from existing name=Name dataentry=Data Entry +indicators=Indicators custom_form=Custom form default_form=Default form menu=Menu === modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/i18n_app_ar.properties' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/i18n_app_ar.properties 2015-06-08 08:54:37 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/i18n_app_ar.properties 2015-06-21 22:18:40 +0000 @@ -114,6 +114,7 @@ search_from_existing=\u0627\u0628\u062d\u062b \u0628\u0645\u0639\u064a\u0627\u0631 \u0645\u062a\u0649 \u062a\u0645 \u0627\u0644\u0627\u0646\u0634\u0627\u0621 name=\u0627\u0633\u0645 dataentry=\u0627\u062f\u062e\u0627\u0644 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a +indicators=\u0627\u0644\u0645\u0624\u0634\u0631\u0627\u062a ( \u062c\u0648\u062c\u0644 \u062a\u0631\u062c\u0645\u0629 ) custom_form=\u0627\u0633\u062a\u0645\u0627\u0631\u0629 \u0645\u062e\u0635\u0635\u0629 default_form=\u0627\u0633\u062a\u0645\u0627\u0631\u0629 \u0627\u0641\u062a\u0631\u0627\u0636\u064a\u0629 menu=\u0642\u0627\u0626\u0645\u0629 === 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-17 08:54:51 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js 2015-06-21 22:18:40 +0000 @@ -10,7 +10,7 @@ var store = new dhis2.storage.Store({ name: "dhis2tc", adapters: [dhis2.storage.IndexedDBAdapter, dhis2.storage.DomSessionStorageAdapter, dhis2.storage.InMemoryAdapter], - objectStores: ['programs', 'programStages', 'trackedEntities', 'trackedEntityForms', 'attributes', 'relationshipTypes', 'optionSets', 'programValidations', 'ouLevels', 'programRuleVariables', 'programRules'] + objectStores: ['programs', 'programStages', 'trackedEntities', 'trackedEntityForms', 'attributes', 'relationshipTypes', 'optionSets', 'programValidations', 'ouLevels', 'programRuleVariables', 'programRules','constants'] }); return{ currentStore: store @@ -22,8 +22,9 @@ var w = {}; w.enrollmentWidget = {title: 'enrollment', view: "components/enrollment/enrollment.html", show: true, expand: true, parent: 'biggerWidget', order: 0}; - w.dataentryWidget = {title: 'dataentry', view: "components/dataentry/dataentry.html", show: true, expand: true, parent: 'biggerWidget', order: 1}; - w.reportWidget = {title: 'report', view: "components/report/tei-report.html", show: true, expand: true, parent: 'biggerWidget', order: 2}; + w.indicatorWidget = {title: 'indicators', view: "components/rulebound/rulebound.html", show: true, expand: true, parent: 'biggerWidget', order: 1, code:'indicators'}; + w.dataentryWidget = {title: 'dataentry', view: "components/dataentry/dataentry.html", show: true, expand: true, parent: 'biggerWidget', order: 2}; + w.reportWidget = {title: 'report', view: "components/report/tei-report.html", show: true, expand: true, parent: 'biggerWidget', order: 3}; w.selectedWidget = {title: 'current_selections', view: "components/selected/selected.html", show: false, expand: true, parent: 'smallerWidget', order: 0}; w.profileWidget = {title: 'profile', view: "components/profile/profile.html", show: true, expand: true, parent: 'smallerWidget', order: 1}; w.relationshipWidget = {title: 'relationships', view: "components/relationship/relationship.html", show: true, expand: true, parent: 'smallerWidget', order: 2}; @@ -1061,6 +1062,55 @@ }; }) +/* factory to fetch and process programValidations */ +.factory('MetaDataFactory', function($q, $rootScope, TCStorageService) { + + return { + get: function(store, uid){ + + var def = $q.defer(); + + TCStorageService.currentStore.open().done(function(){ + TCStorageService.currentStore.get(store, uid).done(function(pv){ + $rootScope.$apply(function(){ + def.resolve(pv); + }); + }); + }); + return def.promise; + }, + getByProgram: function(store, program){ + var def = $q.defer(); + var obj = []; + + TCStorageService.currentStore.open().done(function(){ + TCStorageService.currentStore.getAll(store, program).done(function(pvs){ + angular.forEach(pvs, function(pv){ + if(pv.program.id === program){ + obj.push(pv); + } + }); + $rootScope.$apply(function(){ + def.resolve(obj); + }); + }); + }); + return def.promise; + }, + getAll: function(store){ + var def = $q.defer(); + TCStorageService.currentStore.open().done(function(){ + TCStorageService.currentStore.getAll(store).done(function(pvs){ + $rootScope.$apply(function(){ + def.resolve(pvs); + }); + }); + }); + return def.promise; + } + }; +}) + /* Returns a function for getting rules for a specific program */ .factory('TrackerRulesFactory', function($q,$rootScope,TCStorageService){ return{ @@ -1755,5 +1805,6 @@ } return event; } - }; + }; + }); === modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/tracker-capture.js' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/tracker-capture.js 2015-06-17 21:53:57 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/tracker-capture.js 2015-06-21 22:18:40 +0000 @@ -29,7 +29,7 @@ dhis2.tc.store = new dhis2.storage.Store({ name: 'dhis2tc', adapters: [dhis2.storage.IndexedDBAdapter, dhis2.storage.DomSessionStorageAdapter, dhis2.storage.InMemoryAdapter], - objectStores: ['programs', 'programStages', 'trackedEntities', 'trackedEntityForms', 'attributes', 'relationshipTypes', 'optionSets', 'programValidations', 'ouLevels', 'programRuleVariables', 'programRules'] + objectStores: ['programs', 'programStages', 'trackedEntities', 'trackedEntityForms', 'attributes', 'relationshipTypes', 'optionSets', 'programValidations', 'programIndicators', 'ouLevels', 'programRuleVariables', 'programRules','constants'] }); (function($) { @@ -132,6 +132,7 @@ promise = promise.then( dhis2.tc.store.open ); promise = promise.then( getUserRoles ); promise = promise.then( getCalendarSetting ); + promise = promise.then( getConstants ); promise = promise.then( getRelationships ); promise = promise.then( getTrackedEntities ); promise = promise.then( getMetaPrograms ); @@ -146,7 +147,9 @@ promise = promise.then( getMetaProgramRules ); promise = promise.then( getProgramRules ); promise = promise.then( getMetaProgramValidations ); - promise = promise.then( getProgramValidations ); + promise = promise.then( getProgramValidations ); + promise = promise.then( getMetaProgramIndicators ); + promise = promise.then( getProgramIndicators ); promise = promise.then( getTrackedEntityForms ); promise = promise.then( getOrgUnitLevels ); promise.done(function() { @@ -206,6 +209,16 @@ return def.promise(); } +function getConstants() +{ + dhis2.tc.store.getKeys( 'constants').done(function(res){ + if(res.length > 0){ + return; + } + return getD2Objects('constants', 'constants', '../api/constants.json', 'paging=false&fields=id,name,displayName,value'); + }); +} + function getRelationships() { dhis2.tc.store.getKeys( 'relationshipTypes').done(function(res){ @@ -680,7 +693,7 @@ def.resolve(); promise = promise.done( function () { - mainDef.resolve( data ); + mainDef.resolve( data.programs ); } ); }).fail(function(){ mainDef.resolve( null ); @@ -823,6 +836,16 @@ }; } +function getMetaProgramIndicators( programs ) +{ + return getD2MetaObject(programs, 'programIndicators', '../api/programIndicators.json', 'paging=false&fields=id,program[id]'); +} + +function getProgramIndicators( programIndicators ) +{ + return checkAndGetD2Objects( programIndicators, 'programIndicators', '../api/programIndicators', 'fields=id,name,code,shortName,expression,displayDescription,rootDate,description,valueType,DisplayName,filter,program[id]'); +} + function getMetaTrackeEntityAttributes( programs ){ var def = $.Deferred(); @@ -1017,4 +1040,151 @@ return def.promise(); }); +} + +function getD2MetaObject( programs, objNames, url, filter ) +{ + if( !programs ){ + return; + } + + var def = $.Deferred(); + + var programIds = []; + _.each( _.values( programs ), function ( program ) { + if( program.id ) { + programIds.push( program.id ); + } + }); + + $.ajax({ + url: url, + type: 'GET', + data:filter + }).done( function(response) { + var objs = []; + _.each( _.values( response[objNames]), function ( o ) { + if( o && + o.id && + o.program && + o.program.id && + programIds.indexOf( o.program.id ) !== -1) { + + objs.push( o ); + } + + }); + + def.resolve( {programs: programs, self: objs} ); + + }).fail(function(){ + def.resolve( null ); + }); + + return def.promise(); +} + +function checkAndGetD2Objects( obj, store, url, filter ) +{ + if( !obj || !obj.programs || !obj.self ){ + return; + } + + var mainDef = $.Deferred(); + var mainPromise = mainDef.promise(); + + var def = $.Deferred(); + var promise = def.promise(); + + var builder = $.Deferred(); + var build = builder.promise(); + + _.each( _.values( obj.self ), function ( obj) { + build = build.then(function() { + var d = $.Deferred(); + var p = d.promise(); + dhis2.tc.store.get(store, obj.id).done(function(o) { + if(!o) { + promise = promise.then( getD2Object( obj.id, store, url, filter, 'idb' ) ); + } + d.resolve(); + }); + + return p; + }); + }); + + build.done(function() { + def.resolve(); + promise = promise.done( function () { + mainDef.resolve( obj.programs ); + } ); + }).fail(function(){ + mainDef.resolve( null ); + }); + + builder.resolve(); + + return mainPromise; +} + +function getD2Objects(store, objs, url, filter) +{ + var def = $.Deferred(); + + $.ajax({ + url: url, + type: 'GET', + data: filter + }).done(function(response) { + if(response[objs]){ + dhis2.tc.store.setAll( store, response[objs] ); + } + def.resolve(); + }).fail(function(){ + def.resolve(); + }); + + return def.promise(); +} + + +function getD2Object( id, store, url, filter, storage ) +{ + return function() { + if(id){ + url = url + '/' + id + '.json'; + } + return $.ajax( { + url: url, + type: 'GET', + data: filter + }).done( function( response ){ + if(storage === 'idb'){ + if( response && response.id) { + dhis2.tc.store.set( store, response ); + } + } + if(storage === 'localStorage'){ + localStorage[store] = JSON.stringify(response); + } + if(storage === 'sessionStorage'){ + var SessionStorageService = angular.element('body').injector().get('SessionStorageService'); + SessionStorageService.set(store, response); + } + }); + }; +} + +function uploadLocalData() +{ + var OfflineECStorageService = angular.element('body').injector().get('OfflineECStorageService'); + setHeaderWaitMessage(i18n_uploading_data_notification); + + OfflineECStorageService.uploadLocalData().then(function(){ + dhis2.tc.store.removeAll( 'events' ); + log( 'Successfully uploaded local events' ); + setHeaderDelayMessage( i18n_sync_success ); + selection.responseReceived(); //notify angular + }); } \ No newline at end of file === 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-19 06:01:29 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js 2015-06-21 22:18:40 +0000 @@ -710,13 +710,13 @@ }) /* service for building variables based on the data in users fields */ -.service('VariableService', function($rootScope,$q,TrackerRuleVariableFactory,$filter,orderByFilter,$log){ +.service('VariableService', function($rootScope,$q,TrackerRuleVariableFactory,DateUtils,CalendarService,MetaDataFactory,$filter,orderByFilter,$log){ return { - getVariables: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) { + getVariables: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity, variablesFromIndicators, selectedEnrollment) { var thePromisedVariables = $q.defer(); var variables = {}; - var pushVariable = function(variablename, variableValue, variableType, variablefound) { + var pushVariable = function(variablename, variableValue, variableType, variablefound, variablePrefix) { //First clean away single or double quotation marks at the start and end of the variable name. variableValue = $filter('trimquotes')(variableValue); @@ -745,138 +745,161 @@ variables[variablename] = { variableValue:variableValue, variableType:variableType, - hasValue:variablefound + hasValue:variablefound, + variablePrefix:variablePrefix }; }; - - TrackerRuleVariableFactory.getProgramRuleVariables(programid).then(function(programVariables){ - - 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(); + MetaDataFactory.getAll('constants').then(function(constants) { + TrackerRuleVariableFactory.getProgramRuleVariables(programid).then(function(programVariables){ + + programVariables = programVariables.concat(variablesFromIndicators); + + 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(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 ){ + allEventsSorted = orderByFilter(allEventsSorted, '-sortingDate').reverse(); + + var allDes = allDataElements; + + angular.forEach(programVariables, function(programVariable) { + var dataElementId = programVariable.dataElement; + if(programVariable.dataElement.id) { + dataElementId = programVariable.dataElement.id; + } + + var programStageId = programVariable.programStage; + if(programVariable.programStage.id) { + programStageId = programVariable.programStage.id; + } + + var valueFound = false; + if(programVariable.programRuleVariableSourceType === "DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE"){ + if(programVariable.programStage) { + angular.forEach(eventsSortedPerProgramStage[programStageId], function(event) { + if(angular.isDefined(event[dataElementId]) + && event[dataElementId] !== null ){ + valueFound = true; + pushVariable(programVariable.name, event[dataElementId], allDes[dataElementId].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[dataElementId]) + && event[dataElementId] !== null ){ valueFound = true; - pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); - } + pushVariable(programVariable.name, event[dataElementId], allDes[dataElementId].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 ){ + else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_CURRENT_EVENT"){ + if(angular.isDefined(currentEvent[dataElementId]) + && currentEvent[dataElementId] !== 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 ){ + pushVariable(programVariable.name, currentEvent[dataElementId], allDes[dataElementId].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][dataElementId])) { + previousvalue = allEventsSorted[i][dataElementId]; + 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[dataElementId].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[programStageId] ) { + numberOfEvents = eventsSortedPerProgramStage[programStageId].length; + } 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 ); - } + 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[dhis2.tc]; + if( dataElement ) { + pushVariable(programVariable.name, "", dataElement.dataElement.type, false, '#' ); + } + else { + $log.warn("Variable #{" + programVariable.name + "} is linked to a dataelement that is not part of the program"); + pushVariable(programVariable.name, "", "string",false, '#' ); + } + } else { - $log.warn("Variable #{" + programVariable.name + "} is linked to a dataelement that is not part of the program"); - pushVariable(programVariable.name, "", "string" ); + pushVariable(programVariable.name, "", "string",false, '#' ); } } - 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); + }); + + //add context variables: + //last parameter "valuefound" is always true for event date + pushVariable('incident_date', executingEvent.eventDate, 'date', true, 'V' ); + pushVariable('enrollment_date', selectedEnrollment.dateOfEnrollment, 'date', true, 'V' ); + pushVariable('current_date', DateUtils.getToday(), 'date', true, 'V' ); + //pushVariable('value_count', executingEvent.eventDate, 'date', true, 'V' ); + //pushVariable('zero_pos_value_count', executingEvent.eventDate, 'date', true, 'V' ); + + //Push all constant values: + angular.forEach(constants, function(constant){ + pushVariable(constant.id, constant.value, 'int', true, 'C' ); + }); + + thePromisedVariables.resolve(variables); + }); }); return thePromisedVariables.promise; @@ -885,28 +908,29 @@ }) /* service for executing tracker rules and broadcasting results */ -.service('TrackerRulesExecutionService', function(TrackerRulesFactory,VariableService, $rootScope, $log, $filter, orderByFilter){ +.service('TrackerRulesExecutionService', function(TrackerRulesFactory, MetaDataFactory, VariableService, $rootScope, $log, $q, $filter, orderByFilter){ return { - executeRules: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) { + executeRules: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity, selectedEnrollment ) { //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) { + if(expression.indexOf('#{') !== -1) { //Find every variable name in the expression; - var variablespresent = expression.match(/#{\w+}/g); + var variablespresent = expression.match(/#{\w+.?\w*}/g); //Replace each matched variable: angular.forEach(variablespresent, function(variablepresent) { - //First strip away any dollar signs from the variable name: + //First strip away any prefix and postfix signs from the variable name: variablepresent = variablepresent.replace("#{","").replace("}",""); - if(angular.isDefined(variablesHash[variablepresent])) { + if(angular.isDefined(variablesHash[variablepresent]) && + variablesHash[variablepresent].variablePrefix === '#') { //Replace all occurrences of the variable name(hence using regex replacement): expression = expression.replace(new RegExp("#{" + variablepresent + "}", 'g'), variablesHash[variablepresent].variableValue); @@ -914,8 +938,49 @@ else { $log.warn("Expression " + expression + " conains variable " + variablepresent + " - but this variable is not defined." ); - } - + } + }); + } + + if(expression.indexOf('V{') !== -1) { + //Find every variable name in the expression; + var variablespresent = expression.match(/V{\w+.?\w*}/g); + //Replace each matched variable: + angular.forEach(variablespresent, function(variablepresent) { + //First strip away any prefix and postfix signs from the variable name: + variablepresent = variablepresent.replace("V{","").replace("}",""); + + if(angular.isDefined(variablesHash[variablepresent]) && + variablesHash[variablepresent].variablePrefix === 'V') { + //Replace all occurrences of the variable name(hence using regex replacement): + expression = expression.replace(new RegExp("V{" + variablepresent + "}", 'g'), + variablesHash[variablepresent].variableValue); + } + else { + $log.warn("Expression " + expression + " conains context variable " + variablepresent + + " - but this variable is not defined." ); + } + }); + } + + if(expression.indexOf('C{') !== -1) { + //Find every constant in the expression; + var variablespresent = expression.match(/C{\w+.?\w*}/g); + //Replace each matched variable: + angular.forEach(variablespresent, function(variablepresent) { + //First strip away any prefix and postfix signs from the variable name: + variablepresent = variablepresent.replace("C{","").replace("}",""); + + if(angular.isDefined(variablesHash[variablepresent]) && + variablesHash[variablepresent].variablePrefix === 'C') { + //Replace all occurrences of the variable name(hence using regex replacement): + expression = expression.replace(new RegExp("C{" + variablepresent + "}", 'g'), + variablesHash[variablepresent].variableValue); + } + else { + $log.warn("Expression " + expression + " conains constant " + variablepresent + + " - but this constant is not defined." ); + } }); } return expression; @@ -1034,119 +1099,196 @@ 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.programRuleActions, 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:undefined - }; - } - - //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: + var getIndicatorRules = function (programid) { + var def = $q.defer(); + MetaDataFactory.getByProgram('programIndicators', programid).then(function(pis){ + var variables = []; + + var programRules = []; + + angular.forEach(pis, function(pi){ + var newAction = { + id:pi.id, + content:pi.displayDescription, + data:pi.expression, + programRuleActionType:'DISPLAYKEYVALUEPAIR', + location:'indicators' + }; + var newRule = { + name:pi.name, + shortname:pi.shortname, + code:pi.code, + program:pi.program, + description:pi.description, + condition:pi.filter ? pi.filter : 'true', + programRuleActions: [newAction] + }; + + programRules.push(newRule); + + var variablesInCondition = newRule.condition.match(/#{\w+.?\w*}/g); + var variablesInData = newAction.data.match(/#{\w+.?\w*}/g); + + var pushDirectAddressedVariable = function(variableWithCurls) { + var variableName = variableWithCurls.replace("#{","").replace("}",""); + var variableNameParts = variableName.split('.'); + + + if(variableNameParts.length === 2) { + //this is a programstage and dataelement specification. translate to program variable: + variables.push({ + name:variableName, + programRuleVariableSourceType:'DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE', + dataElement:variableNameParts[1], + programStage:variableNameParts[0], + program:programid + }); + } + else if(variableNameParts.length === 1) + { + //This is an attribute - let us translate to program variable: + variables.push({ + name:variableName, + programRuleVariableSourceType:'TEI_ATTRIBUTE', + trackedEntityAttribute:variableNameParts[0], + program:programid + }); + } + + }; + + angular.forEach(variablesInCondition, function(variableInCondition) { + pushDirectAddressedVariable(variableInCondition); + }); + + angular.forEach(variablesInData, function(variableInData) { + pushDirectAddressedVariable(variableInData); + }); + }); + + def.resolve({rules:programRules,variables:variables}); + }); + return def.promise; + }; + + + getIndicatorRules(programid).then(function(indicatorRulesAndVariables){ + VariableService.getVariables(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity,indicatorRulesAndVariables.variables, selectedEnrollment).then(function(variablesReceived){ + TrackerRulesFactory.getProgramStageRules(programid, executingEvent.programStage).then(function(rules){ + //Concatenate rules produced by indicator definitions into the other rules: + rules = rules.concat(indicatorRulesAndVariables.rules); + + //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.programRuleActions, 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:undefined + }; + } + + //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; - //Then we assign the new value: - variablesHash[variabletoassign].variableValue = $rootScope.ruleeffects[executingEvent.event][action.id].data; - } - } + $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 }); + //Broadcast rules finished if there was any actual changes to the event. + if(updatedEffectsExits){ + $rootScope.$broadcast("ruleeffectsupdated", { event: executingEvent.event }); + } } - } - return true; + return true; + }); }); }); }