Skip to content

Commit

Permalink
chore: Validate completedAt for Event and Enrollment [DHIS2-18222]
Browse files Browse the repository at this point in the history
  • Loading branch information
enricocolasante committed Oct 21, 2024
1 parent 24b1656 commit 255a89d
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ public enum ValidationCode {
E1020("Enrollment date: `{0}`, cannot be a future date."),
E1021("Incident date: `{0}`, cannot be a future date."),
E1022("TrackedEntity: `{0}`, must have same TrackedEntityType as Program `{1}`."),
E1023(
"DisplayIncidentDate is true but property occurredAt is null or has an invalid format: `{0}`."),
E1025("Property enrolledAt is null or has an invalid format: `{0}`."),
E1023("DisplayIncidentDate is true but property occurredAt is null."),
E1025("Property enrolledAt is null."),
E1029("Event OrganisationUnit: `{0}`, and Program: `{1}`, don't match."),
E1030("Event: `{0}`, already exists."),
E1031("Event occurredAt date is missing."),
Expand All @@ -69,7 +68,6 @@ public enum ValidationCode {
E1035("Event: `{0}`, ProgramStage value is null."),
E1039("ProgramStage: `{0}`, is not repeatable and an event already exists."),
E1041("Enrollment OrganisationUnit: `{0}`, and Program: `{1}`, don't match."),
E1042("Event: `{0}`, needs to have completed date."),
E1043("Event: `{0}`, completeness date has expired. Not possible to make changes to this event."),
E1044("Event: `{0}`, needs to have event date."),
E1045(
Expand All @@ -80,6 +78,8 @@ public enum ValidationCode {
E1048("Object: `{0}`, uid: `{1}`, has an invalid uid format."),
E1049("Could not find OrganisationUnit: `{0}`, linked to Tracked Entity."),
E1050("Event ScheduledAt date is missing."),
E1051("Event: `{0}`, completedAt must be null when status is `{1}`"),
E1052("Enrollment: `{0}`, completedAt must be null when status is `{1}`"),
E1054("AttributeOptionCombo `{0}` is not in the event programs category combo `{1}`."),
E1055("Default AttributeOptionCombo is not allowed since program has non-default CategoryCombo."),
E1056("Event date: `{0}`, is before start date: `{1}`, for AttributeOption: `{2}`."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,21 @@ private TrackerObjectsMapper() {

if (enrollment.getStatus() != dbEnrollment.getStatus()) {
dbEnrollment.setStatus(enrollment.getStatus());
Date completedDate =
enrollment.getCompletedAt() == null
? now
: DateUtils.fromInstant(enrollment.getCompletedAt());
switch (dbEnrollment.getStatus()) {
case ACTIVE -> {
dbEnrollment.setCompletedDate(null);
dbEnrollment.setCompletedBy(null);
}
case COMPLETED -> {
dbEnrollment.setCompletedDate(now);
dbEnrollment.setCompletedDate(completedDate);
dbEnrollment.setCompletedBy(user.getUsername());
}
case CANCELLED -> {
dbEnrollment.setCompletedDate(now);
dbEnrollment.setCompletedDate(completedDate);
dbEnrollment.setCompletedBy(null);
}
}
Expand Down Expand Up @@ -211,7 +215,8 @@ private TrackerObjectsMapper() {
EventStatus currentStatus = event.getStatus();
EventStatus previousStatus = dbEvent.getStatus();
if (currentStatus != previousStatus && currentStatus == EventStatus.COMPLETED) {
dbEvent.setCompletedDate(now);
dbEvent.setCompletedDate(
event.getCompletedAt() == null ? now : DateUtils.fromInstant(event.getCompletedAt()));
dbEvent.setCompletedBy(user.getUsername());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public class Enrollment implements TrackerDto, Serializable {

@JsonProperty private String storedBy;

@JsonProperty private Instant completedAt;

@JsonProperty private Geometry geometry;

@JsonProperty @Builder.Default private List<Attribute> attributes = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ public class Event implements TrackerDto, Serializable {
@JsonProperty @Builder.Default
private Set<MetadataIdentifier> attributeCategoryOptions = new HashSet<>();

@JsonProperty private String completedBy;

@JsonProperty private Instant completedAt;

@JsonProperty private Geometry geometry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1021;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1023;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1025;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1052;

import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.Objects;
import org.hisp.dhis.program.EnrollmentStatus;
import org.hisp.dhis.program.Program;
import org.hisp.dhis.tracker.imports.bundle.TrackerBundle;
import org.hisp.dhis.tracker.imports.domain.Enrollment;
Expand All @@ -55,13 +57,23 @@ public void validate(Reporter reporter, TrackerBundle bundle, Enrollment enrollm

if (Boolean.TRUE.equals(program.getDisplayIncidentDate())
&& Objects.isNull(enrollment.getOccurredAt())) {
reporter.addError(enrollment, E1023, enrollment.getOccurredAt());
reporter.addError(enrollment, E1023);
}

validateCompletedDateIsSetOnlyForSupportedStatus(reporter, enrollment);
}

private void validateCompletedDateIsSetOnlyForSupportedStatus(
Reporter reporter, Enrollment enrollment) {
if (enrollment.getCompletedAt() != null
&& EnrollmentStatus.COMPLETED != enrollment.getStatus()) {
reporter.addError(enrollment, E1052, enrollment, enrollment.getStatus());
}
}

private void validateMandatoryDates(Reporter reporter, Enrollment enrollment) {
if (Objects.isNull(enrollment.getEnrolledAt())) {
reporter.addError(enrollment, E1025, enrollment.getEnrolledAt());
reporter.addError(enrollment, E1025);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
import static java.time.Duration.ofDays;
import static java.time.Instant.now;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1031;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1042;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1043;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1046;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1047;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1050;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1051;

import java.time.Instant;
import java.util.Date;
Expand Down Expand Up @@ -70,25 +70,28 @@ public void validate(Reporter reporter, TrackerBundle bundle, Event event) {
return;
}

validateCompletedDateIsSetOnlyForSupportedStatus(reporter, event);
validateExpiryDays(reporter, event, program, bundle.getUser());
validatePeriodType(reporter, event, program);
}

private void validateCompletedDateIsSetOnlyForSupportedStatus(Reporter reporter, Event event) {
if (event.getCompletedAt() != null && EventStatus.COMPLETED != event.getStatus()) {
reporter.addError(event, E1051, event, event.getStatus());
}
}

private void validateExpiryDays(
Reporter reporter, Event event, Program program, UserDetails user) {
if (user.isAuthorized(Authorities.F_EDIT_EXPIRED.name())) {
if (user.isAuthorized(Authorities.F_EDIT_EXPIRED.name()) || event.getCompletedAt() == null) {
return;
}

if ((program.getCompleteEventsExpiryDays() > 0 && EventStatus.COMPLETED == event.getStatus())) {
if (event.getCompletedAt() == null) {
reporter.addError(event, E1042, event);
} else {
if (now()
if (program.getCompleteEventsExpiryDays() > 0
&& EventStatus.COMPLETED == event.getStatus()
&& now()
.isAfter(event.getCompletedAt().plus(ofDays(program.getCompleteEventsExpiryDays())))) {
reporter.addError(event, E1043, event);
}
}
reporter.addError(event, E1043, event);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,21 @@
*/
package org.hisp.dhis.tracker.imports.validation.validator.enrollment;

import static java.time.Instant.now;
import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1020;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1021;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1023;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1025;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1052;
import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.Mockito.when;

import java.time.Duration;
import java.time.Instant;
import org.hisp.dhis.common.CodeGenerator;
import org.hisp.dhis.program.EnrollmentStatus;
import org.hisp.dhis.program.Program;
import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams;
import org.hisp.dhis.tracker.imports.bundle.TrackerBundle;
Expand Down Expand Up @@ -82,7 +85,7 @@ void testMandatoryDatesMustBePresent() {
Enrollment.builder()
.enrollment(CodeGenerator.generateUid())
.program(MetadataIdentifier.ofUid(CodeGenerator.generateUid()))
.occurredAt(Instant.now())
.occurredAt(now())
.build();

when(preheat.getProgram(enrollment.getProgram())).thenReturn(new Program());
Expand All @@ -94,7 +97,7 @@ void testMandatoryDatesMustBePresent() {

@Test
void testDatesMustNotBeInTheFuture() {
final Instant dateInTheFuture = Instant.now().plus(Duration.ofDays(2));
final Instant dateInTheFuture = now().plus(Duration.ofDays(2));
Enrollment enrollment =
Enrollment.builder()
.enrollment(CodeGenerator.generateUid())
Expand All @@ -114,7 +117,7 @@ void testDatesMustNotBeInTheFuture() {

@Test
void testDatesShouldBeAllowedOnSameDayIfFutureDatesAreNotAllowed() {
final Instant today = Instant.now().plus(Duration.ofMinutes(1));
final Instant today = now().plus(Duration.ofMinutes(1));
Enrollment enrollment =
Enrollment.builder()
.enrollment(CodeGenerator.generateUid())
Expand All @@ -132,7 +135,7 @@ void testDatesShouldBeAllowedOnSameDayIfFutureDatesAreNotAllowed() {

@Test
void testDatesCanBeInTheFuture() {
final Instant dateInTheFuture = Instant.now().plus(Duration.ofDays(2));
final Instant dateInTheFuture = now().plus(Duration.ofDays(2));
Enrollment enrollment =
Enrollment.builder()
.enrollment(CodeGenerator.generateUid())
Expand All @@ -157,7 +160,7 @@ void testFailOnMissingOccurredAtDate() {
Enrollment.builder()
.enrollment(CodeGenerator.generateUid())
.program(MetadataIdentifier.ofUid(CodeGenerator.generateUid()))
.enrolledAt(Instant.now())
.enrolledAt(now())
.build();

Program program = new Program();
Expand All @@ -168,4 +171,20 @@ void testFailOnMissingOccurredAtDate() {

assertHasError(reporter, enrollment, E1023);
}

@Test
void shouldFailWhenCompletedAtIsPresentAndStatusIsNotCompleted() {
Enrollment enrollment = new Enrollment();
enrollment.setEnrollment(CodeGenerator.generateUid());
enrollment.setProgram(MetadataIdentifier.ofUid(CodeGenerator.generateUid()));
enrollment.setOccurredAt(now());
enrollment.setCompletedAt(now());
enrollment.setStatus(EnrollmentStatus.ACTIVE);

when(preheat.getProgram(enrollment.getProgram())).thenReturn(new Program());

validator.validate(reporter, bundle, enrollment);

assertHasError(reporter, enrollment, E1052);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@

import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1031;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1042;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1043;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1046;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1047;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1050;
import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1051;
import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -174,21 +174,20 @@ void testEventIsNotValidWhenScheduledDateIsNotPresentAndEventIsSchedule() {
}

@Test
void testEventIsNotValidWhenCompletedAtIsNotPresentAndEventIsCompleted() {
void shouldFailWhenCompletedAtIsPresentAndStatusIsNotCompleted() {
// given
when(preheat.getProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID)))
.thenReturn(getProgramWithRegistration());
Event event = new Event();
event.setEvent(CodeGenerator.generateUid());
event.setProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID));
event.setOccurredAt(now());
event.setStatus(EventStatus.COMPLETED);
event.setCompletedAt(now());
event.setStatus(EventStatus.ACTIVE);

// when
validator.validate(reporter, bundle, event);

// then
assertHasError(reporter, event, E1042);
assertHasError(reporter, event, E1051);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@ void shouldSendTrackerNotificationAtEnrollmentCompletionAndThenEventCompletion()
.programStage(MetadataIdentifier.ofUid(programStageA.getUid()))
.status(EventStatus.ACTIVE)
.attributeOptionCombo(MetadataIdentifier.EMPTY_UID)
.completedAt(Instant.now())
.occurredAt(Instant.now())
.build();

Expand Down

0 comments on commit 255a89d

Please sign in to comment.