diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.ts b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.ts index fba369880b..737082fea9 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.ts +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.ts @@ -62,6 +62,7 @@ export const runRulesOnUpdateFieldBatch = async ({ enrollmentData, attributeValues, formFoundation, + isEnrollmentFormWithEvent: Boolean(currentEvent), }); const effectsWithValidations = await validateAssignEffects({ diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.ts b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.ts index 270ceb864c..bd6f54d7e1 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.ts +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.ts @@ -70,6 +70,13 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ const formId = getDataEntryKey(dataEntryId, itemId); const addFormDataActions = addFormData(`${dataEntryId}-${itemId}`, formValues); const firstStageDataEntryPropsToInclude = firstStage && getDataEntryPropsToInclude(firstStage); + + const fabricateFirstStageEvent = (stage: ProgramStage) => ({ + programStageId: stage.id, + programStageName: stage.name, + }); + const firstStageEvent = firstStage && fabricateFirstStageEvent(firstStage); + const dataEntryPropsToInclude = [ ...enrollmentDataEntryPropsToInclude, ...extraDataEntryProps, @@ -94,10 +101,12 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ const effects = getApplicableRuleEffectsForTrackerProgram({ program, orgUnit, + currentEvent: firstStageEvent, stage: firstStage, attributeValues: clientValues, enrollmentData: { enrolledAt: new Date().toISOString() }, formFoundation, + isEnrollmentFormWithEvent: true, }); return batchActions([ diff --git a/src/core_modules/capture-core/rules/RuleEngine/RuleEngine.ts b/src/core_modules/capture-core/rules/RuleEngine/RuleEngine.ts index b36a1cb3d7..8535a4ba75 100644 --- a/src/core_modules/capture-core/rules/RuleEngine/RuleEngine.ts +++ b/src/core_modules/capture-core/rules/RuleEngine/RuleEngine.ts @@ -2,6 +2,7 @@ import { RuleEngineJs } from '@dhis2/rule-engine'; import { InputBuilder, ValueProcessor, + evaluateRules, getRulesEffectsProcessor, } from './helpers'; import type { @@ -43,6 +44,7 @@ export class RuleEngine { selectedOrgUnit, selectedUserRoles, optionSets, + isEnrollmentFormWithEvent, }: RulesEngineInput): OutputEffects { if (!programRulesContainer.programRules || !selectedOrgUnit || @@ -71,23 +73,19 @@ export class RuleEngine { []; const ruleEngine = new RuleEngineJs(this.flags.verbose || false); - const effects = (currentEvent ? - ruleEngine.evaluateEvent( - inputBuilder.convertEvent(currentEvent), - enrollment, - events, - executionContext, - ) : - ruleEngine.evaluateEnrollment( - enrollment!, - events, - executionContext, - )) - .map(effect => ({ - ...Object.fromEntries(effect.ruleAction.values), - action: effect.ruleAction.type, - data: effect.data, - })) as Array; + + const effects = evaluateRules({ + ruleEngine, + enrollment, + currentEvent: currentEvent && inputBuilder.convertEvent(currentEvent), + events, + executionContext, + isEnrollmentFormWithEvent, + }).map(effect => ({ + ...Object.fromEntries(effect.ruleAction.values), + action: effect.ruleAction.type, + data: effect.data, + })) as Array; const processRulesEffects = getRulesEffectsProcessor(this.outputConverter); return processRulesEffects({ diff --git a/src/core_modules/capture-core/rules/RuleEngine/helpers/InputBuilder.ts b/src/core_modules/capture-core/rules/RuleEngine/helpers/InputBuilder.ts index acb9c1fa52..6332f9f696 100644 --- a/src/core_modules/capture-core/rules/RuleEngine/helpers/InputBuilder.ts +++ b/src/core_modules/capture-core/rules/RuleEngine/helpers/InputBuilder.ts @@ -362,7 +362,7 @@ export class InputBuilder { const convertDate = (dateString?: string | null) => this.toLocalDate(dateString, RuleLocalDate.currentDate()); return new RuleEnrollmentJs( - enrollment!, + enrollment || 'registration', programName || '', convertDate(incidentDate), convertDate(enrollmentDate), diff --git a/src/core_modules/capture-core/rules/RuleEngine/helpers/evaluateRules.ts b/src/core_modules/capture-core/rules/RuleEngine/helpers/evaluateRules.ts new file mode 100644 index 0000000000..129cbcc550 --- /dev/null +++ b/src/core_modules/capture-core/rules/RuleEngine/helpers/evaluateRules.ts @@ -0,0 +1,52 @@ +import { + RuleEngineJs, + RuleEnrollmentJs, + RuleEventJs, + RuleEngineContextJs, +} from '@dhis2/rule-engine'; + +export const evaluateRules = ({ + ruleEngine, + enrollment, + currentEvent, + events, + executionContext, + isEnrollmentFormWithEvent, +}: { + ruleEngine: RuleEngineJs + enrollment: RuleEnrollmentJs | null, + currentEvent: RuleEventJs | null | undefined, + events: Array, + executionContext: RuleEngineContextJs, + isEnrollmentFormWithEvent: boolean | undefined, +}) => { + if (!currentEvent) { + return ruleEngine.evaluateEnrollment( + enrollment!, + events, + executionContext, + ); + } + if (!isEnrollmentFormWithEvent) { + return ruleEngine.evaluateEvent( + currentEvent, + enrollment, + events, + executionContext, + ); + } + const duplicateActionIds = new Set(); + return ruleEngine.evaluateAll(enrollment, [currentEvent], executionContext) + .flatMap(entry => entry.ruleEffects) + .filter((effect) => { + const actionId = effect.ruleAction.values.get('id'); + if (!actionId) { + return true; + } + if (duplicateActionIds.has(actionId)) { + return false; + } + duplicateActionIds.add(actionId); + return true; + }); +}; diff --git a/src/core_modules/capture-core/rules/RuleEngine/helpers/index.ts b/src/core_modules/capture-core/rules/RuleEngine/helpers/index.ts index d2fed8e065..969bcbf0ca 100644 --- a/src/core_modules/capture-core/rules/RuleEngine/helpers/index.ts +++ b/src/core_modules/capture-core/rules/RuleEngine/helpers/index.ts @@ -1,3 +1,4 @@ export { InputBuilder } from './InputBuilder'; export { ValueProcessor } from './ValueProcessor'; +export { evaluateRules } from './evaluateRules'; export { getRulesEffectsProcessor } from './rulesEffectsProcessor'; diff --git a/src/core_modules/capture-core/rules/RuleEngine/types/ruleEngine.types.ts b/src/core_modules/capture-core/rules/RuleEngine/types/ruleEngine.types.ts index 9681673ffe..cb839c3921 100644 --- a/src/core_modules/capture-core/rules/RuleEngine/types/ruleEngine.types.ts +++ b/src/core_modules/capture-core/rules/RuleEngine/types/ruleEngine.types.ts @@ -254,6 +254,7 @@ export type RulesEngineInput = { selectedOrgUnit: OrgUnit | null, selectedUserRoles?: Array | null, optionSets: OptionSets, + isEnrollmentFormWithEvent?: boolean, }; export type Translator = (value: string) => string; diff --git a/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRuleEffectsForTrackerProgram.test.js.snap b/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRuleEffectsForTrackerProgram.test.js.snap index be8d636e1b..da0104a463 100644 --- a/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRuleEffectsForTrackerProgram.test.js.snap +++ b/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRuleEffectsForTrackerProgram.test.js.snap @@ -25,6 +25,7 @@ Object { "valueType": "TEXT", }, }, + "isEnrollmentFormWithEvent": undefined, "optionSets": Object { "0": Object { "displayName": undefined, diff --git a/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRulesEffectsForEventProgram.test.js.snap b/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRulesEffectsForEventProgram.test.js.snap index e3b154ce97..96eaaf786f 100644 --- a/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRulesEffectsForEventProgram.test.js.snap +++ b/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRulesEffectsForEventProgram.test.js.snap @@ -25,6 +25,7 @@ Object { "valueType": "TEXT", }, }, + "isEnrollmentFormWithEvent": undefined, "optionSets": Object { "0": Object { "displayName": undefined, diff --git a/src/core_modules/capture-core/rules/getApplicableRuleEffects.ts b/src/core_modules/capture-core/rules/getApplicableRuleEffects.ts index ef63ee3a98..5d1b1210fe 100644 --- a/src/core_modules/capture-core/rules/getApplicableRuleEffects.ts +++ b/src/core_modules/capture-core/rules/getApplicableRuleEffects.ts @@ -52,6 +52,7 @@ export const getApplicableRuleEffectsForTrackerProgram = ({ attributeValues, enrollmentData, formFoundation, + isEnrollmentFormWithEvent, }: GetApplicableRuleEffectsForTrackerProgramInput, flattenedResult = false, ) => { @@ -83,6 +84,7 @@ flattenedResult = false, programRuleVariables, trackedEntityAttributes: getTrackedEntityAttributesForRulesExecution(program.attributes), foundationForPostProcessing, + isEnrollmentFormWithEvent, }); return flattenedResult ? effects : buildEffectsHierarchy(effects); @@ -99,6 +101,7 @@ const getApplicableRuleEffects = ({ programRuleVariables, trackedEntityAttributes, foundationForPostProcessing, + isEnrollmentFormWithEvent, }: GetApplicableRuleEffectsInput) => { const dataElements = getDataElementsForRulesExecution(stages); @@ -115,6 +118,7 @@ const getApplicableRuleEffects = ({ selectedEntity: attributeValues, selectedOrgUnit: orgUnit, optionSets, + isEnrollmentFormWithEvent, }); return postProcessRulesEffects( diff --git a/src/core_modules/capture-core/rules/rules.types.ts b/src/core_modules/capture-core/rules/rules.types.ts index 5c43298d69..15455b2ebb 100644 --- a/src/core_modules/capture-core/rules/rules.types.ts +++ b/src/core_modules/capture-core/rules/rules.types.ts @@ -19,6 +19,7 @@ export type GetApplicableRuleEffectsForTrackerProgramInput = { attributeValues?: TEIValues, enrollmentData?: Enrollment, formFoundation?: RenderFoundation, + isEnrollmentFormWithEvent?: boolean, }; export type GetApplicableRuleEffectsForEventProgramInput = { @@ -38,4 +39,5 @@ export type GetApplicableRuleEffectsInput = { programRuleVariables: Array, trackedEntityAttributes?: TrackedEntityAttributes, foundationForPostProcessing: RenderFoundation, + isEnrollmentFormWithEvent?: boolean, };