diff --git a/api/src/main/java/org/openmrs/module/emrapi/db/EmrApiDAO.java b/api/src/main/java/org/openmrs/module/emrapi/db/EmrApiDAO.java index 7ab3fbd85..8f8907b9d 100644 --- a/api/src/main/java/org/openmrs/module/emrapi/db/EmrApiDAO.java +++ b/api/src/main/java/org/openmrs/module/emrapi/db/EmrApiDAO.java @@ -1,5 +1,8 @@ package org.openmrs.module.emrapi.db; +import org.openmrs.Patient; +import org.openmrs.module.emrapi.visit.VisitWithDiagnoses; + import java.util.List; import java.util.Map; @@ -8,4 +11,15 @@ public interface EmrApiDAO { List executeHql(String queryString, Map parameters, Class clazz); List executeHqlFromResource(String resource, Map parameters, Class clazz); + + /** + * Fetches visits with note encounters and diagnoses of a patient. + * + * @param patient the patient + * @param startIndex the start index of the number of visits to fetch + * @param limit the limit of the number of visits to fetch + * + * @return a list of visits that has note encounters and diagnoses + */ + List getVisitsWithNotesAndDiagnosesByPatient(Patient patient, int startIndex, int limit); } diff --git a/api/src/main/java/org/openmrs/module/emrapi/db/EmrApiDAOImpl.java b/api/src/main/java/org/openmrs/module/emrapi/db/EmrApiDAOImpl.java index 908bbb8f9..4947d3d34 100644 --- a/api/src/main/java/org/openmrs/module/emrapi/db/EmrApiDAOImpl.java +++ b/api/src/main/java/org/openmrs/module/emrapi/db/EmrApiDAOImpl.java @@ -1,29 +1,41 @@ package org.openmrs.module.emrapi.db; +import lombok.Setter; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Query; +import org.openmrs.Diagnosis; +import org.openmrs.EncounterType; +import org.openmrs.Patient; +import org.openmrs.Visit; import org.openmrs.api.db.hibernate.DbSessionFactory; +import org.openmrs.module.emrapi.EmrApiProperties; +import org.openmrs.module.emrapi.visit.VisitWithDiagnoses; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; public class EmrApiDAOImpl implements EmrApiDAO { protected final Log log = LogFactory.getLog(getClass()); + @Setter private DbSessionFactory sessionFactory; - - public void setSessionFactory(DbSessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - } - - @Override + + @Setter + private EmrApiProperties emrApiProperties; + + @Override @SuppressWarnings("unchecked") public List executeHql(String queryString, Map parameters, Class clazz) { Query query = sessionFactory.getCurrentSession().createQuery(queryString); @@ -55,4 +67,54 @@ public List executeHqlFromResource(String resource, Map p } return executeHql(hql, parameters, clazz); } + + public List getVisitsWithNotesAndDiagnosesByPatient(Patient patient, int startIndex, int limit) { + + String visitNoteEncounterTypeUuid = emrApiProperties.getVisitNoteEncounterType().getUuid(); + + String hqlVisit="SELECT DISTINCT v FROM Visit v " + + "LEFT JOIN FETCH v.encounters enc " + + "LEFT JOIN enc.encounterType et " + + "WHERE v.patient.id = :patientId " + + "AND (et.uuid = :encounterTypeUuid OR enc IS NULL) " + + "ORDER BY v.startDatetime DESC"; + + Query visitQuery = sessionFactory.getCurrentSession().createQuery(hqlVisit); + + visitQuery.setParameter("patientId", patient.getId()); + visitQuery.setParameter("encounterTypeUuid", visitNoteEncounterTypeUuid); + visitQuery.setFirstResult(startIndex); + visitQuery.setMaxResults(limit); + + List visits = visitQuery.list(); + + String hqlDiagnosis = "SELECT DISTINCT diag FROM Diagnosis diag " + + "JOIN diag.encounter e " + + "WHERE e.visit.id IN :visitIds"; + + List visitIds = visits.stream() + .map(Visit::getId) + .collect(Collectors.toList()); + + List diagnoses = sessionFactory.getCurrentSession() + .createQuery(hqlDiagnosis) + .setParameterList("visitIds", visitIds) + .list(); + + Map> visitToDiagnosesMap = new HashMap<>(); + for (Diagnosis diagnosis : diagnoses) { + Visit visit = diagnosis.getEncounter().getVisit(); + visitToDiagnosesMap + .computeIfAbsent(visit, v -> new HashSet<>()) + .add(diagnosis); + } + + List visitWithDiagnoses = visits.stream() + .sorted(Comparator.comparing(Visit::getStartDatetime).reversed()) + .map(visit -> new VisitWithDiagnoses(visit, visitToDiagnosesMap.getOrDefault(visit, new HashSet<>()))) + .collect(Collectors.toList()); + + return visitWithDiagnoses; + } + } diff --git a/api/src/main/java/org/openmrs/module/emrapi/visit/EmrApiVisitService.java b/api/src/main/java/org/openmrs/module/emrapi/visit/EmrApiVisitService.java new file mode 100644 index 000000000..4898d07de --- /dev/null +++ b/api/src/main/java/org/openmrs/module/emrapi/visit/EmrApiVisitService.java @@ -0,0 +1,13 @@ +package org.openmrs.module.emrapi.visit; + +import java.util.List; + +public interface EmrApiVisitService { + /** + * Fetches visits with note encounters and diagnoses of a patient by patient ID. + * + * @param patientUuid the UUID of the patient + * @return a list of visits that has note encounters and diagnoses + */ + List getVisitsWithNotesAndDiagnosesByPatient(String patientUuid, int startIndex, int limit); +} diff --git a/api/src/main/java/org/openmrs/module/emrapi/visit/EmrApiVisitServiceImpl.java b/api/src/main/java/org/openmrs/module/emrapi/visit/EmrApiVisitServiceImpl.java new file mode 100644 index 000000000..ee6742a50 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/emrapi/visit/EmrApiVisitServiceImpl.java @@ -0,0 +1,31 @@ +package org.openmrs.module.emrapi.visit; + +import lombok.Setter; +import org.hibernate.ObjectNotFoundException; +import org.openmrs.Patient; +import org.openmrs.api.PatientService; +import org.openmrs.api.impl.BaseOpenmrsService; +import org.openmrs.module.emrapi.db.EmrApiDAO; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Setter +@Service +public class EmrApiVisitServiceImpl extends BaseOpenmrsService implements EmrApiVisitService { + + PatientService patientService; + EmrApiDAO emrApiDAO; + + @Override + public List getVisitsWithNotesAndDiagnosesByPatient(String patientUuid, int startIndex, int limit) { + + Patient patient = patientService.getPatientByUuid(patientUuid); + + if (patient == null) { + throw new ObjectNotFoundException("No patient found with uuid " + patientUuid, Patient.class.getName()); + } + + return emrApiDAO.getVisitsWithNotesAndDiagnosesByPatient(patient, startIndex, limit); + } +} diff --git a/api/src/main/java/org/openmrs/module/emrapi/visit/VisitWithDiagnoses.java b/api/src/main/java/org/openmrs/module/emrapi/visit/VisitWithDiagnoses.java new file mode 100644 index 000000000..c17b45e65 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/emrapi/visit/VisitWithDiagnoses.java @@ -0,0 +1,22 @@ +package org.openmrs.module.emrapi.visit; + +import lombok.Getter; +import lombok.Setter; +import org.openmrs.Diagnosis; +import org.openmrs.Visit; + +import java.util.Set; + +@Setter +@Getter +public class VisitWithDiagnoses { + + private Visit visit; + private Set diagnoses; + + public VisitWithDiagnoses(Visit visit, Set diagnoses) { + this.visit = visit; + this.diagnoses = diagnoses; + } + +} diff --git a/api/src/main/resources/moduleApplicationContext.xml b/api/src/main/resources/moduleApplicationContext.xml index 8328fe08d..5af81b1a5 100644 --- a/api/src/main/resources/moduleApplicationContext.xml +++ b/api/src/main/resources/moduleApplicationContext.xml @@ -315,10 +315,19 @@ + + + + + + + + + diff --git a/api/src/test/java/org/openmrs/module/emrapi/db/EmrApiDaoTest.java b/api/src/test/java/org/openmrs/module/emrapi/db/EmrApiDaoTest.java new file mode 100644 index 000000000..0919a5ebf --- /dev/null +++ b/api/src/test/java/org/openmrs/module/emrapi/db/EmrApiDaoTest.java @@ -0,0 +1,90 @@ +package org.openmrs.module.emrapi.db; + +import org.junit.Before; +import org.junit.Test; +import org.openmrs.Diagnosis; +import org.openmrs.Encounter; +import org.openmrs.Patient; +import org.openmrs.module.emrapi.EmrApiContextSensitiveTest; +import org.openmrs.module.emrapi.visit.VisitWithDiagnoses; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class EmrApiDaoTest extends EmrApiContextSensitiveTest { + + @Autowired + private EmrApiDAO emrApiDAO; + + @Before + public void setup() { + executeDataSet("baseTestDataset.xml"); + executeDataSet("pastVisitSetup.xml"); + } + + @Test + public void shouldFetchVisitsWithNotesAndDiagnosesByPatientId() { + String visitNoteEncounterTypeUuid = "d7151f82-c1f3-4152-a605-2f9ea7414a79"; + + Patient patient = new Patient(); + patient.setPatientId(109); + + List visits = emrApiDAO.getVisitsWithNotesAndDiagnosesByPatient(patient,0,10); + assertNotNull(visits); + assert visits.size() == 3; + + VisitWithDiagnoses firstVisit = visits.get(2); + Set firstVisitEncounters = firstVisit.getVisit().getEncounters(); + Set firstVisitDiagnoses = firstVisit.getDiagnoses(); + + assert firstVisit.getVisit().getId() == 1014; + assert firstVisit.getVisit().getPatient().getPatientId() == 109; + assert firstVisitEncounters.size() == 2; + assert firstVisitDiagnoses.size() == 3; + + for (Encounter encounter : firstVisitEncounters) { + assert encounter.getEncounterType().getUuid().equals(visitNoteEncounterTypeUuid); + } + + VisitWithDiagnoses secondVisit = visits.get(1); + Set secondVisitEncounters = secondVisit.getVisit().getEncounters(); + Set secondVisitDiagnoses = secondVisit.getDiagnoses(); + + assert secondVisit.getVisit().getId() == 1015; + assert secondVisit.getVisit().getPatient().getPatientId() == 109; + assert secondVisitEncounters.size() == 1; + assert secondVisitDiagnoses.size() == 2; + + for (Encounter encounter : secondVisitEncounters) { + assert encounter.getEncounterType().getUuid().equals(visitNoteEncounterTypeUuid); + } + + VisitWithDiagnoses thirdVisit = visits.get(0); + Set thirdVisitEncounters = thirdVisit.getVisit().getEncounters(); + Set thirdVisitDiagnoses = thirdVisit.getDiagnoses(); + + assert thirdVisit.getVisit().getId() == 1017; + assert thirdVisit.getVisit().getPatient().getPatientId() == 109; + assert thirdVisitEncounters.isEmpty(); + assert thirdVisitDiagnoses.isEmpty(); + + } + + @Test + public void shouldFetchVisitsWithNotesAndDiagnosesWithPagination() { + Patient patient = new Patient(); + patient.setPatientId(109); + + List visits = emrApiDAO.getVisitsWithNotesAndDiagnosesByPatient(patient,0,1); + assertNotNull(visits); + assert visits.size() == 1; + + VisitWithDiagnoses mostRecentVisit = visits.get(0); + assert mostRecentVisit.getVisit().getId() == 1017; + } + +} diff --git a/api/src/test/resources/metadata/pastVisitSetup.xml b/api/src/test/resources/metadata/pastVisitSetup.xml new file mode 100644 index 000000000..1aace64d0 --- /dev/null +++ b/api/src/test/resources/metadata/pastVisitSetup.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/api/src/test/resources/pastVisitSetup.xml b/api/src/test/resources/pastVisitSetup.xml new file mode 100644 index 000000000..0cd850bfc --- /dev/null +++ b/api/src/test/resources/pastVisitSetup.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/omod/src/main/java/org/openmrs/module/emrapi/web/controller/VisitController.java b/omod/src/main/java/org/openmrs/module/emrapi/web/controller/VisitController.java new file mode 100644 index 000000000..a2939450f --- /dev/null +++ b/omod/src/main/java/org/openmrs/module/emrapi/web/controller/VisitController.java @@ -0,0 +1,62 @@ +package org.openmrs.module.emrapi.web.controller; + +import lombok.Setter; +import org.openmrs.Diagnosis; +import org.openmrs.Patient; +import org.openmrs.module.emrapi.visit.VisitWithDiagnoses; +import org.openmrs.module.emrapi.visit.EmrApiVisitService; +import org.openmrs.module.webservices.rest.SimpleObject; +import org.openmrs.module.webservices.rest.web.ConversionUtil; +import org.openmrs.module.webservices.rest.web.RequestContext; +import org.openmrs.module.webservices.rest.web.RestUtil; +import org.openmrs.module.webservices.rest.web.representation.Representation; +import org.openmrs.module.webservices.rest.web.resource.impl.NeedsPaging; +import org.openmrs.module.webservices.rest.web.v1_0.controller.BaseRestController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; + +@Setter +@Controller +public class VisitController extends BaseRestController { + + EmrApiVisitService emrApiVisitService; + + @RequestMapping(method = RequestMethod.GET, value = "/rest/**/emrapi/patient/{patientUuid}/visitWithNotesAndDiagnoses") + public ResponseEntity getVisitsWithDiagnosesByPatient( + HttpServletRequest request, + HttpServletResponse response, + @PathVariable("patientUuid") String patientUuid) { + + RequestContext context = RestUtil.getRequestContext(request, response, Representation.DEFAULT); + List visitsEntries; + visitsEntries = emrApiVisitService.getVisitsWithNotesAndDiagnosesByPatient(patientUuid, context.getStartIndex(), context.getLimit()); + + // Convert the visits and diagnoses to SimpleObjects + List convertedVisits = new ArrayList<>(); + for (VisitWithDiagnoses visitEntry : visitsEntries) { + SimpleObject visitEntryObject = new SimpleObject(); + SimpleObject visitObject = (SimpleObject) ConversionUtil.convertToRepresentation(visitEntry.getVisit(), context.getRepresentation()); + List convertedDiagnoses = new ArrayList<>(); + + for (Diagnosis diagnosis : visitEntry.getDiagnoses()) { + convertedDiagnoses.add((SimpleObject) ConversionUtil.convertToRepresentation(diagnosis, context.getRepresentation())); + } + visitEntryObject.put("visit", visitObject); + visitEntryObject.put("diagnoses", convertedDiagnoses); + + convertedVisits.add(visitEntryObject); + } + + return new ResponseEntity<>(new NeedsPaging<>(convertedVisits, context), HttpStatus.OK); + } +} diff --git a/omod/src/main/resources/moduleApplicationContext.xml b/omod/src/main/resources/moduleApplicationContext.xml new file mode 100644 index 000000000..86bfce697 --- /dev/null +++ b/omod/src/main/resources/moduleApplicationContext.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/omod/src/test/java/org/openmrs/module/emrapi/web/controller/VisitControllerTest.java b/omod/src/test/java/org/openmrs/module/emrapi/web/controller/VisitControllerTest.java new file mode 100644 index 000000000..c2a7cc91c --- /dev/null +++ b/omod/src/test/java/org/openmrs/module/emrapi/web/controller/VisitControllerTest.java @@ -0,0 +1,123 @@ +package org.openmrs.module.emrapi.web.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; +import org.junit.Test; +import org.openmrs.web.test.BaseModuleWebContextSensitiveTest; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertNotNull; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class VisitControllerTest extends BaseModuleWebContextSensitiveTest { + + @Autowired + private ObjectFactory controllerFactory; + + private MockMvc mockMvc; + + @Before + public void setUp() { + executeDataSet("pastVisitSetup.xml"); + mockMvc = MockMvcBuilders.standaloneSetup(controllerFactory.getObject()).build(); + } + + @Test + public void shouldGetVisitsWithNotesAndDiagnosesByPatient() throws Exception { + + String patientUuid = "8604d42e-3ca8-11e3-bf2b-0d0c09861e97"; + String firstVisitUuid = "1esd5218-6b78-11e0-93c3-18a905e044dc"; + String secondVisitUuid = "1c72e1ac-9b18-11e0-93c3-18a905e044dc"; + String thirdVisitUuid = "3c72f2bc-9b18-11e0-93c3-18a905e044ec"; + + MvcResult response = mockMvc.perform(get("/rest/v1/emrapi/patient/" + patientUuid + "/visitWithNotesAndDiagnoses?patient="+patientUuid) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + String jsonResponse = response.getResponse().getContentAsString(); + ObjectMapper objectMapper = new ObjectMapper(); + Map result = objectMapper.readValue(jsonResponse, Map.class); + + assertNotNull(result); + assert result.get("totalCount").equals(3); + + List> visitEntries = (List>) result.get("pageOfResults"); + assert visitEntries.size() == 3; + + // extract the first visit and check its properties + Map firstVisitEntry = visitEntries.get(2); + Map firstVisit = (Map) firstVisitEntry.get("visit"); + List> firstVisitEncounters = (List>) firstVisit.get("encounters"); + List> firstVisitDiagnoses = (List>) firstVisitEntry.get("diagnoses"); + + assert firstVisit.get("uuid").equals(firstVisitUuid); + assert firstVisitEncounters.size() == 2; + assert firstVisitDiagnoses.size() == 3; + + for (Map encounter : firstVisitEncounters) { + assert ((String) encounter.get("display")).startsWith("Visit Note"); + } + + // extract the second visit and check its properties + Map secondVisitEntry = visitEntries.get(1); + Map secondVisit = (Map) secondVisitEntry.get("visit"); + List> secondVisitEncounters = (List>) secondVisit.get("encounters"); + List> secondVisitDiagnoses = (List>) secondVisitEntry.get("diagnoses"); + + assert secondVisit.get("uuid").equals(secondVisitUuid); + assert secondVisitEncounters.size() == 1; + assert secondVisitDiagnoses.size() == 2; + + for (Map encounter : secondVisitEncounters) { + assert ((String) encounter.get("display")).startsWith("Visit Note"); + } + + // extract the third visit and check its properties (no notes or diagnoses) + Map thirdVisitEntry = visitEntries.get(0); + Map thirdVisit = (Map) thirdVisitEntry.get("visit"); + List> thirdVisitEncounters = (List>) thirdVisit.get("encounters"); + List> thirdVisitDiagnoses = (List>) thirdVisitEntry.get("diagnoses"); + + assert thirdVisit.get("uuid").equals(thirdVisitUuid); + assert thirdVisitEncounters.isEmpty(); + assert thirdVisitDiagnoses.isEmpty(); + } + + @Test + public void shouldGetVisitsWithNotesAndDiagnosesByPatientWithPagination() throws Exception { + + String patientUuid = "8604d42e-3ca8-11e3-bf2b-0d0c09861e97"; + String mostRecentVisitUuid = "3c72f2bc-9b18-11e0-93c3-18a905e044ec"; + + MvcResult response = mockMvc.perform(get("/rest/v1/emrapi/patient/" + patientUuid+"/visitWithNotesAndDiagnoses") + .param("startIndex", "0") + .param("limit", "1") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + String jsonResponse = response.getResponse().getContentAsString(); + ObjectMapper objectMapper = new ObjectMapper(); + Map result = objectMapper.readValue(jsonResponse, Map.class); + + assertNotNull(result); + assert result.get("totalCount").equals(1); + + List> visitsEntries = (List>) result.get("pageOfResults"); + assert visitsEntries.size() == 1; + + Map recentVisitEntry = visitsEntries.get(0); + Map recentVisit = (Map) recentVisitEntry.get("visit"); + assert recentVisit.get("uuid").equals(mostRecentVisitUuid); + } +} diff --git a/omod/src/test/resources/pastVisitSetup.xml b/omod/src/test/resources/pastVisitSetup.xml new file mode 100644 index 000000000..970c324b6 --- /dev/null +++ b/omod/src/test/resources/pastVisitSetup.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +