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

O3-2862 - Queue Module - validate that queue entry falls within visit… #52

Merged
merged 5 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,55 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.queue.api;

import java.util.Date;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.User;
import org.openmrs.Visit;
import org.openmrs.annotation.Handler;
import org.openmrs.api.handler.SaveHandler;
import org.openmrs.module.queue.api.search.QueueEntrySearchCriteria;
import org.openmrs.module.queue.model.QueueEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@Handler(supports = Visit.class)
public class VisitWithQueueEntriesSaveHandler implements SaveHandler<Visit> {

private final Log log = LogFactory.getLog(getClass());

private final QueueEntryService queueEntryService;

@Autowired
public VisitWithQueueEntriesSaveHandler(@Qualifier("queue.QueueEntryService") QueueEntryService queueEntryService) {
this.queueEntryService = queueEntryService;
}

@Override
public void handle(Visit visit, User user, Date date, String s) {
if (visit.getStopDatetime() != null) {
QueueEntrySearchCriteria criteria = new QueueEntrySearchCriteria();
criteria.setVisit(visit);
criteria.setIsEnded(false);
List<QueueEntry> queueEntries = queueEntryService.getQueueEntries(criteria);
if (!queueEntries.isEmpty()) {
log.debug("Closing " + +queueEntries.size() + " queue entries associated with stopped visit");
}
for (QueueEntry qe : queueEntries) {
qe.setEndedAt(visit.getStopDatetime());
queueEntryService.saveQueueEntry(qe);
log.trace("Closed queue entry " + qe + " on " + visit.getStopDatetime());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import static org.springframework.validation.ValidationUtils.rejectIfEmptyOrWhitespace;

import org.openmrs.Visit;
import org.openmrs.annotation.Handler;
import org.openmrs.api.context.Context;
import org.openmrs.module.queue.api.QueueServicesWrapper;
Expand Down Expand Up @@ -40,6 +41,22 @@ public void validate(Object target, Errors errors) {
QueueEntry queueEntry = (QueueEntry) target;
Queue queue = queueEntry.getQueue();

Visit visit = queueEntry.getVisit();
if (visit != null) {
if (queueEntry.getStartedAt().before(visit.getStartDatetime())) {
errors.rejectValue("startedAt", "queue.entry.error.cannotStartBeforeVisitStartDate",
"A queue entry cannot start before the associated visit start date");
} else if (visit.getStopDatetime() != null) {
if (queueEntry.getStartedAt().after(visit.getStopDatetime())) {
errors.rejectValue("startedAt", "queue.entry.error.cannotStartAfterVisitStopDate",
"A queue entry cannot start after the associated visit stop date");
} else if (queueEntry.getEndedAt() == null || queueEntry.getEndedAt().after(visit.getStopDatetime())) {
errors.rejectValue("endedAt", "queue.entry.error.cannotEndAfterVisitStopDate",
"A queue entry cannot end after the associated visit stop date");
}
}
}

if (queueEntry.getEndedAt() != null) {
if (queueEntry.getStartedAt().after(queueEntry.getEndedAt())) {
errors.rejectValue("endedAt", "queueEntry.endedAt.invalid",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.queue.validators;

import java.util.Date;
import java.util.List;

import org.openmrs.Visit;
import org.openmrs.annotation.Handler;
import org.openmrs.module.queue.api.QueueEntryService;
import org.openmrs.module.queue.api.search.QueueEntrySearchCriteria;
import org.openmrs.module.queue.model.QueueEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

@Handler(supports = { Visit.class }, order = 60)
public class VisitWithQueueEntriesValidator implements Validator {

private final QueueEntryService queueEntryService;

@Autowired
public VisitWithQueueEntriesValidator(@Qualifier("queue.QueueEntryService") QueueEntryService queueEntryService) {
this.queueEntryService = queueEntryService;
}

@Override
public boolean supports(Class<?> clazz) {
return Visit.class.isAssignableFrom(clazz);
}

@Override
public void validate(Object target, Errors errors) {
if (!(target instanceof Visit)) {
throw new IllegalArgumentException("the parameter target must be of type " + Visit.class);
}
Visit visit = (Visit) target;

// This implementation is copied to match the core validator approach to nested encounters
if (visit.getId() != null) {
Date startDateTime = visit.getStartDatetime();
Date stopDateTime = visit.getStopDatetime();

QueueEntrySearchCriteria criteria = new QueueEntrySearchCriteria();
criteria.setIsEnded(null);
Copy link
Member

Choose a reason for hiding this comment

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

I assume setting this to null means returns all queues, both those that are ended and those that aren't ended.

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct, the default (which I don't love, but the way it is implemented) is to only return active entries by default. So to get all entries, you need to explicitly null out this property :/

criteria.setVisit(visit);
List<QueueEntry> queueEntries = queueEntryService.getQueueEntries(criteria);
for (QueueEntry queueEntry : queueEntries) {
if (queueEntry.getStartedAt().before(startDateTime)) {
errors.rejectValue("startDatetime", "queue.entry.error.cannotStartBeforeVisitStartDate",
"This visit has queue entries whose dates cannot be before the start date");
break;
}
if (stopDateTime != null) {
if (queueEntry.getStartedAt().after(stopDateTime)) {
errors.rejectValue("stopDatetime", "queue.entry.error.cannotStartAfterVisitStopDate",
"This visit has queue entries which start after the stop date");
break;
}
// We do not fail validation if the endedAt is null. The visit saveHandler will ensure this is set
if (queueEntry.getEndedAt() != null && queueEntry.getEndedAt().after(stopDateTime)) {
errors.rejectValue("stopDatetime", "queue.entry.error.cannotEndAfterVisitStopDate",
"This visit has queue entries which end after the stop date");
break;
}
}
}
}
}
}
3 changes: 3 additions & 0 deletions api/src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@
#

queue.entry.duplicate.patient=Patient already in the queue
queue.entry.error.cannotStartBeforeVisitStartDate=Queue entry cannot start before the visit start date
queue.entry.error.cannotStartAfterVisitStopDate=Queue entry cannot start after the visit stop date
queue.entry.error.cannotEndAfterVisitStopDate=Queue entry cannot end after the visit stop date
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.queue.api;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.time.DateUtils;
import org.junit.Before;
import org.junit.Test;
import org.openmrs.Visit;
import org.openmrs.api.VisitService;
import org.openmrs.module.queue.SpringTestConfiguration;
import org.openmrs.module.queue.model.QueueEntry;
import org.openmrs.test.BaseModuleContextSensitiveTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;

@ContextConfiguration(classes = SpringTestConfiguration.class, inheritLocations = false)
public class VisitWithQueueEntriesSaveHandlerTest extends BaseModuleContextSensitiveTest {

private static final List<String> INITIAL_DATASET_XML = Arrays.asList(
"org/openmrs/module/queue/api/dao/QueueDaoTest_locationInitialDataset.xml",
"org/openmrs/module/queue/api/dao/QueueEntryDaoTest_conceptsInitialDataset.xml",
"org/openmrs/module/queue/api/dao/QueueEntryDaoTest_patientInitialDataset.xml",
"org/openmrs/module/queue/api/dao/VisitQueueEntryDaoTest_visitInitialDataset.xml",
"org/openmrs/module/queue/api/dao/QueueDaoTest_initialDataset.xml",
"org/openmrs/module/queue/api/dao/QueueEntryDaoTest_initialDataset.xml",
"org/openmrs/module/queue/validators/QueueEntryValidatorTest_globalPropertyInitialDataset.xml");

private Visit visit;

private QueueEntry queueEntry;

private Date stopDate;

@Autowired
@Qualifier("queue.QueueEntryService")
private QueueEntryService queueEntryService;

@Autowired
private VisitService visitService;

@Before
public void setup() {
INITIAL_DATASET_XML.forEach(this::executeDataSet);
queueEntry = queueEntryService.getQueueEntryById(3).get();
visit = queueEntry.getVisit();
stopDate = DateUtils.addHours(visit.getStartDatetime(), 12);
}

@Test
public void shouldNotEndQueueEntriesIfVisitIsNotStopped() {
assertNull(visit.getStopDatetime());
assertNull(queueEntry.getEndedAt());
visit = visitService.saveVisit(visit);
queueEntry = queueEntryService.getQueueEntryById(queueEntry.getId()).get();
assertNull(visit.getStopDatetime());
assertNull(queueEntry.getEndedAt());
}

@Test
public void shouldEndQueueEntriesIfVisitIsStopped() {
assertNull(visit.getStopDatetime());
assertNull(queueEntry.getEndedAt());
visit.setStopDatetime(stopDate);
visit = visitService.saveVisit(visit);
queueEntry = queueEntryService.getQueueEntryById(queueEntry.getId()).get();
assertThat(visit.getStopDatetime(), equalTo(stopDate));
assertThat(queueEntry.getEndedAt(), equalTo(stopDate));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.time.DateUtils;
import org.junit.Before;
import org.junit.Test;
import org.openmrs.Concept;
import org.openmrs.Visit;
import org.openmrs.api.context.Context;
import org.openmrs.module.queue.SpringTestConfiguration;
import org.openmrs.module.queue.model.Queue;
Expand All @@ -46,6 +48,8 @@ public class QueueEntryValidatorTest extends BaseModuleContextSensitiveTest {

private QueueEntry queueEntry;

private Visit visit;

@Autowired
private QueueEntryValidator validator;

Expand All @@ -54,6 +58,9 @@ public void setup() {
INITIAL_CONCEPTS_DATASETS.forEach(this::executeDataSet);
queueEntry = new QueueEntry();
queueEntry.setQueue(new Queue());
visit = new Visit();
visit.setStartDatetime(DateUtils.addHours(new Date(), -2));
visit.setStopDatetime(DateUtils.addHours(new Date(), -1));
errors = new BindException(queueEntry, queueEntry.getClass().getName());
}

Expand Down Expand Up @@ -119,6 +126,59 @@ public void shouldRejectQueueEntryIfEndedAtIsBeforeStartedAtDate() {
assertThat(queueEntryStatusFieldError.getCode(), is("queueEntry.endedAt.invalid"));
}

@Test
public void shouldNotRejectIfQueueEntryStartedAndEndedDuringVisit() {
queueEntry.setVisit(visit);
queueEntry.setStartedAt(visit.getStartDatetime());
queueEntry.setEndedAt(visit.getStopDatetime());
validator.validate(queueEntry, errors);
FieldError startedAtFieldError = errors.getFieldError("startedAt");
assertNull(startedAtFieldError);
FieldError endedAtFieldError = errors.getFieldError("endedAt");
assertNull(endedAtFieldError);
}

@Test
public void shouldRejectIfQueueEntryStartedBeforeVisitStartDate() {
queueEntry.setVisit(visit);
queueEntry.setStartedAt(DateUtils.addMilliseconds(visit.getStartDatetime(), -1));
validator.validate(queueEntry, errors);
FieldError startedAtFieldError = errors.getFieldError("startedAt");
assertNotNull(startedAtFieldError);
assertThat(startedAtFieldError.getCode(), is("queue.entry.error.cannotStartBeforeVisitStartDate"));
}

@Test
public void shouldRejectIfQueueEntryStartedAfterVisitEndDate() {
queueEntry.setVisit(visit);
queueEntry.setStartedAt(DateUtils.addMilliseconds(visit.getStopDatetime(), 1));
validator.validate(queueEntry, errors);
FieldError startedAtFieldError = errors.getFieldError("startedAt");
assertNotNull(startedAtFieldError);
assertThat(startedAtFieldError.getCode(), is("queue.entry.error.cannotStartAfterVisitStopDate"));
}

@Test
public void shouldRejectIfQueueEntryEndedAfterVisitEndDate() {
queueEntry.setVisit(visit);
queueEntry.setStartedAt(visit.getStartDatetime());
queueEntry.setEndedAt(DateUtils.addHours(visit.getStopDatetime(), 1));
validator.validate(queueEntry, errors);
FieldError endedAtFieldError = errors.getFieldError("endedAt");
assertNotNull(endedAtFieldError);
assertThat(endedAtFieldError.getCode(), is("queue.entry.error.cannotEndAfterVisitStopDate"));
}

@Test
public void shouldRejectIfQueueEntryNotEndedWhenVisitStopped() {
queueEntry.setVisit(visit);
queueEntry.setStartedAt(visit.getStartDatetime());
validator.validate(queueEntry, errors);
FieldError endedAtFieldError = errors.getFieldError("endedAt");
assertNotNull(endedAtFieldError);
assertThat(endedAtFieldError.getCode(), is("queue.entry.error.cannotEndAfterVisitStopDate"));
}

private Date yesterday() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -1);
Expand Down
Loading
Loading