Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

import javax.validation.constraints.NotNull;

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

import org.openmrs.Location;
Expand Down Expand Up @@ -129,6 +131,20 @@ public interface QueueEntryService {
@Authorized({ PrivilegeConstants.GET_QUEUE_ENTRIES })
List<QueueEntry> getQueueEntries(@NotNull QueueEntrySearchCriteria searchCriteria);

/**
* Paginated variant of {@link #getQueueEntries(QueueEntrySearchCriteria)}. Nulls disable the
* corresponding pagination bound.
*/
@Authorized({ PrivilegeConstants.GET_QUEUE_ENTRIES })
List<QueueEntry> getQueueEntries(@NotNull QueueEntrySearchCriteria searchCriteria, Integer startIndex, Integer limit);

/**
* Batch-resolves the uuid of the previous queue entry for each input entry that has a non-null
* {@code queueComingFrom}. Entries without a resolvable predecessor are absent from the result.
*/
@Authorized({ PrivilegeConstants.GET_QUEUE_ENTRIES })
Map<QueueEntry, String> getPreviousQueueEntryUuids(Collection<QueueEntry> entries);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kindly to follow the 3-layer architectural pattern (Controller -> Service -> DAO) mentioned in the PR description, please expose
this method getPreviousQueueEntryUuids in the Service interface to ensures that the logic is available to the REST
controller while maintaining proper security and transaction boundaries. please don't forget to add the @Authorized annotation for
PrivilegeConstants.GET_QUEUE_ENTRIES."

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already in place, the method is declared on QueueEntryService at line 146 with @Authorized({ PrivilegeConstants.GET_QUEUE_ENTRIES }) on line 145. Let me know if you're seeing something different.


/**
* @return {@link Long} count of queue entries that match the given
* %{@link QueueEntrySearchCriteria}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

import javax.validation.constraints.NotNull;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.openmrs.module.queue.api.search.QueueEntrySearchCriteria;
import org.openmrs.module.queue.model.QueueEntry;
Expand All @@ -24,6 +26,20 @@ public interface QueueEntryDao extends BaseQueueDao<QueueEntry> {
*/
List<QueueEntry> getQueueEntries(@NotNull QueueEntrySearchCriteria searchCriteria);

/**
* Paginated variant of {@link #getQueueEntries(QueueEntrySearchCriteria)}. Nulls disable the
* corresponding pagination bound.
*/
List<QueueEntry> getQueueEntries(@NotNull QueueEntrySearchCriteria searchCriteria, Integer startIndex, Integer limit);

/**
* Batch-resolves the uuid of the previous queue entry for each of the given entries (i.e. the entry
* the patient was transitioned from, identified by matching patient + visit, queue =
* input.queueComingFrom, endedAt = input.startedAt). Entries with no {@code queueComingFrom} are
* absent from the returned map.
*/
Map<QueueEntry, String> getPreviousQueueEntryUuids(Collection<QueueEntry> entries);

/**
* @return {@link Long} of the number of queue entries that match the given
* %{@link QueueEntrySearchCriteria}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.Session;
Expand All @@ -26,6 +32,7 @@
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.openmrs.Patient;
import org.openmrs.Visit;
import org.openmrs.module.queue.api.dao.QueueEntryDao;
import org.openmrs.module.queue.api.search.QueueEntrySearchCriteria;
import org.openmrs.module.queue.model.Queue;
Expand All @@ -41,14 +48,87 @@ public QueueEntryDaoImpl(@Qualifier("sessionFactory") SessionFactory sessionFact

@Override
public List<QueueEntry> getQueueEntries(QueueEntrySearchCriteria searchCriteria) {
return getQueueEntries(searchCriteria, null, null);
}

@Override
public List<QueueEntry> getQueueEntries(QueueEntrySearchCriteria searchCriteria, Integer startIndex, Integer limit) {
Criteria c = createCriteriaFromSearchCriteria(searchCriteria);
c.addOrder(Order.desc("qe.sortWeight"));
c.addOrder(Order.asc("qe.startedAt"));
c.addOrder(Order.asc("qe.dateCreated"));
c.addOrder(Order.asc("qe.queueEntryId"));
if (startIndex != null) {
c.setFirstResult(startIndex);
}
if (limit != null) {
c.setMaxResults(limit);
}
return c.list();
}

@Override
public Map<QueueEntry, String> getPreviousQueueEntryUuids(Collection<QueueEntry> entries) {
if (entries == null || entries.isEmpty()) {
return Collections.emptyMap();
}

List<QueueEntry> candidates = new ArrayList<>();
Set<Queue> queuesComingFrom = new HashSet<>();
Set<Patient> patients = new HashSet<>();
Set<Visit> visits = new HashSet<>();
Set<Date> startedAts = new HashSet<>();
for (QueueEntry e : entries) {
if (e.getQueueComingFrom() == null || e.getPatient() == null || e.getStartedAt() == null) {
continue;
}
candidates.add(e);
queuesComingFrom.add(e.getQueueComingFrom());
patients.add(e.getPatient());
startedAts.add(e.getStartedAt());
if (e.getVisit() != null) {
visits.add(e.getVisit());
}
}
if (candidates.isEmpty()) {
return Collections.emptyMap();
}

// Filter via IN clauses; post-match the composite tuple in Java to avoid an OR-AND explosion.
StringBuilder hql = new StringBuilder();
hql.append("FROM QueueEntry prev WHERE prev.voided = false ");
hql.append("AND prev.endedAt IN (:startedAts) ");
hql.append("AND prev.patient IN (:patients) ");
hql.append("AND prev.queue IN (:queuesComingFrom)");
if (!visits.isEmpty()) {
hql.append(" AND (prev.visit IS NULL OR prev.visit IN (:visits))");
}

javax.persistence.Query query = getCurrentSession().createQuery(hql.toString());
query.setParameter("startedAts", startedAts);
query.setParameter("patients", patients);
query.setParameter("queuesComingFrom", queuesComingFrom);
if (!visits.isEmpty()) {
query.setParameter("visits", visits);
}
List<QueueEntry> candidatesFromDb = query.getResultList();

Map<PrevKey, QueueEntry> index = new HashMap<>();
for (QueueEntry prev : candidatesFromDb) {
index.put(new PrevKey(prev.getPatient(), prev.getVisit(), prev.getQueue(), prev.getEndedAt()), prev);
}

Map<QueueEntry, String> result = new LinkedHashMap<>();
for (QueueEntry curr : candidates) {
QueueEntry prev = index
.get(new PrevKey(curr.getPatient(), curr.getVisit(), curr.getQueueComingFrom(), curr.getStartedAt()));
if (prev != null) {
result.put(curr, prev.getUuid());
}
}
return result;
}

@Override
public Long getCountOfQueueEntries(QueueEntrySearchCriteria searchCriteria) {
Criteria criteria = createCriteriaFromSearchCriteria(searchCriteria);
Expand Down Expand Up @@ -176,4 +256,40 @@ private Criteria createCriteriaFromSearchCriteria(QueueEntrySearchCriteria searc
}
return c;
}

private static final class PrevKey {

private final Patient patient;

private final Visit visit;

private final Queue queue;

private final Date endedAt;

PrevKey(Patient patient, Visit visit, Queue queue, Date endedAt) {
this.patient = patient;
this.visit = visit;
this.queue = queue;
this.endedAt = endedAt;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof PrevKey)) {
return false;
}
PrevKey k = (PrevKey) o;
return java.util.Objects.equals(patient, k.patient) && java.util.Objects.equals(visit, k.visit)
&& java.util.Objects.equals(queue, k.queue) && java.util.Objects.equals(endedAt, k.endedAt);
}

@Override
public int hashCode() {
return java.util.Objects.hash(patient, visit, queue, endedAt);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import lombok.Setter;
Expand Down Expand Up @@ -215,6 +217,18 @@ public List<QueueEntry> getQueueEntries(QueueEntrySearchCriteria searchCriteria)
return dao.getQueueEntries(searchCriteria);
}

@Override
@Transactional(readOnly = true)
public List<QueueEntry> getQueueEntries(QueueEntrySearchCriteria searchCriteria, Integer startIndex, Integer limit) {
return dao.getQueueEntries(searchCriteria, startIndex, limit);
}

@Override
@Transactional(readOnly = true)
public Map<QueueEntry, String> getPreviousQueueEntryUuids(Collection<QueueEntry> entries) {
return dao.getPreviousQueueEntryUuids(entries);
}
Comment on lines +228 to +230
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kindly provide the implementation for getPreviousQueueEntryUuids by delegating the call to the DAO layer. This maintains
consistency with the patterns used in emrapi and ensures the service layer remains the primary entry point for business logic.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation at lines 228-230 delegates to the DAO - return dao.getPreviousQueueEntryUuids(entries); with @Override + @Transactional(readOnly = true) on the lines above. Happy to adjust if you'd like a different pattern.


@Override
@Transactional(readOnly = true)
public Long getCountOfQueueEntries(QueueEntrySearchCriteria searchCriteria) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.junit.Before;
Expand Down Expand Up @@ -486,6 +487,55 @@ public void getOverlappingQueueEntries_shouldReturnEntriesOverlappingWithGivenRa
assertThat(noOverlapResults, hasSize(0));
}

@Test
public void getQueueEntries_shouldRespectStartIndexAndLimit() {
// Full result set (active only) is 4 entries, sorted by sortWeight DESC then startedAt ASC:
// → ids [3 (sw=20), 2 (sw=10), 1 (sw=0, started earlier), 4 (sw=0, started later)]
List<QueueEntry> all = dao.getQueueEntries(criteria);
assertThat(all, hasSize(4));

List<QueueEntry> firstPage = dao.getQueueEntries(criteria, 0, 2);
assertThat(firstPage, hasSize(2));
assertThat(firstPage.get(0).getQueueEntryId(), is(all.get(0).getQueueEntryId()));
assertThat(firstPage.get(1).getQueueEntryId(), is(all.get(1).getQueueEntryId()));

List<QueueEntry> secondPage = dao.getQueueEntries(criteria, 2, 2);
assertThat(secondPage, hasSize(2));
assertThat(secondPage.get(0).getQueueEntryId(), is(all.get(2).getQueueEntryId()));
assertThat(secondPage.get(1).getQueueEntryId(), is(all.get(3).getQueueEntryId()));

// Past the end → empty
assertThat(dao.getQueueEntries(criteria, 10, 5), hasSize(0));

// Nulls disable bounds — equivalent to the unpaginated call
assertThat(dao.getQueueEntries(criteria, null, null), hasSize(4));
}

@Test
public void getPreviousQueueEntryUuids_shouldReturnEmptyForNullOrEmptyInput() {
assertThat(dao.getPreviousQueueEntryUuids(null).isEmpty(), is(true));
assertThat(dao.getPreviousQueueEntryUuids(Collections.emptyList()).isEmpty(), is(true));
}

@Test
public void getPreviousQueueEntryUuids_shouldReturnUuidForEntriesWithMatchingPredecessorOnly() {
// In the dataset, entry 2 was transitioned from queue 1 — entry 1 is its predecessor
// (same patient=100, same visit=101, prev.endedAt = entry2.startedAt).
QueueEntry entry1 = dao.get(1).orElseThrow(IllegalStateException::new);
QueueEntry entry2 = dao.get(2).orElseThrow(IllegalStateException::new);
QueueEntry entry3 = dao.get(3).orElseThrow(IllegalStateException::new);
QueueEntry entry4 = dao.get(4).orElseThrow(IllegalStateException::new);

Map<QueueEntry, String> result = dao.getPreviousQueueEntryUuids(Arrays.asList(entry1, entry2, entry3, entry4));

// Only entry 2 has a queueComingFrom that resolves to a real predecessor.
assertThat(result.size(), is(1));
assertThat(result.get(entry2), is(entry1.getUuid()));
assertThat(result.containsKey(entry1), is(false));
assertThat(result.containsKey(entry3), is(false));
assertThat(result.containsKey(entry4), is(false));
}

/**
* Utility method that tests criteria against both DAO methods to getQueueEntries and
* getCountOfQueueEntries
Expand Down
Loading