Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Validate completedAt for Event and Enrollment [DHIS2-18222] #18881

Merged
merged 5 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -141,17 +141,21 @@ private TrackerObjectsMapper() {

if (enrollment.getStatus() != dbEnrollment.getStatus()) {
dbEnrollment.setStatus(enrollment.getStatus());
switch (dbEnrollment.getStatus()) {
Date completedDate =
enrollment.getCompletedAt() == null
? now
: DateUtils.fromInstant(enrollment.getCompletedAt());
switch (enrollment.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 @@ -208,7 +212,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 @@ -27,10 +27,12 @@
*/
package org.hisp.dhis.tracker.imports.validation.validator.enrollment;

import static org.hisp.dhis.program.EnrollmentStatus.*;
enricocolasante marked this conversation as resolved.
Show resolved Hide resolved
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 java.time.LocalDate;
import java.time.ZoneOffset;
Expand All @@ -55,13 +57,24 @@ 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
&& enrollment.getStatus() != COMPLETED
&& enrollment.getStatus() != CANCELLED) {
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 (event.getCompletedAt() == null || user.isAuthorized(Authorities.F_EDIT_EXPIRED.name())) {
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,22 @@
*/
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.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasNoError;
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 All @@ -49,6 +53,8 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

Expand Down Expand Up @@ -82,7 +88,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 +100,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 +120,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 +138,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 +163,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 +174,39 @@ 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);
}

@ParameterizedTest
@EnumSource(
value = EnrollmentStatus.class,
names = {"COMPLETED", "CANCELLED"})
void shouldValidateWhenCompletedAtIsPresentAndStatusAcceptCompletedAt(EnrollmentStatus status) {
enricocolasante marked this conversation as resolved.
Show resolved Hide resolved
Enrollment enrollment = new Enrollment();
enrollment.setEnrollment(CodeGenerator.generateUid());
enrollment.setProgram(MetadataIdentifier.ofUid(CodeGenerator.generateUid()));
enrollment.setOccurredAt(now());
enrollment.setCompletedAt(now());
enrollment.setStatus(status);

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

validator.validate(reporter, bundle, enrollment);

assertHasNoError(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
Loading