Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const runRulesOnUpdateFieldBatch = async ({
enrollmentData,
attributeValues,
formFoundation,
isEnrollmentFormWithEvent: Boolean(currentEvent),
});

const effectsWithValidations = await validateAssignEffects({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Comment on lines +74 to +77

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ideal, but since the alternative would require a big refactor, I agree with using this fabricateFirstStageEvent workaround to serve as a dummy event during the form initialization run.

const firstStageEvent = firstStage && fabricateFirstStageEvent(firstStage);

const dataEntryPropsToInclude = [
...enrollmentDataEntryPropsToInclude,
...extraDataEntryProps,
Expand All @@ -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([
Expand Down
32 changes: 15 additions & 17 deletions src/core_modules/capture-core/rules/RuleEngine/RuleEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RuleEngineJs } from '@dhis2/rule-engine';
import {
InputBuilder,
ValueProcessor,
evaluateRules,
getRulesEffectsProcessor,
} from './helpers';
import type {
Expand Down Expand Up @@ -43,6 +44,7 @@ export class RuleEngine {
selectedOrgUnit,
selectedUserRoles,
optionSets,
isEnrollmentFormWithEvent,
}: RulesEngineInput): OutputEffects {
if (!programRulesContainer.programRules ||
!selectedOrgUnit ||
Expand Down Expand Up @@ -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<ProgramRuleEffect>;

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<ProgramRuleEffect>;

const processRulesEffects = getRulesEffectsProcessor(this.outputConverter);
return processRulesEffects({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<RuleEventJs>,
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<string>();
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;
});
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { InputBuilder } from './InputBuilder';
export { ValueProcessor } from './ValueProcessor';
export { evaluateRules } from './evaluateRules';
export { getRulesEffectsProcessor } from './rulesEffectsProcessor';
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ export type RulesEngineInput = {
selectedOrgUnit: OrgUnit | null,
selectedUserRoles?: Array<string> | null,
optionSets: OptionSets,
isEnrollmentFormWithEvent?: boolean,
};

export type Translator = (value: string) => string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Object {
"valueType": "TEXT",
},
},
"isEnrollmentFormWithEvent": undefined,
"optionSets": Object {
"0": Object {
"displayName": undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Object {
"valueType": "TEXT",
},
},
"isEnrollmentFormWithEvent": undefined,
"optionSets": Object {
"0": Object {
"displayName": undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const getApplicableRuleEffectsForTrackerProgram = ({
attributeValues,
enrollmentData,
formFoundation,
isEnrollmentFormWithEvent,
}: GetApplicableRuleEffectsForTrackerProgramInput,
flattenedResult = false,
) => {
Expand Down Expand Up @@ -83,6 +84,7 @@ flattenedResult = false,
programRuleVariables,
trackedEntityAttributes: getTrackedEntityAttributesForRulesExecution(program.attributes),
foundationForPostProcessing,
isEnrollmentFormWithEvent,
});

return flattenedResult ? effects : buildEffectsHierarchy(effects);
Expand All @@ -99,6 +101,7 @@ const getApplicableRuleEffects = ({
programRuleVariables,
trackedEntityAttributes,
foundationForPostProcessing,
isEnrollmentFormWithEvent,
}: GetApplicableRuleEffectsInput) => {
const dataElements = getDataElementsForRulesExecution(stages);

Expand All @@ -115,6 +118,7 @@ const getApplicableRuleEffects = ({
selectedEntity: attributeValues,
selectedOrgUnit: orgUnit,
optionSets,
isEnrollmentFormWithEvent,
});

return postProcessRulesEffects(
Expand Down
2 changes: 2 additions & 0 deletions src/core_modules/capture-core/rules/rules.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type GetApplicableRuleEffectsForTrackerProgramInput = {
attributeValues?: TEIValues,
enrollmentData?: Enrollment,
formFoundation?: RenderFoundation,
isEnrollmentFormWithEvent?: boolean,
};

export type GetApplicableRuleEffectsForEventProgramInput = {
Expand All @@ -38,4 +39,5 @@ export type GetApplicableRuleEffectsInput = {
programRuleVariables: Array<ProgramRuleVariable>,
trackedEntityAttributes?: TrackedEntityAttributes,
foundationForPostProcessing: RenderFoundation,
isEnrollmentFormWithEvent?: boolean,
};
Loading