diff --git a/api/src/main/java/org/openmrs/module/queue/api/dao/impl/QueueEntryDaoImpl.java b/api/src/main/java/org/openmrs/module/queue/api/dao/impl/QueueEntryDaoImpl.java index b982ecc..72346d2 100644 --- a/api/src/main/java/org/openmrs/module/queue/api/dao/impl/QueueEntryDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/queue/api/dao/impl/QueueEntryDaoImpl.java @@ -87,6 +87,12 @@ public List getOverlappingQueueEntries(QueueEntrySearchCriteria sear predicates.add(cb.or(root.get("endedAt").isNull(), cb.greaterThan(root.get("endedAt"), startedAt))); } + Date endedAt = searchCriteria.getEndedOn(); + if (endedAt != null) { + // any queue entries that started before this queue entry ends + predicates.add(cb.lessThan(root.get("startedAt"), endedAt)); + } + query.where(cb.and(predicates.toArray(new Predicate[0]))); return session.createQuery(query).list(); diff --git a/api/src/main/java/org/openmrs/module/queue/validators/QueueEntryValidator.java b/api/src/main/java/org/openmrs/module/queue/validators/QueueEntryValidator.java index 279c6b8..2da02b7 100644 --- a/api/src/main/java/org/openmrs/module/queue/validators/QueueEntryValidator.java +++ b/api/src/main/java/org/openmrs/module/queue/validators/QueueEntryValidator.java @@ -104,6 +104,9 @@ public void validate(Object target, Errors errors) { private boolean isDuplicate(QueueEntry queueEntry, QueueEntryService queueEntryService) { List queueEntries = queueEntryService.getOverlappingQueueEntries(queueEntry.getPatient(), queueEntry.getQueue(), queueEntry.getStartedAt(), queueEntry.getEndedAt()); + // cascade voids (e.g. voiding a patient) may not yet be flushed to the DB when this runs, + // so voided entries can slip through the DAO-level filter — remove them here as a safety net + queueEntries.removeIf(QueueEntry::getVoided); // if we aren't checking an existing queue entry, any overlaps are "duplicates" if (queueEntry.getId() == null) { diff --git a/integration-tests/src/test/java/org/openmrs/module/queue/api/dao/QueueEntryDaoTest.java b/integration-tests/src/test/java/org/openmrs/module/queue/api/dao/QueueEntryDaoTest.java index ae773fb..4cf4909 100644 --- a/integration-tests/src/test/java/org/openmrs/module/queue/api/dao/QueueEntryDaoTest.java +++ b/integration-tests/src/test/java/org/openmrs/module/queue/api/dao/QueueEntryDaoTest.java @@ -426,6 +426,35 @@ public void shouldSearchAndCountQueueEntriesEndedOnOrAfterDate() { assertResults(criteria, 2, 3); } + @Test + // Dataset entries for queue=3, patient=2: only entry 4 [2022-03-02 16:40:56 → 2022-03-02 18:41:56] + public void getOverlappingQueueEntries_shouldReturnEntriesOverlappingWithGivenRange() { + Queue queue3 = services.getQueueService().getQueueById(3).orElseThrow(IllegalStateException::new); + Patient patient2 = services.getPatientService().getPatient(2); + + // Open-ended new entry: endedAt=null — entry 4 overlaps (its endedAt > new startedAt) + QueueEntrySearchCriteria openCriteria = QueueEntrySearchCriteria.builder().queues(Collections.singletonList(queue3)) + .patient(patient2).startedOn(date("2022-03-02 10:00:00")).build(); + List openResults = dao.getOverlappingQueueEntries(openCriteria); + assertThat(openResults, hasSize(1)); + assertThat(openResults.get(0).getQueueEntryId(), is(4)); + + // Finite new entry whose range overlaps entry 4: new [10:00 → 17:00], entry 4 starts at 16:40 < 17:00 + QueueEntrySearchCriteria overlapCriteria = QueueEntrySearchCriteria.builder() + .queues(Collections.singletonList(queue3)).patient(patient2).startedOn(date("2022-03-02 10:00:00")) + .endedOn(date("2022-03-02 17:00:00")).build(); + List overlapResults = dao.getOverlappingQueueEntries(overlapCriteria); + assertThat(overlapResults, hasSize(1)); + assertThat(overlapResults.get(0).getQueueEntryId(), is(4)); + + // Finite new entry that ends BEFORE entry 4 starts: new [10:00 → 15:00], entry 4 starts at 16:40 — no overlap + QueueEntrySearchCriteria noOverlapCriteria = QueueEntrySearchCriteria.builder() + .queues(Collections.singletonList(queue3)).patient(patient2).startedOn(date("2022-03-02 10:00:00")) + .endedOn(date("2022-03-02 15:00:00")).build(); + List noOverlapResults = dao.getOverlappingQueueEntries(noOverlapCriteria); + assertThat(noOverlapResults, hasSize(0)); + } + /** * Utility method that tests criteria against both DAO methods to getQueueEntries and * getCountOfQueueEntries