diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/programrule/DefaultTrackerProgramRuleService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/programrule/DefaultTrackerProgramRuleService.java index a06602226ea0..53bdf6b6bb39 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/programrule/DefaultTrackerProgramRuleService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/programrule/DefaultTrackerProgramRuleService.java @@ -38,9 +38,12 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.hisp.dhis.dxf2.events.event.EventQueryParams; +import org.hisp.dhis.dxf2.events.event.EventService; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramInstance; import org.hisp.dhis.program.ProgramStageInstance; +import org.hisp.dhis.program.ProgramStageInstanceService; import org.hisp.dhis.programrule.engine.ProgramRuleEngine; import org.hisp.dhis.rules.models.RuleEffects; import org.hisp.dhis.trackedentity.TrackedEntityInstance; @@ -79,6 +82,10 @@ public class DefaultTrackerProgramRuleService implements TrackerProgramRuleServi private final TrackerConverterService attributeValueTrackerConverterService; + private final EventService eventService; + + private final ProgramStageInstanceService programStageInstanceService; + /** * This method is calling rule engine for every enrollment and all the linked events, for all * events linked to an enrollment not present in the payload and for all the program events. @@ -99,7 +106,7 @@ public List calculateRuleEffects(TrackerBundle bundle) { return programRuleEngine .evaluateEnrollmentAndEvents( enrollment, - getEventsFromEnrollment(enrollment.getUid(), bundle), + getEventsFromEnrollment(enrollment.getUid(), bundle, bundle.getPreheat()), getAttributes(e, bundle)) .stream(); }); @@ -179,7 +186,7 @@ private List calculateEventRuleEffects(TrackerBundle bundle) { return programRuleEngine .evaluateEnrollmentAndEvents( enrollment, - getEventsFromEnrollment(enrollment.getUid(), bundle), + getEventsFromEnrollment(enrollment.getUid(), bundle, bundle.getPreheat()), attributeValues) .stream(); } @@ -196,22 +203,19 @@ private ProgramInstance getEnrollment(TrackerBundle bundle, String enrollmentUid } private Set getEventsFromEnrollment( - String enrollment, TrackerBundle bundle) { - List bundleEventUids = - bundle.getEvents().stream().map(Event::getUid).collect(Collectors.toList()); + String enrollmentUid, TrackerBundle bundle, TrackerPreheat preheat) { + EventQueryParams eventQueryParams = new EventQueryParams(); + eventQueryParams.setProgramInstances(Set.of(enrollmentUid)); + List events = + eventService.getEvents(eventQueryParams).getEvents(); - // Get all programStageInstances from preheat that are linked to - // enrollment - // and are not present in the payload Stream programStageInstances = - bundle.getPreheat().getEvents().values().stream() - .filter(e -> e.getProgramInstance().getUid().equals(enrollment)) - .filter(e -> !bundleEventUids.contains(e.getUid())); + events.stream().map(e -> programStageInstanceService.getProgramStageInstance(e.getUid())); // All events in the payload that are linked to enrollment Stream bundleEvents = bundle.getEvents().stream() - .filter(e -> e.getEnrollment().equals(enrollment)) + .filter(e -> e.getEnrollment().equals(enrollmentUid)) .map( event -> eventTrackerConverterService.fromForRuleEngine(bundle.getPreheat(), event)); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/programrule/ProgramRuleAssignActionTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/programrule/ProgramRuleAssignActionTest.java index 40b0f3ae5773..72e946d37303 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/programrule/ProgramRuleAssignActionTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/programrule/ProgramRuleAssignActionTest.java @@ -33,14 +33,19 @@ import static org.hisp.dhis.tracker.report.TrackerErrorCode.E1307; import static org.hisp.dhis.tracker.report.TrackerErrorCode.E1308; import static org.hisp.dhis.tracker.report.TrackerErrorCode.E1310; +import static org.hisp.dhis.utils.Assertions.assertContainsOnly; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundle; +import org.hisp.dhis.eventdatavalue.EventDataValue; import org.hisp.dhis.preheat.PreheatIdentifier; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; +import org.hisp.dhis.program.ProgramStageInstance; import org.hisp.dhis.programrule.ProgramRule; import org.hisp.dhis.programrule.ProgramRuleAction; import org.hisp.dhis.programrule.ProgramRuleActionService; @@ -48,6 +53,7 @@ import org.hisp.dhis.programrule.ProgramRuleService; import org.hisp.dhis.programrule.ProgramRuleVariable; import org.hisp.dhis.programrule.ProgramRuleVariableService; +import org.hisp.dhis.programrule.ProgramRuleVariableSourceType; import org.hisp.dhis.setting.SettingKey; import org.hisp.dhis.setting.SystemSettingManager; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; @@ -56,7 +62,10 @@ import org.hisp.dhis.tracker.TrackerImportStrategy; import org.hisp.dhis.tracker.TrackerTest; import org.hisp.dhis.tracker.report.TrackerImportReport; +import org.hisp.dhis.util.DateUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.beans.factory.annotation.Autowired; class ProgramRuleAssignActionTest extends TrackerTest { @@ -74,6 +83,8 @@ class ProgramRuleAssignActionTest extends TrackerTest { private DataElement dataElement1; + private DataElement dataElement2; + private TrackedEntityAttribute attribute1; @Override @@ -81,8 +92,7 @@ public void initTest() throws IOException { ObjectBundle bundle = setUpMetadata("tracker/simple_metadata.json"); program = bundle.getPreheat().get(PreheatIdentifier.UID, Program.class, "BFcipDERJnf"); dataElement1 = bundle.getPreheat().get(PreheatIdentifier.UID, DataElement.class, "DATAEL00001"); - DataElement dataElement2 = - bundle.getPreheat().get(PreheatIdentifier.UID, DataElement.class, "DATAEL00002"); + dataElement2 = bundle.getPreheat().get(PreheatIdentifier.UID, DataElement.class, "DATAEL00002"); attribute1 = bundle.getPreheat().get(PreheatIdentifier.UID, TrackedEntityAttribute.class, "dIVt4l5vIOa"); TrackedEntityAttribute attribute2 = @@ -94,9 +104,14 @@ public void initTest() throws IOException { programRuleVariableService.addProgramRuleVariable(programRuleVariable); programRuleVariableService.addProgramRuleVariable(programRuleVariableAttribute); + ProgramRuleVariable programRuleVariablePreviousEvent = + createProgramRuleVariableWithDataElement('C', program, dataElement1); + programRuleVariablePreviousEvent.setSourceType( + ProgramRuleVariableSourceType.DATAELEMENT_PREVIOUS_EVENT); + programRuleVariableService.addProgramRuleVariable(programRuleVariablePreviousEvent); + injectAdminUser(); - assignProgramRule(); trackerImportService.importTracker( fromJson("tracker/programrule/tei_enrollment_completed_event.json")); } @@ -104,7 +119,7 @@ public void initTest() throws IOException { @Test void shouldNotImportWithWarningWhenAttributeWithSameValueIsAssignedByAssignRule() throws IOException { - + assignProgramRule(); TrackerImportParams params = fromJson("tracker/programrule/te_enrollment_update_attribute_same_value.json"); params.setImportStrategy(TrackerImportStrategy.CREATE_AND_UPDATE); @@ -115,9 +130,44 @@ void shouldNotImportWithWarningWhenAttributeWithSameValueIsAssignedByAssignRule( assertEquals(E1310, importReport.getValidationReport().getWarnings().get(0).getWarningCode()); } + @ParameterizedTest + @CsvSource({"2024-02-10,THIRD", "2024-01-28,SECOND", "2024-01-19,FIRST"}) + void shouldImportEventAndCorrectlyAssignPreviousEventDataValue( + String eventOccurredDate, String previousEventDataValue) throws IOException { + TrackerImportParams params = + fromJson("tracker/programrule/three_events_with_different_dates.json"); + params.setImportStrategy(TrackerImportStrategy.CREATE_AND_UPDATE); + + trackerImportService.importTracker(params); + + assignPreviousEventProgramRule(); + + dbmsManager.clearSession(); + dbmsManager.flushSession(); + + params = fromJson("tracker/programrule/event_with_data_value.json"); + + params.getEvents().get(0).setOccurredAt(DateUtils.instantFromDateAsString(eventOccurredDate)); + + TrackerImportReport importReport = trackerImportService.importTracker(params); + assertEquals(E1308, importReport.getValidationReport().getWarnings().get(0).getWarningCode()); + + ProgramStageInstance event = manager.get(ProgramStageInstance.class, "D9PbzJY8bZZ"); + + List eventDataValues = + event.getEventDataValues().stream() + .filter(dv -> dv.getDataElement().equals("DATAEL00002")) + .map(EventDataValue::getValue) + .collect(Collectors.toList()); + assertContainsOnly(List.of(previousEventDataValue), eventDataValues); + } + @Test void shouldImportWithWarningWhenDataElementWithSameValueIsAssignedByAssignRule() throws IOException { + assignProgramRule(); + dbmsManager.clearSession(); + dbmsManager.flushSession(); TrackerImportParams params = fromJson("tracker/programrule/event_update_datavalue_same_value.json"); params.setImportStrategy(TrackerImportStrategy.CREATE_AND_UPDATE); @@ -130,6 +180,7 @@ void shouldImportWithWarningWhenDataElementWithSameValueIsAssignedByAssignRule() @Test void shouldNotImportWhenDataElementWithDifferentValueIsAssignedByAssignRule() throws IOException { + assignProgramRule(); TrackerImportParams params = fromJson("tracker/programrule/event_update_datavalue_different_value.json"); params.setImportStrategy(TrackerImportStrategy.CREATE_AND_UPDATE); @@ -143,6 +194,7 @@ void shouldNotImportWhenDataElementWithDifferentValueIsAssignedByAssignRule() th void shouldImportWithWarningWhenDataElementWithDifferentValueIsAssignedByAssignRuleAndOverwriteKeyIsTrue() throws IOException { + assignProgramRule(); systemSettingManager.saveSystemSetting(SettingKey.RULE_ENGINE_ASSIGN_OVERWRITE, true); TrackerImportParams params = fromJson("tracker/programrule/event_update_datavalue_different_value.json"); @@ -158,7 +210,10 @@ void shouldNotImportWhenDataElementWithDifferentValueIsAssignedByAssignRule() th void shouldImportWithWarningWhenDataElementWithDifferentAndEmptyValueIsAssignedByAssignRuleAndOverwriteKeyIsTrue() throws IOException { + assignProgramRule(); systemSettingManager.saveSystemSetting(SettingKey.RULE_ENGINE_ASSIGN_OVERWRITE, true); + dbmsManager.clearSession(); + dbmsManager.flushSession(); TrackerImportParams params = fromJson("tracker/programrule/event_update_datavalue_empty_value.json"); params.setImportStrategy(TrackerImportStrategy.CREATE_AND_UPDATE); @@ -183,6 +238,16 @@ private void assignProgramRule() { programRuleService.updateProgramRule(programRule); } + private void assignPreviousEventProgramRule() { + ProgramRule programRule = createProgramRule('G', program, null, "true"); + programRuleService.addProgramRule(programRule); + ProgramRuleAction programRuleAction = + createProgramRuleAction(programRule, ASSIGN, dataElement2, "#{ProgramRuleVariableC}"); + programRuleActionService.addProgramRuleAction(programRuleAction); + programRule.getProgramRuleActions().add(programRuleAction); + programRuleService.updateProgramRule(programRule); + } + private ProgramRule createProgramRule( char uniqueCharacter, Program program, ProgramStage programStage, String condition) { ProgramRule programRule = createProgramRule(uniqueCharacter, program); diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/event_update_datavalue_same_value.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/event_update_datavalue_same_value.json index 1945da3e2094..d0f92940e778 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/event_update_datavalue_same_value.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/event_update_datavalue_same_value.json @@ -34,7 +34,7 @@ "enrollments": [], "events": [ { - "event": "D9PbzJY8bJO", + "event": "D9PbzJY8bJX", "status": "COMPLETED", "program": { "idScheme": "UID", diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/event_with_data_value.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/event_with_data_value.json new file mode 100644 index 000000000000..3c41ce0ed0c4 --- /dev/null +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/event_with_data_value.json @@ -0,0 +1,45 @@ +{ + "trackedEntities": [], + "enrollments": [], + "events": [ + { + "event": "D9PbzJY8bZZ", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "BFcipDERJnf" + }, + "programStage": { + "idScheme": "UID", + "identifier": "NpsdDv6kKSO" + }, + "enrollment": "TvctPPhpD8u", + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "relationships": [], + "scheduledAt": "2019-01-25T12:10:38.100", + "storedBy": "admin", + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID" + }, + "attributeCategoryOptions": [], + + "dataValues": [ + { + "storedBy": "admin", + "providedElsewhere": false, + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00001" + }, + "value": "LAST" + } + ], + "notes": [] + } + ], + "relationships": [] +} \ No newline at end of file diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/three_events_with_different_dates.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/three_events_with_different_dates.json new file mode 100644 index 000000000000..a889a716896d --- /dev/null +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/programrule/three_events_with_different_dates.json @@ -0,0 +1,124 @@ +{ + "trackedEntities": [], + "enrollments": [], + "events": [ + { + "event": "D9PbzJY8bJO", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "BFcipDERJnf" + }, + "programStage": { + "idScheme": "UID", + "identifier": "NpsdDv6kKSO" + }, + "enrollment": "TvctPPhpD8u", + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "relationships": [], + "occurredAt": "2024-01-11T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID" + }, + "attributeCategoryOptions": [], + + "dataValues": [ + { + "storedBy": "admin", + "providedElsewhere": false, + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00001" + }, + "value": "FIRST" + } + ], + "notes": [] + }, + { + "event": "D9PbzJY8bFF", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "BFcipDERJnf" + }, + "programStage": { + "idScheme": "UID", + "identifier": "NpsdDv6kKSO" + }, + "enrollment": "TvctPPhpD8u", + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "relationships": [], + "occurredAt": "2024-01-25T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID" + }, + "attributeCategoryOptions": [], + + "dataValues": [ + { + "storedBy": "admin", + "providedElsewhere": false, + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00001" + }, + "value": "SECOND" + } + ], + "notes": [] + }, + { + "event": "D9PbzJY8bXX", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "BFcipDERJnf" + }, + "programStage": { + "idScheme": "UID", + "identifier": "NpsdDv6kKSO" + }, + "enrollment": "TvctPPhpD8u", + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "relationships": [], + "occurredAt": "2024-01-30T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID" + }, + "attributeCategoryOptions": [], + + "dataValues": [ + { + "storedBy": "admin", + "providedElsewhere": false, + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00001" + }, + "value": "THIRD" + } + ], + "notes": [] + } + ], + "relationships": [] +} \ No newline at end of file