From 0754882ba925e38beee8ed3ae66b3a5582f40aaa Mon Sep 17 00:00:00 2001 From: Mark Goodrich Date: Wed, 26 Jun 2024 14:56:28 -0400 Subject: [PATCH] EA-193: REST API - create first pass at REST endpoints for ADT --- .../openmrs/module/emrapi/adt/AdtService.java | 8 + .../module/emrapi/adt/AdtServiceImpl.java | 7 + .../emrapi/visit/VisitDomainWrapper.java | 36 +++++ .../emrapi/visit/VisitDomainWrapperTest.java | 148 ++++++++++++++++++ .../controller/EmrApiConfigController.java | 27 ++++ .../controller/InpatientVisitsController.java | 106 +++++++++++++ 6 files changed, 332 insertions(+) create mode 100644 omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigController.java create mode 100644 omod/src/main/java/org/openmrs/module/emrapi/web/controller/InpatientVisitsController.java diff --git a/api/src/main/java/org/openmrs/module/emrapi/adt/AdtService.java b/api/src/main/java/org/openmrs/module/emrapi/adt/AdtService.java index d16be9fd..cdb9ecf6 100644 --- a/api/src/main/java/org/openmrs/module/emrapi/adt/AdtService.java +++ b/api/src/main/java/org/openmrs/module/emrapi/adt/AdtService.java @@ -344,5 +344,13 @@ VisitDomainWrapper createRetrospectiveVisit(Patient patient, Location location, * @param visitIds - if non-null, only returns matches for visits with the given ids * @return List of the matching visits */ + // TODO expand this to take in an admissionLocation parameter and limit to admissions at that location List getVisitsAwaitingAdmission(Location location, Collection patientIds, Collection visitIds); + + /** + * Returns all patient awaiting transfer + * @param transferLocation - if non-null, only return matches for patients awaiting transfer to this location + * @return List of the matching visits< + */ + List getVisitsAwaitingTransfer(Location transferLocation); } diff --git a/api/src/main/java/org/openmrs/module/emrapi/adt/AdtServiceImpl.java b/api/src/main/java/org/openmrs/module/emrapi/adt/AdtServiceImpl.java index 08a0abf7..69e42265 100644 --- a/api/src/main/java/org/openmrs/module/emrapi/adt/AdtServiceImpl.java +++ b/api/src/main/java/org/openmrs/module/emrapi/adt/AdtServiceImpl.java @@ -917,4 +917,11 @@ public List getVisitsAwaitingAdmission(Location location, Collection getVisitsAwaitingTransfer(Location transferLocation) { + // TODO implement! + return Collections.emptyList(); + } } diff --git a/api/src/main/java/org/openmrs/module/emrapi/visit/VisitDomainWrapper.java b/api/src/main/java/org/openmrs/module/emrapi/visit/VisitDomainWrapper.java index 0de780e2..008aae90 100644 --- a/api/src/main/java/org/openmrs/module/emrapi/visit/VisitDomainWrapper.java +++ b/api/src/main/java/org/openmrs/module/emrapi/visit/VisitDomainWrapper.java @@ -18,6 +18,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.joda.time.DateMidnight; +import org.joda.time.DateTime; +import org.joda.time.Minutes; import org.openmrs.Encounter; import org.openmrs.EncounterType; import org.openmrs.Location; @@ -532,6 +534,40 @@ public Location getInpatientLocation(Date onDate) { return null; } + public Integer getTimeSinceAdmissionInMinutes() { + if (!isAdmitted() || getAdmissionEncounter() == null) { + return null; + } else { + return Minutes.minutesBetween(new DateTime(getAdmissionEncounter().getEncounterDatetime()), new DateTime()).getMinutes(); + } + } + + public Integer getTimeAtCurrentInpatientLocationInMinutes() { + + if (!isAdmitted()) { + return null; + } + + EncounterType admissionEncounterType = emrApiProperties.getAdmissionEncounterType(); + EncounterType transferEncounterType = emrApiProperties.getTransferWithinHospitalEncounterType(); + + Location ward = null; + Integer time = null; + + for (Encounter encounter : getSortedEncounters(SortOrder.MOST_RECENT_FIRST)) { + if (encounter.getEncounterType().equals(admissionEncounterType) || encounter.getEncounterType().equals(transferEncounterType)) { + if (ward == null) { + ward = encounter.getLocation(); + } + else if (!ward.equals(encounter.getLocation())) { + break; + } + time = Minutes.minutesBetween(new DateTime(encounter.getEncounterDatetime()), new DateTime()).getMinutes(); + } + } + return time; + } + public Date getStartDatetime() { return visit.getStartDatetime(); } diff --git a/api/src/test/java/org/openmrs/module/emrapi/visit/VisitDomainWrapperTest.java b/api/src/test/java/org/openmrs/module/emrapi/visit/VisitDomainWrapperTest.java index 4575a156..0ac0e5d4 100644 --- a/api/src/test/java/org/openmrs/module/emrapi/visit/VisitDomainWrapperTest.java +++ b/api/src/test/java/org/openmrs/module/emrapi/visit/VisitDomainWrapperTest.java @@ -1010,6 +1010,154 @@ public void shouldNotFailIfStopDatetimeNull() { assertNull(visitDomainWrapper.getStopDate()); } + @Test + public void timeSinceAdmissionInMinutes_shouldReturnTimeSinceAdmissionInMinutes() { + + EncounterType admitEncounterType = new EncounterType(); + EncounterType transferEncounterType = new EncounterType(); + + Location icu = new Location(); + + when(visit.getStartDatetime()).thenReturn(DateUtils.addHours(new Date(), -5)); + + EmrApiProperties props = mock(EmrApiProperties.class); + when(props.getAdmissionEncounterType()).thenReturn(admitEncounterType); + when(props.getTransferWithinHospitalEncounterType()).thenReturn(transferEncounterType); + visitDomainWrapper.setEmrApiProperties(props); + + Encounter admit = new Encounter(); + admit.setEncounterType(admitEncounterType); + admit.setEncounterDatetime(DateUtils.addHours(new Date(), -3)); + admit.setLocation(icu); + + Encounter transfer = new Encounter(); + transfer.setEncounterType(transferEncounterType); + transfer.setEncounterDatetime(DateUtils.addHours(new Date(), -2)); + transfer.setLocation(icu); + + Set encounters = new LinkedHashSet(); + encounters.add(admit); + encounters.add(transfer); + when(visit.getEncounters()).thenReturn(encounters); + + assertThat(visitDomainWrapper.getTimeSinceAdmissionInMinutes(), is(180)); + } + + @Test + public void timeSinceAdmissionInMinutes_shouldReturnNullIfPatientNeverAdmitted() { + when(visit.getStartDatetime()).thenReturn(DateUtils.addHours(new Date(), -5)); + assertNull(visitDomainWrapper.getTimeSinceAdmissionInMinutes()); + } + + @Test + public void timeSinceAdmissionInMinutes_shouldReturnNullIfPatientNoLongerAdmitted() { + + EncounterType admitEncounterType = new EncounterType(); + EncounterType exitFromInpatientEncounterType = new EncounterType(); + + Location icu = new Location(); + + when(visit.getStartDatetime()).thenReturn(DateUtils.addHours(new Date(), -5)); + + EmrApiProperties props = mock(EmrApiProperties.class); + when(props.getAdmissionEncounterType()).thenReturn(admitEncounterType); + when(props.getExitFromInpatientEncounterType()).thenReturn(exitFromInpatientEncounterType); + visitDomainWrapper.setEmrApiProperties(props); + + Encounter admit = new Encounter(); + admit.setEncounterType(admitEncounterType); + admit.setEncounterDatetime(DateUtils.addHours(new Date(), -3)); + admit.setLocation(icu); + + Encounter discharge = new Encounter(); + discharge.setEncounterType(exitFromInpatientEncounterType); + discharge.setEncounterDatetime(DateUtils.addHours(new Date(), -2)); + discharge.setLocation(icu); + + Set encounters = new LinkedHashSet(); + encounters.add(admit); + encounters.add(discharge); + when(visit.getEncounters()).thenReturn(encounters); + + assertNull(visitDomainWrapper.getTimeSinceAdmissionInMinutes()); + } + + @Test + public void timeAtCurrentInpatientLocationInMinutes_shouldReturnTimeAtCurrentInpatientLocationInMinutesEvenWhenMostRecentAdtEncounterDoesNotResultInLocationChange() { + + EncounterType admitEncounterType = new EncounterType(); + EncounterType transferEncounterType = new EncounterType(); + + Location icu = new Location(); + + when(visit.getStartDatetime()).thenReturn(DateUtils.addHours(new Date(), -5)); + + EmrApiProperties props = mock(EmrApiProperties.class); + when(props.getAdmissionEncounterType()).thenReturn(admitEncounterType); + when(props.getTransferWithinHospitalEncounterType()).thenReturn(transferEncounterType); + visitDomainWrapper.setEmrApiProperties(props); + + Encounter admit = new Encounter(); + admit.setEncounterType(admitEncounterType); + admit.setEncounterDatetime(DateUtils.addHours(new Date(), -3)); + admit.setLocation(icu); + + // not sure if this is a real use case, transfer to same location, but just in case + Encounter transfer = new Encounter(); + transfer.setEncounterType(transferEncounterType); + transfer.setEncounterDatetime(DateUtils.addHours(new Date(), -2)); + transfer.setLocation(icu); + + Set encounters = new LinkedHashSet(); + encounters.add(admit); + encounters.add(transfer); + when(visit.getEncounters()).thenReturn(encounters); + + assertThat(visitDomainWrapper.getTimeAtCurrentInpatientLocationInMinutes(), is(180)); + } + + + + @Test + public void timeAtCurrentInpatientLocationInMinutes_shouldReturnNullIfPatientNeverAdmitted() { + when(visit.getStartDatetime()).thenReturn(DateUtils.addHours(new Date(), -5)); + assertNull(visitDomainWrapper.getTimeAtCurrentInpatientLocationInMinutes()); + } + + @Test + public void timeAtCurrentInpatientLocationInMinutes_shouldReturnNullIfPatientNoLongerAdmitted() { + + EncounterType admitEncounterType = new EncounterType(); + EncounterType exitFromInpatientEncounterType = new EncounterType(); + + Location icu = new Location(); + + when(visit.getStartDatetime()).thenReturn(DateUtils.addHours(new Date(), -5)); + + EmrApiProperties props = mock(EmrApiProperties.class); + when(props.getAdmissionEncounterType()).thenReturn(admitEncounterType); + when(props.getExitFromInpatientEncounterType()).thenReturn(exitFromInpatientEncounterType); + visitDomainWrapper.setEmrApiProperties(props); + + Encounter admit = new Encounter(); + admit.setEncounterType(admitEncounterType); + admit.setEncounterDatetime(DateUtils.addHours(new Date(), -3)); + admit.setLocation(icu); + + Encounter discharge = new Encounter(); + discharge.setEncounterType(exitFromInpatientEncounterType); + discharge.setEncounterDatetime(DateUtils.addHours(new Date(), -2)); + discharge.setLocation(icu); + + Set encounters = new LinkedHashSet(); + encounters.add(admit); + encounters.add(discharge); + when(visit.getEncounters()).thenReturn(encounters); + + assertNull(visitDomainWrapper.getTimeAtCurrentInpatientLocationInMinutes()); + } + + private class ExpectedDiagnosis extends ArgumentMatcher { private Diagnosis expectedDiagnosis; diff --git a/omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigController.java b/omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigController.java new file mode 100644 index 00000000..4a355668 --- /dev/null +++ b/omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigController.java @@ -0,0 +1,27 @@ +package org.openmrs.module.emrapi.web.controller; + +import org.openmrs.module.emrapi.EmrApiProperties; +import org.openmrs.module.webservices.rest.SimpleObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping(value = "/rest/emrapi/configuration") +public class EmrApiConfigController { + + @Autowired + private EmrApiProperties emrApiProperties; + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public SimpleObject getEmrApiConfig() { + SimpleObject response = new SimpleObject(); + response.put("admissionEncounterType", emrApiProperties.getAdmissionEncounterType().getUuid()); + response.put("transferWithinHospitalEncounterType", emrApiProperties.getTransferWithinHospitalEncounterType().getUuid()); + response.put("exitFromInpatientEncounterTpye", emrApiProperties.getExitFromInpatientEncounterType().getUuid()); + return response; + } + +} diff --git a/omod/src/main/java/org/openmrs/module/emrapi/web/controller/InpatientVisitsController.java b/omod/src/main/java/org/openmrs/module/emrapi/web/controller/InpatientVisitsController.java new file mode 100644 index 00000000..069bafed --- /dev/null +++ b/omod/src/main/java/org/openmrs/module/emrapi/web/controller/InpatientVisitsController.java @@ -0,0 +1,106 @@ +package org.openmrs.module.emrapi.web.controller; + +import java.util.ArrayList; +import java.util.List; + +import org.openmrs.Location; +import org.openmrs.Visit; +import org.openmrs.module.emrapi.adt.AdtAction; +import org.openmrs.module.emrapi.adt.AdtService; +import org.openmrs.module.emrapi.visit.VisitDomainWrapper; +import org.openmrs.module.webservices.rest.SimpleObject; +import org.openmrs.module.webservices.rest.web.ConversionUtil; +import org.openmrs.module.webservices.rest.web.representation.Representation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping(value = "/rest/emrapi/inpatient") +public class InpatientVisitsController { + + @Autowired + private AdtService adtService; + + @RequestMapping(method = RequestMethod.GET, value = "/visits") + @ResponseBody + public List getInpatientVisits(@RequestParam(value = "currentLocation") Location currentLocation) { + + if (currentLocation == null) { + throw new IllegalArgumentException("currentLocation is required"); + } + + List visits = adtService.getInpatientVisits(adtService.getLocationThatSupportsVisits(currentLocation), currentLocation); + List response = new ArrayList(); + + if (visits == null) { + return response; + } + + for (VisitDomainWrapper visit : visits) { + SimpleObject inpatientVisit = new SimpleObject(); + inpatientVisit.put("visit", ConversionUtil.convertToRepresentation(visit.getVisit(), Representation.DEFAULT)); + inpatientVisit.put("patient", ConversionUtil.convertToRepresentation(visit.getVisit().getPatient(), Representation.DEFAULT)); + inpatientVisit.put("currentLocation", ConversionUtil.convertToRepresentation(currentLocation, Representation.DEFAULT)); + inpatientVisit.put("timeSinceAdmissionInMinutes", visit.getTimeSinceAdmissionInMinutes()); + inpatientVisit.put("timeAtInpatientLocationdInMinutes", visit.getTimeAtCurrentInpatientLocationInMinutes()); + response.add(inpatientVisit); + } + + return response; + } + + @RequestMapping(method = RequestMethod.GET, value = "/admissionRequests") + @ResponseBody + public List getVisitsAwaitingAdmission(@RequestParam("admissionLocation") Location admissionLocation) { + return getVisitsAwaitingAdmissionHelper(admissionLocation); + } + + @RequestMapping(method = RequestMethod.GET, value = "/transferRequests") + @ResponseBody + public List getVisitsAwaitingTransfer(@RequestParam("transferLocation") Location transferLocation) { + return getVisitsAwaitingTransferHelper(transferLocation); + } + + @RequestMapping(method = RequestMethod.GET, value = "/admissionAndTransferRequests") + @ResponseBody + public List getVisitsAwaitingAdminstOrTransfer(@RequestParam("location") Location location) { + List response = getVisitsAwaitingAdmissionHelper(location); + response.addAll(getVisitsAwaitingTransferHelper(location)); + return response; + } + + private List getVisitsAwaitingAdmissionHelper(Location admissionLocation) { + // TODO note also that this service method does *not* actually limit by admission location; we will need to expand the underlying service method/hql query to do this, see: https://openmrs.atlassian.net/browse/O3-3464 + List visits = adtService.getVisitsAwaitingAdmission(admissionLocation, null, null); + List visitObjects = new ArrayList(); + for (Visit visit : visits) { + SimpleObject inpatientVisit = new SimpleObject(); + inpatientVisit.put("visit", ConversionUtil.convertToRepresentation(visit, Representation.DEFAULT)); + inpatientVisit.put("patient", ConversionUtil.convertToRepresentation(visit.getPatient(), Representation.DEFAULT)); + inpatientVisit.put("type", AdtAction.Type.ADMISSION); + visitObjects.add(inpatientVisit); + } + return visitObjects; + } + + private List getVisitsAwaitingTransferHelper(Location transferLocation) { + List visits = adtService.getVisitsAwaitingTransfer(transferLocation); + List visitObjects = new ArrayList(); + for (Visit visit : visits) { + SimpleObject inpatientVisit = new SimpleObject(); + inpatientVisit.put("visit", ConversionUtil.convertToRepresentation(visit, Representation.DEFAULT)); + inpatientVisit.put("patient", ConversionUtil.convertToRepresentation(visit.getPatient(), Representation.DEFAULT)); + inpatientVisit.put("type", AdtAction.Type.TRANSFER); + visitObjects.add(inpatientVisit); + } + return visitObjects; + } + +} + + +