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 22, 2024
1 parent 210af0d commit 21c98ae
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ public interface QueueEntryService {
*/
QueueEntry transitionQueueEntry(@NotNull QueueEntryTransition queueEntryTransition);

/**
* Undos a transition to the input queue entry by voiding it and making its previous queue entry
* active by setting the previous entry's end time to null.
*
* @see QueueEntryService#getPreviousQueueEntry(QueueEntry)
* @param queueEntry the queue entry to undo transition to. Must be active
* @return the previous queue entry, re-activated
* @throws IllegalArgumentException if the previous queue entry does not exist
* @throws IllegalStateException if multiple previous entries are identified
*/
QueueEntry undoTransition(@NotNull QueueEntry queueEntry);

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

/**
* Given a specified queue entry Q, return its previous queue entry P, where P has same patient and
* visit as Q, and P.endedAt time is same as Q.startedAt time, and P.queue is same as
* Q.queueComingFrom
*
* @param queueEntry
* @return the previous queue entry, null otherwise.
* @throws IllegalStateException if multiple previous queue entries are identified
*/
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 @@ -17,6 +17,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -131,6 +132,32 @@ public QueueEntry transitionQueueEntry(QueueEntryTransition queueEntryTransition
return getProxiedQueueEntryService().saveQueueEntry(queueEntryToStart);
}

/**
* @see QueueEntryService#undoTransition(QueueEntry)
*/
@Override
public QueueEntry undoTransition(@NotNull QueueEntry queueEntry) {
// TODO: Exceptions should be translatable and human readable on the frontend.
// See: https://openmrs.atlassian.net/browse/O3-2988
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 +252,28 @@ private void endQueueEntry(@NotNull QueueEntry queueEntry) {
queueEntry.setEndedAt(new Date());
dao.createOrUpdate(queueEntry);
}

@Override
@Transactional(readOnly = true)
public QueueEntry getPreviousQueueEntry(@NotNull QueueEntry queueEntry) {
Queue queueComingFrom = queueEntry.getQueueComingFrom();
if(queueComingFrom == null) {
return null;
}
QueueEntrySearchCriteria criteria = new QueueEntrySearchCriteria();
criteria.setPatient(queueEntry.getPatient());
criteria.setVisit(queueEntry.getVisit());
criteria.setEndedOn(queueEntry.getStartedAt());
criteria.setQueues(Arrays.asList(queueComingFrom));
List<QueueEntry> prevQueueEntries = dao.getQueueEntries(criteria);
if (prevQueueEntries.size() == 1) {
return prevQueueEntries.get(0);
} else if (prevQueueEntries.size() > 1) {
// TODO: Exceptions should be translatable and human readable on the frontend.
// See: https://openmrs.atlassian.net/browse/O3-2988
throw new IllegalStateException("Multiple previous queue entries found");
} 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 @@ -286,6 +287,57 @@ public void shouldTransitionQueueEntry() {
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]);

// Create transition
QueueEntryTransition transition1 = new QueueEntryTransition();
transition1.setQueueEntryToTransition(queueEntry1);
transition1.setTransitionDate(date2);
QueueEntry queueEntry2 = queueEntryService.transitionQueueEntry(transition1);

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

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

@Test
public void shouldGenerateVisitQueueNumber() {
Visit visit = new Visit();
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,81 @@
* 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/transition", method = RequestMethod.DELETE)
@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");
}
}
}
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;
}
Loading

0 comments on commit 21c98ae

Please sign in to comment.