From bcfb3acc56f5ee7fc5e49457f68a8773981498a8 Mon Sep 17 00:00:00 2001 From: 9sneha-n <9sneha.n@gmail.com> Date: Mon, 26 Aug 2024 13:33:04 +0530 Subject: [PATCH] fix: antimicrobial rules + TEA program rules enabled --- src/data/entities/D2Program.ts | 6 +++ src/data/utils/ruleHelper.ts | 47 ++++++++++++++--- .../entities/Questionnaire/Questionnaire.ts | 51 ++++++++++++++----- .../Questionnaire/QuestionnaireQuestion.ts | 33 +++++++++--- .../Questionnaire/QuestionnaireRules.ts | 8 ++- .../Questionnaire/QuestionnaireSection.ts | 3 +- 6 files changed, 119 insertions(+), 29 deletions(-) diff --git a/src/data/entities/D2Program.ts b/src/data/entities/D2Program.ts index ee86a099..02798838 100644 --- a/src/data/entities/D2Program.ts +++ b/src/data/entities/D2Program.ts @@ -108,6 +108,9 @@ export interface D2ProgramRuleVariable { dataElement: { id: Id; }; + trackedEntityAttribute?: { + id: Id; + }; } export interface D2ProgramRuleAction { @@ -116,6 +119,9 @@ export interface D2ProgramRuleAction { dataElement?: { id: Id | undefined; }; + trackedEntityAttribute: { + id: Id | undefined; + }; data?: string; programStageSection?: { id: Id | undefined; diff --git a/src/data/utils/ruleHelper.ts b/src/data/utils/ruleHelper.ts index 4f3f791d..dcebdca4 100644 --- a/src/data/utils/ruleHelper.ts +++ b/src/data/utils/ruleHelper.ts @@ -10,8 +10,10 @@ export const getProgramRules = ( ): QuestionnaireRule[] => { return ( programRulesResponse?.map(({ id, condition, programRuleActions: actions }) => { + const dataElementVariablePattern = /#{(.*?)}/g; + const attributeVariablePattern = /A{(.*?)}/g; const dataElementIds = - condition.match(/#{(.*?)}/g)?.map(programRuleVariableName => { + condition.match(dataElementVariablePattern)?.map(programRuleVariableName => { const variableName = programRuleVariableName.replace(/#{|}/g, ""); const dataElementId = programRuleVariables?.find( @@ -23,14 +25,41 @@ export const getProgramRules = ( } return dataElementId; }) || []; + const teaIds = + condition.match(attributeVariablePattern)?.map(programRuleVariableName => { + const variableName = programRuleVariableName.replace(/A{|}/g, ""); - const parsedCondition = condition.replace(/#{(.*?)}/g, (match, programRuleVar) => { - const dataElementId = programRuleVariables?.find( - programRuleVariable => programRuleVariable.name === programRuleVar - )?.dataElement?.id; + const attributeId = programRuleVariables?.find( + programRuleVariable => variableName === programRuleVariable.name + )?.trackedEntityAttribute?.id; + + if (!attributeId) { + console.debug(`Could not find attributeId for variable: ${variableName}`); + } + return attributeId; + }) || []; + + const parsedCondition = condition.replace( + dataElementVariablePattern, + (match, programRuleVar) => { + const dataElementId = programRuleVariables?.find( + programRuleVariable => programRuleVariable.name === programRuleVar + )?.dataElement?.id; + + return `#{${dataElementId}}`; + } + ); + + const attributeParsedCondition = parsedCondition.replace( + attributeVariablePattern, + (match, programRuleVar) => { + const attributeId = programRuleVariables?.find( + programRuleVariable => programRuleVariable.name === programRuleVar + )?.trackedEntityAttribute?.id; - return `#{${dataElementId}}`; - }); + return `#{${attributeId}}`; + } + ); const programRuleActionIds: string[] = actions.map(action => action.id); @@ -42,6 +71,7 @@ export const getProgramRules = ( programRuleActionType: programRuleAction.programRuleActionType, data: programRuleAction.data, dataElement: programRuleAction.dataElement, + trackedEntityAttribute: programRuleAction.trackedEntityAttribute, programStageSection: { id: programRuleAction.programStageSection?.id, }, @@ -54,8 +84,9 @@ export const getProgramRules = ( return { id: id, - condition: parsedCondition.replace(/d2:/g, "fn:"), //replace d2: with fn: to decouple entity from DHIS + condition: attributeParsedCondition.replace(/d2:/g, "fn:"), //replace d2: with fn: to decouple entity from DHIS dataElementIds: _(dataElementIds).uniq().compact().value(), + teAttributeIds: _(teaIds).uniq().compact().value(), actions: programRuleActions || [], }; }) || [] diff --git a/src/domain/entities/Questionnaire/Questionnaire.ts b/src/domain/entities/Questionnaire/Questionnaire.ts index 84ba17f5..53ecd5ec 100644 --- a/src/domain/entities/Questionnaire/Questionnaire.ts +++ b/src/domain/entities/Questionnaire/Questionnaire.ts @@ -2,7 +2,12 @@ import { generateUid } from "../../../utils/uid"; import { SurveyRule } from "../AMRSurveyModule"; import { Id, Ref } from "../Ref"; import _ from "../generic/Collection"; -import { Code, Question, isAntibioticQuestion } from "./QuestionnaireQuestion"; +import { + Code, + Question, + QuestionnaireQuestion, + isAntibioticQuestion, +} from "./QuestionnaireQuestion"; import { QuestionnaireRule, getApplicableRules } from "./QuestionnaireRules"; import { QuestionnaireSection, QuestionnaireSectionM } from "./QuestionnaireSection"; @@ -142,7 +147,7 @@ export class Questionnaire { try { if (!questionnaire.rules || questionnaire.rules.length === 0) return questionnaire; - const allQsInQuestionnaire: Question[] = questionnaire.stages.flatMap(stage => { + const allQsInQuestionnaireStages: Question[] = questionnaire.stages.flatMap(stage => { return stage.sections.flatMap(section => { return section.questions.map(question => { return { ...question, stageId: stage.id }; @@ -150,6 +155,11 @@ export class Questionnaire { }); }); + const allQsInQuestionnaire = [ + ...(questionnaire.entity?.questions || []), + ...allQsInQuestionnaireStages, + ]; + const updatedQuestionnaire = allQsInQuestionnaire.reduce( (questionnaireAcc, question) => { return this.updateQuestionnaire( @@ -249,11 +259,18 @@ export class Questionnaire { initialLoad = false ): Questionnaire { //For the updated question, get all rules that are applicable - const allQsInQuestionnaire = questionnaire.stages.flatMap((stage: QuestionnaireStage) => { - return stage.sections.flatMap(section => { - return section.questions.map(question => question); - }); - }); + const allQsInQuestionnaireStages = questionnaire.stages.flatMap( + (stage: QuestionnaireStage) => { + return stage.sections.flatMap(section => { + return section.questions.map(question => question); + }); + } + ); + + const allQsInQuestionnaire = [ + ...(questionnaire.entity?.questions || []), + ...allQsInQuestionnaireStages, + ]; const applicableRules = getApplicableRules( updatedQuestion, @@ -283,7 +300,12 @@ export class Questionnaire { }), entity: isEntityQuestionUpdated && questionnaire.entity - ? this.updateEntityQuestion(questionnaire.entity, updatedQuestion) + ? this.updateEntityQuestion( + questionnaire.entity, + updatedQuestion, + questionnaire, + applicableRules + ) : questionnaire.entity, }); } @@ -343,11 +365,16 @@ export class Questionnaire { static updateEntityQuestion( questionnaireEntity: QuestionnaireEntity, - updatedQuestion: Question + updatedQuestion: Question, + questionnaire: Questionnaire, + rules: QuestionnaireRule[] ): QuestionnaireEntity | undefined { - const updatedEntityQuestions = questionnaireEntity.questions.map(question => { - return question.id === updatedQuestion.id ? updatedQuestion : question; - }); + const updatedEntityQuestions = QuestionnaireQuestion.updateQuestions( + questionnaireEntity.questions, + updatedQuestion, + rules, + questionnaire + ); return { ...questionnaireEntity, diff --git a/src/domain/entities/Questionnaire/QuestionnaireQuestion.ts b/src/domain/entities/Questionnaire/QuestionnaireQuestion.ts index d62b1779..fbf3997e 100644 --- a/src/domain/entities/Questionnaire/QuestionnaireQuestion.ts +++ b/src/domain/entities/Questionnaire/QuestionnaireQuestion.ts @@ -155,11 +155,25 @@ export class QuestionnaireQuestion { questions: Question[], updatedQuestion: Question, rules: QuestionnaireRule[], - questionnaire: Questionnaire + questionnaire: Questionnaire, + parentSectionHidden?: boolean ): Question[] { //1. Update the question value before anything else, the updated value needs to be used to parse rule conditions const updatedQuestions = questions.map(question => { - return question.id === updatedQuestion.id ? updatedQuestion : question; + if (question.id === updatedQuestion.id) { + return updatedQuestion; + } else if ( + parentSectionHidden && + question.text.includes("Add another") && + question.type === "boolean" + ) { + return { + ...question, + value: false, + }; + } else { + return question; + } }); //2. Now, apply all possible side effects of the updated value to the rest of the questionnaire. @@ -167,9 +181,12 @@ export class QuestionnaireQuestion { //Get list question ids that require update const allQuestionsRequiringUpdate = _( rules.flatMap(rule => { - const actionUpdates = rule.actions.flatMap(action => action?.dataElement?.id); + const actionUpdates = rule.actions.flatMap( + action => action?.dataElement?.id || action.trackedEntityAttribute?.id + ); const dataElementUpdates = rule.dataElementIds; - return [...actionUpdates, ...dataElementUpdates]; + const teaUpdates = rule.teAttributeIds; + return [...actionUpdates, ...dataElementUpdates, ...teaUpdates]; }) ) .compact() @@ -238,9 +255,11 @@ export class QuestionnaireQuestion { rule => rule.actions.filter( action => - action.programRuleActionType === "HIDEFIELD" && - action.dataElement && - action.dataElement.id === question.id + (action.programRuleActionType === "HIDEFIELD" && + action.dataElement && + action.dataElement.id === question.id) || + (action.trackedEntityAttribute && + action.trackedEntityAttribute.id === question.id) ).length > 0 ); if (!applicableRules || applicableRules.length === 0) return question.isVisible; diff --git a/src/domain/entities/Questionnaire/QuestionnaireRules.ts b/src/domain/entities/Questionnaire/QuestionnaireRules.ts index 3b1096c4..9e2ca297 100644 --- a/src/domain/entities/Questionnaire/QuestionnaireRules.ts +++ b/src/domain/entities/Questionnaire/QuestionnaireRules.ts @@ -31,6 +31,9 @@ export interface QuestionnaireRuleAction { dataElement?: { id: Id | undefined; // to hide }; + trackedEntityAttribute?: { + id: Id | undefined; // to hide + }; data?: string; // to assign programStageSection?: { id: Id | undefined; // to hide/show @@ -44,6 +47,7 @@ export interface QuestionnaireRule { id: Id; condition: string; //condition is parsed with dataelementId e.g: #{dataElementId} == 'Yes' dataElementIds: Id[]; // all dataElements in condition (there could be mutiple conditions) + teAttributeIds: Id[]; // all trackedEntityAttributes in condition (there could be mutiple conditions) actions: QuestionnaireRuleAction[]; parsedResult?: boolean; //calculate the condition and store the result } @@ -57,7 +61,9 @@ export const getApplicableRules = ( const applicableRules = questionnaireRules.filter( rule => rule.dataElementIds.includes(updatedQuestion.id) || - rule.actions.some(action => action.dataElement?.id === updatedQuestion.id) + rule.teAttributeIds.includes(updatedQuestion.id) || + rule.actions.some(action => action.dataElement?.id === updatedQuestion.id) || + rule.actions.some(action => action.trackedEntityAttribute?.id === updatedQuestion.id) ); //2. Run the rule conditions and return rules with parsed results diff --git a/src/domain/entities/Questionnaire/QuestionnaireSection.ts b/src/domain/entities/Questionnaire/QuestionnaireSection.ts index 6816c1bf..27a8e9ee 100644 --- a/src/domain/entities/Questionnaire/QuestionnaireSection.ts +++ b/src/domain/entities/Questionnaire/QuestionnaireSection.ts @@ -62,7 +62,8 @@ export class QuestionnaireSectionM { updatedSection.questions, updatedQuestion, rules, - questionnaire + questionnaire, + updatedSection.isVisible === false && section.isVisible === true ), }; });