Skip to content

Commit

Permalink
(BREAKING) O3-2748 add endpoint for un-doing transition of queue entr…
Browse files Browse the repository at this point in the history
…ies; rename endpoints related to transitions
  • Loading branch information
chibongho committed Mar 21, 2024
1 parent 210af0d commit 4ac6bd9
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ public interface QueueEntryService {
*/
QueueEntry transitionQueueEntry(@NotNull QueueEntryTransition queueEntryTransition);

/**
* Undos a transition by voiding the input queue entry and making its previous queue entry
* (which MUST not be null) active by setting the previous entry's end time to null.
*
* @param queueEntry the queue entry to undo transition from
* @return the previous queue entry, re-activated
*
*/
QueueEntry undoTransition(@NotNull QueueEntry queueEntry);

/**
* Voids a queue entry
*
Expand Down Expand Up @@ -113,4 +123,13 @@ String generateVisitQueueNumber(@NotNull Location location, @NotNull Queue queue
* @param sortWeightGenerator the SortWeightGenerator to set
*/
void setSortWeightGenerator(SortWeightGenerator sortWeightGenerator);

/**
* Given a specified queue entry, return its previous queue entry (i.e. the queue entry the patient
* transition from to get to the specified one)
*
* @param queueEntry
* @return the previous queue entry, if uniquely identifable; null otherwise.
*/
QueueEntry getPreviousQueueEntry(@NotNull QueueEntry queueEntry);
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ private Criteria createCriteriaFromSearchCriteria(QueueEntrySearchCriteria searc
limitByCollectionProperty(c, "qe.queueComingFrom", searchCriteria.getQueuesComingFrom());
limitToGreaterThanOrEqualToProperty(c, "qe.startedAt", searchCriteria.getStartedOnOrAfter());
limitToLessThanOrEqualToProperty(c, "qe.startedAt", searchCriteria.getStartedOnOrBefore());
limitToEqualsProperty(c, "qe.startedAt", searchCriteria.getStartedOn());
limitToGreaterThanOrEqualToProperty(c, "qe.endedAt", searchCriteria.getEndedOnOrAfter());
limitToLessThanOrEqualToProperty(c, "qe.endedAt", searchCriteria.getEndedOnOrBefore());
limitToEqualsProperty(c, "qe.endedAt", searchCriteria.getEndedOn());
if (searchCriteria.getHasVisit() == Boolean.TRUE) {
c.add(Restrictions.isNotNull("qe.visit"));
} else if (searchCriteria.getHasVisit() == Boolean.FALSE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,31 @@ public QueueEntry transitionQueueEntry(QueueEntryTransition queueEntryTransition
return getProxiedQueueEntryService().saveQueueEntry(queueEntryToStart);
}

/**
* @see QueueEntryService#undoTransition(QueueEntry)
*/
@Override
@Transactional
public QueueEntry undoTransition(@NotNull QueueEntry queueEntry) {
if (queueEntry.getVoided()) {
throw new IllegalArgumentException("cannot undo transition on a voided queue entry");
}
if (queueEntry.getEndedAt() != null) {
throw new IllegalArgumentException("cannot undo transition on an ended queue entry");
}
QueueEntry prevQueueEntry = getPreviousQueueEntry(queueEntry);
if (prevQueueEntry == null) {
throw new IllegalArgumentException("specified queue entry does not have a previous queue entry");
}
prevQueueEntry.setEndedAt(null);
prevQueueEntry = dao.createOrUpdate(prevQueueEntry);

queueEntry.setVoided(true);
queueEntry.setVoidReason("undo transition");
dao.createOrUpdate(queueEntry);
return prevQueueEntry;
}

/**
* @see QueueEntryService#voidQueueEntry(QueueEntry, String)
*/
Expand Down Expand Up @@ -225,4 +250,18 @@ private void endQueueEntry(@NotNull QueueEntry queueEntry) {
queueEntry.setEndedAt(new Date());
dao.createOrUpdate(queueEntry);
}

@Override
public QueueEntry getPreviousQueueEntry(@NotNull QueueEntry queueEntry) {
QueueEntrySearchCriteria criteria = new QueueEntrySearchCriteria();
criteria.setPatient(queueEntry.getPatient());
criteria.setVisit(queueEntry.getVisit());
criteria.setEndedOn(queueEntry.getStartedAt());
List<QueueEntry> prevQueueEntries = dao.getQueueEntries(criteria);
if (prevQueueEntries.size() == 1) {
return prevQueueEntries.get(0);
} else {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,15 @@ public class QueueEntrySearchCriteria implements Serializable {

private Date startedOnOrBefore;

private Date startedOn;

private Boolean isEnded = null;

private Date endedOnOrAfter;

private Date endedOnOrBefore;

private Date endedOn;

private boolean includedVoided = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Optional;
Expand Down Expand Up @@ -285,6 +286,56 @@ public void shouldTransitionQueueEntry() {
assertThat(queueEntry3.getStartedAt(), equalTo(date3));
assertNull(queueEntry3.getEndedAt());
}

@Test
public void shouldUndoTransitionQueueEntry() {
Patient patient1 = new Patient();
Visit visit1 = new Visit();
visit1.setPatient(patient1);
Queue queue0 = new Queue();
Queue queue1 = new Queue();
Concept concept1 = new Concept();
String string1 = "starting";
double double1 = 5.0;
Location location1 = new Location();
Provider provider1 = new Provider();
Date date1 = DateUtils.addHours(new Date(), -12);
Date date2 = DateUtils.addHours(date1, 6);

QueueEntry queueEntry1 = new QueueEntry();
queueEntry1.setQueue(queue1);
queueEntry1.setPatient(patient1);
queueEntry1.setVisit(visit1);
queueEntry1.setPriority(concept1);
queueEntry1.setPriorityComment(string1);
queueEntry1.setStatus(concept1);
queueEntry1.setSortWeight(double1);
queueEntry1.setLocationWaitingFor(location1);
queueEntry1.setProviderWaitingFor(provider1);
queueEntry1.setQueueComingFrom(queue0);
queueEntry1.setStartedAt(date1);
assertNull(queueEntry1.getEndedAt());

// Mock the DAO to return the object being saved
when(dao.createOrUpdate(any())).thenAnswer(invocation -> invocation.getArguments()[0]);

// Mock the DAO to searches for previous queue entry correctly
QueueEntrySearchCriteria criteria = new QueueEntrySearchCriteria();
criteria.setPatient(patient1);
criteria.setVisit(visit1);
criteria.setEndedOn(date2);
when(dao.getQueueEntries(criteria)).thenReturn(Arrays.asList(queueEntry1));

// First transition test that no changes are required and all values will be pulled from existing queue entry
QueueEntryTransition transition1 = new QueueEntryTransition();
transition1.setQueueEntryToTransition(queueEntry1);
transition1.setTransitionDate(date2);
QueueEntry queueEntry2 = queueEntryService.transitionQueueEntry(transition1);

queueEntryService.undoTransition(queueEntry2);
assertThat(queueEntry2.getVoided(), equalTo(true));
assertNull(queueEntry1.getEndedAt());
}

@Test
public void shouldGenerateVisitQueueNumber() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@
package org.openmrs.module.queue.web;

import java.util.Date;
import java.util.Map;
import java.util.Optional;

import org.openmrs.Concept;
import org.openmrs.api.APIException;
import org.openmrs.module.queue.api.QueueEntryService;
import org.openmrs.module.queue.api.QueueServicesWrapper;
import org.openmrs.module.queue.model.Queue;
import org.openmrs.module.queue.model.QueueEntry;
import org.openmrs.module.queue.model.QueueEntryTransition;
import org.openmrs.module.queue.web.dto.QueueEntryTransitionRequest;
import org.openmrs.module.queue.web.dto.UndoQueueEntryTransitionRequest;
import org.openmrs.module.webservices.rest.web.ConversionUtil;
import org.openmrs.module.webservices.rest.web.RestConstants;
import org.openmrs.module.webservices.rest.web.representation.Representation;
Expand All @@ -34,80 +36,82 @@
* The main controller that exposes additional end points for order entry
*/
@Controller
@RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/queue-entry-transition")
public class QueueEntryTransitionRestController extends BaseRestController {

public static final String QUEUE_ENTRY_TO_TRANSITION = "queueEntryToTransition";

public static final String TRANSITION_DATE = "transitionDate";

public static final String NEW_QUEUE = "newQueue";

public static final String NEW_STATUS = "newStatus";

public static final String NEW_PRIORITY = "newPriority";

public static final String NEW_PRIORITY_COMMENT = "newPriorityComment";

private final QueueServicesWrapper services;

@Autowired
public QueueEntryTransitionRestController(QueueServicesWrapper services) {
this.services = services;
}

@RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
@RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/queue-entry/transition", method = { RequestMethod.PUT,
RequestMethod.POST })
@ResponseBody
public Object transitionQueueEntry(@RequestBody Map<String, String> body) {
public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body) {
QueueEntryTransition transition = new QueueEntryTransition();

// Queue Entry to Transition
String queueEntryUuid = body.get(QUEUE_ENTRY_TO_TRANSITION);
String queueEntryUuid = body.getQueueEntryToTransition();
QueueEntry queueEntry = services.getQueueEntryService().getQueueEntryByUuid(queueEntryUuid)
.orElseThrow(() -> new APIException(QUEUE_ENTRY_TO_TRANSITION + " is a required parameter"));
.orElseThrow(() -> new APIException("queueEntryToTransition not specified or found"));
transition.setQueueEntryToTransition(queueEntry);

// Transition Date
Date transitionDate = new Date();
if (body.containsKey(TRANSITION_DATE)) {
transitionDate = (Date) ConversionUtil.convert(body.get(TRANSITION_DATE), Date.class);
if (body.getTransitionDate() != null) {
transitionDate = (Date) ConversionUtil.convert(body.getTransitionDate(), Date.class);
}
if (transitionDate == null) {
throw new APIException("Invalid transition date specified: " + body.get(TRANSITION_DATE));
throw new APIException("Invalid transition date specified: " + body.getTransitionDate());
}
transition.setTransitionDate(transitionDate);

// Queue
if (body.containsKey(NEW_QUEUE)) {
Optional<Queue> queueOptional = services.getQueueService().getQueueByUuid(body.get(NEW_QUEUE));
if (body.getNewQueue() != null) {
Optional<Queue> queueOptional = services.getQueueService().getQueueByUuid(body.getNewQueue());
if (!queueOptional.isPresent()) {
throw new APIException("Invalid queue specified: " + body.get(NEW_QUEUE));
throw new APIException("Invalid queue specified: " + body.getNewQueue());
}
transition.setNewQueue(queueOptional.get());
}

// Status
if (body.containsKey(NEW_STATUS)) {
Concept concept = services.getConcept(body.get(NEW_STATUS));
if (body.getNewStatus() != null) {
Concept concept = services.getConcept(body.getNewStatus());
if (concept == null) {
throw new APIException("Invalid status specified: " + body.get(NEW_STATUS));
throw new APIException("Invalid status specified: " + body.getNewStatus());
}
transition.setNewStatus(concept);
}

// Priority
if (body.containsKey(NEW_PRIORITY)) {
Concept concept = services.getConcept(body.get(NEW_PRIORITY));
if (body.getNewPriority() != null) {
Concept concept = services.getConcept(body.getNewPriority());
if (concept == null) {
throw new APIException("Invalid priority specified: " + body.get(NEW_PRIORITY));
throw new APIException("Invalid priority specified: " + body.getNewPriority());
}
transition.setNewPriority(concept);
}

transition.setNewPriorityComment(body.get(NEW_PRIORITY_COMMENT));
transition.setNewPriorityComment(body.getNewPriorityComment());

// Execute transition
QueueEntry newQueueEntry = services.getQueueEntryService().transitionQueueEntry(transition);
return ConversionUtil.convertToRepresentation(newQueueEntry, Representation.REF);
}

@RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/queue-entry/undo-transition", method = {
RequestMethod.PUT, RequestMethod.POST })
@ResponseBody
public Object undoTransition(@RequestBody UndoQueueEntryTransitionRequest body) {
QueueEntryService qes = services.getQueueEntryService();
Optional<QueueEntry> queueEntry = qes.getQueueEntryByUuid(body.getQueueEntry());
if (queueEntry.isPresent()) {
QueueEntry unEndedQueueEntry = services.getQueueEntryService().undoTransition(queueEntry.get());
return ConversionUtil.convertToRepresentation(unEndedQueueEntry, Representation.REF);
} else {
throw new APIException("Invalid queue entry uuid: " + body);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.web.dto;

import lombok.Getter;

@Getter
public class QueueEntryTransitionRequest {

private String queueEntryToTransition;

private String transitionDate;

private String newQueue;

private String newStatus;

private String newPriority;

private String newPriorityComment;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.web.dto;

import lombok.Getter;

@Getter
public class UndoQueueEntryTransitionRequest {

private String queueEntry;
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ public DelegatingResourceDescription getRepresentationDescription(Representation
description.addProperty("locationWaitingFor", Representation.DEFAULT);
description.addProperty("queueComingFrom", Representation.DEFAULT);
description.addProperty("providerWaitingFor", Representation.DEFAULT);

// gets the previous queue entry, but with REF representation so it doesn't recursively
// fetch more previous entries.
description.addProperty("previousQueueEntry", Representation.REF);

description.addLink("full", ".?v=" + RestConstants.REPRESENTATION_FULL);
} else if (representation instanceof FullRepresentation) {
addSharedResourceDescriptionProperties(description);
Expand All @@ -197,6 +202,7 @@ public DelegatingResourceDescription getRepresentationDescription(Representation
description.addProperty("voided");
description.addProperty("voidReason");
description.addProperty("auditInfo");
description.addProperty("previousQueueEntry", Representation.FULL);
} else if (representation instanceof CustomRepresentation) {
description = null;
}
Expand All @@ -214,6 +220,11 @@ public String getDisplay(QueueEntry queueEntry) {
return (personName == null ? queueEntry.getPatient().toString() : personName.getFullName());
}

@PropertyGetter("previousQueueEntry")
public QueueEntry getPreviousQueueEntry(QueueEntry queueEntry) {
return services.getQueueEntryService().getPreviousQueueEntry(queueEntry);
}

@Override
public String getResourceVersion() {
return "2.3";
Expand Down

0 comments on commit 4ac6bd9

Please sign in to comment.