Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

O3-2448 - Visit queue number customization #44

Closed
wants to merge 5 commits into from
Closed
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 @@ -14,11 +14,7 @@
import java.util.Collection;
import java.util.Optional;

import org.openmrs.Location;
import org.openmrs.Visit;
import org.openmrs.VisitAttributeType;
import org.openmrs.api.APIException;
import org.openmrs.module.queue.model.Queue;
import org.openmrs.module.queue.model.QueueEntry;

public interface QueueEntryService {
Expand Down Expand Up @@ -63,6 +59,11 @@ public interface QueueEntryService {
*/
void purgeQueueEntry(@NotNull QueueEntry queueEntry) throws APIException;

/**
* Return all QueueEntries that are active (non-voided, not ended)
*/
Collection<QueueEntry> getActiveQueueEntries();

/**
* Search for queue entries by conceptStatus
*
Expand All @@ -80,14 +81,6 @@ public interface QueueEntryService {
*/
Long getQueueEntriesCountByStatus(@NotNull String status);

/**
* @param location
* @param queue
* @return VisitQueueNumber - used to identify patients in the queue instead of using patient name
*/
String generateVisitQueueNumber(@NotNull Location location, @NotNull Queue queue, @NotNull Visit visit,
@NotNull VisitAttributeType visitAttributeType);

/**
* Closes all active queue entries
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.openmrs.api.APIException;
import org.openmrs.module.queue.model.VisitQueueEntry;
import org.openmrs.module.queue.processor.VisitQueueEntryProcessor;

public interface VisitQueueEntryService {

Expand Down Expand Up @@ -106,9 +107,13 @@ Collection<VisitQueueEntry> findVisitQueueEntries(String status, String service,
*
* @param service concept service name
* @param status concept status name
* @param locaitonUuid location uuid
* @param locationUUid location uuid
* @return {@link Long} count of visit queue entries
*/
Long getVisitQueueEntriesCountByLocationStatusAndService(@NotNull String status, String service, String locationUUid);

/**
* @param visitQueueEntryProcessor the visitQueueEntryProcessor to set
*/
void setVisitQueueEntryProcessor(VisitQueueEntryProcessor visitQueueEntryProcessor);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
import java.util.List;

import org.openmrs.Auditable;
import org.openmrs.Location;
import org.openmrs.OpenmrsObject;
import org.openmrs.api.ConceptNameType;
import org.openmrs.module.queue.model.Queue;
import org.openmrs.module.queue.model.QueueEntry;

public interface QueueEntryDao<Q extends OpenmrsObject & Auditable> extends BaseQueueDao<Q> {
Expand All @@ -44,13 +42,6 @@ Collection<QueueEntry> SearchQueueEntriesByConceptStatus(@NotNull String concept
Long getQueueEntriesCountByConceptStatus(@NotNull String conceptStatus, ConceptNameType conceptNameType,
boolean localePreferred);

/**
* @param location
* @param queue
* @return VisitQueueNumber - used to identify patients in the queue instead of using patient name
*/
String generateVisitQueueNumber(@NotNull Location location, @NotNull Queue queue);

/**
* Gets active queue entries
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,19 @@
*/
package org.openmrs.module.queue.api.dao.impl;

import static org.hibernate.criterion.Restrictions.eq;

import javax.validation.constraints.NotNull;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.openmrs.Location;
import org.openmrs.api.ConceptNameType;
import org.openmrs.module.queue.api.dao.QueueEntryDao;
import org.openmrs.module.queue.model.Queue;
import org.openmrs.module.queue.model.QueueEntry;
import org.openmrs.module.queue.model.VisitQueueEntry;
import org.springframework.beans.factory.annotation.Qualifier;

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -74,42 +64,6 @@ public Long getQueueEntriesCountByConceptStatus(@NotNull String conceptStatus, C
return (Long) criteria.uniqueResult();
}

@Override
Copy link
Member Author

Choose a reason for hiding this comment

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

The logic here (combined with the logic in the service below) has essentially been moved to the VisitAttributeQueueNumberProcessor. Implementations that want to retain this behavior can enable that processor by setting it on the service from a downstream module. That said, if we don't feel this is something we need/want to keep in the main codebase, I'd be supportive of moving the entire VisitAttributeQueueNumberProcessor over to a Palladium module, and not having to support that within the module.

Note, the code relocated to the processor is not exactly the same. It does not hit the DAO, but rather just iterates over the entries in the queue to achieve the same result. This may need to be optimized for speed, but didn't want to do that pre-emptively if this ultimately gets moved out of the module.

It was never really clear to me what purpose the location had here, given that queue has a location on it. If there needs to be different location than that of the queue or the visit, this will need to be looked at. That said, there are some potential issues in this method as documented in O3-2448, so it likely needs a review overally

public String generateVisitQueueNumber(Location location, Queue queue) {
Criteria criteriaVisitQueueEntries = getCurrentSession().createCriteria(VisitQueueEntry.class, "_vqe");
includeVoidedObjects(criteriaVisitQueueEntries, false);
Criteria criteriaQueueEntries = criteriaVisitQueueEntries.createCriteria("_vqe.queueEntry", "_qe");
Criteria criteriaQueue = criteriaQueueEntries.createCriteria("_qe.queue", "_q");
Criteria criteriaQueueLocation = criteriaQueue.createCriteria("_q.location", "_ql");
criteriaQueueLocation.add(eq("_ql.uuid", location.getUuid()));
criteriaQueueLocation.add(eq("_q.uuid", queue.getUuid()));

LocalDate minDate = LocalDate.now();
LocalDate maxDate = LocalDate.now().plusDays(1);

Date startOfDay = Date.from(minDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
Date endOfDay = Date.from(maxDate.atStartOfDay(ZoneId.systemDefault()).toInstant());

Conjunction queueEntryStartedAtCheck = Restrictions.conjunction();
queueEntryStartedAtCheck.add(Restrictions.ge("startedAt", startOfDay));
queueEntryStartedAtCheck.add(Restrictions.lt("startedAt", endOfDay));
criteriaQueueEntries.add(queueEntryStartedAtCheck);

List<VisitQueueEntry> queueEntryList = criteriaQueueLocation.list();

int visitQueueNumber = 1;

if (!queueEntryList.isEmpty()) {
visitQueueNumber = queueEntryList.size() + 1;
}

String paddedString = StringUtils.leftPad(String.valueOf(visitQueueNumber), 3, "0");

String serviceName = queue.getName().toUpperCase();
String prefix = serviceName.length() < 3 ? serviceName : serviceName.substring(0, 3);
return prefix + "-" + paddedString;
}

/**
* @see QueueEntryDao#getActiveQueueEntries()
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,12 @@
import lombok.AccessLevel;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.openmrs.Location;
import org.openmrs.Visit;
import org.openmrs.VisitAttribute;
import org.openmrs.VisitAttributeType;
import org.openmrs.api.APIException;
import org.openmrs.api.ConceptNameType;
import org.openmrs.api.context.Context;
import org.openmrs.api.impl.BaseOpenmrsService;
import org.openmrs.module.queue.api.QueueEntryService;
import org.openmrs.module.queue.api.dao.QueueEntryDao;
import org.openmrs.module.queue.model.Queue;
import org.openmrs.module.queue.model.QueueEntry;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -91,6 +86,11 @@ public void purgeQueueEntry(QueueEntry queueEntry) throws APIException {
this.dao.delete(queueEntry);
}

@Override
public Collection<QueueEntry> getActiveQueueEntries() {
return dao.getActiveQueueEntries();
}

/**
* @see org.openmrs.module.queue.api.QueueEntryService#searchQueueEntriesByConceptStatus(String,
* boolean)
Expand All @@ -109,23 +109,6 @@ public Long getQueueEntriesCountByStatus(@NotNull String status) {
return this.dao.getQueueEntriesCountByConceptStatus(status, ConceptNameType.FULLY_SPECIFIED, false);
}

@Override
public String generateVisitQueueNumber(Location location, Queue queue, Visit visit,
VisitAttributeType visitAttributeType) {
if (location == null || queue == null || visit == null || visitAttributeType == null) {
throw new APIException("Sufficient parameters not supplied for generation of VisitQueueNumber");
}
String queueNumber = dao.generateVisitQueueNumber(location, queue);

// Create Visit Attribute using generated queue number
VisitAttribute visitQueueNumber = new VisitAttribute();
visitQueueNumber.setAttributeType(visitAttributeType);
visitQueueNumber.setValue(queueNumber);
visit.setAttribute(visitQueueNumber);
Context.getVisitService().saveVisit(visit);
return queueNumber;
}

@Override
public void closeActiveQueueEntries() {
List<QueueEntry> queueEntries = dao.getActiveQueueEntries();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
import org.openmrs.module.queue.api.dao.VisitQueueEntryDao;
import org.openmrs.module.queue.model.QueueEntry;
import org.openmrs.module.queue.model.VisitQueueEntry;
import org.openmrs.module.queue.processor.VisitQueueEntryProcessor;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Transactional
@Setter(AccessLevel.MODULE)
@Setter(AccessLevel.PUBLIC)
public class VisitQueueEntryServiceImpl extends BaseOpenmrsService implements VisitQueueEntryService {

private VisitQueueEntryProcessor visitQueueEntryProcessor;

private VisitQueueEntryDao<VisitQueueEntry> dao;

public void setDao(VisitQueueEntryDao<VisitQueueEntry> dao) {
Expand All @@ -49,23 +52,23 @@ public Optional<VisitQueueEntry> getVisitQueueEntryByUuid(@NotNull String uuid)
}

@Override
public VisitQueueEntry createVisitQueueEntry(@NotNull VisitQueueEntry visitQueueEntry) {
//verify visit -patient & queue entry patient
//Todo more refactor
Visit visit = Context.getVisitService().getVisitByUuid(visitQueueEntry.getVisit().getUuid());
Patient visitPatient = visit.getPatient();
Patient queueEntryPatient = visitQueueEntry.getQueueEntry().getPatient();
if (visitPatient != null & queueEntryPatient != null) {
boolean isPatientSame = visitPatient.getUuid().equals(queueEntryPatient.getUuid());
if (!isPatientSame) {
throw new IllegalArgumentException("Patient mismatch - visit.patient does not match queueEntry.patient");
}
QueueEntry newlyCreatedQueueEntry = Context.getService(QueueEntryService.class)
.createQueueEntry(visitQueueEntry.getQueueEntry());
visitQueueEntry.setQueueEntry(newlyCreatedQueueEntry);
return this.dao.createOrUpdate(visitQueueEntry);
public synchronized VisitQueueEntry createVisitQueueEntry(@NotNull VisitQueueEntry visitQueueEntry) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Made this method synchronized, as there tend to be situations where the queue number to generate is based on the queue entries and/or existing queue numbers assigned that exist in the database, and may need to protect against several threads generating the same queue number at the same time in different threads. See some of the comments in O3-2448

Visit visit = visitQueueEntry.getVisit();
Patient visitPatient = Context.getVisitService().getVisitByUuid(visit.getUuid()).getPatient();
QueueEntry queueEntry = visitQueueEntry.getQueueEntry();
if (queueEntry.getPatient() == null) {
queueEntry.setPatient(visitPatient);
} else if (!queueEntry.getPatient().getUuid().equals(visitPatient.getUuid())) {
throw new IllegalArgumentException("Patient mismatch - visit.patient does not match queueEntry.patient");
}
queueEntry = Context.getService(QueueEntryService.class).createQueueEntry(queueEntry);
visitQueueEntry.setQueueEntry(queueEntry);
visitQueueEntry = dao.createOrUpdate(visitQueueEntry);

if (visitQueueEntryProcessor != null) {
visitQueueEntryProcessor.beforeSaveVisitQueueEntry(visitQueueEntry);
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the main area that addresses the issue and allows customization. If a visitQueueEntryProcessor is set on the service, then it will be called to generate and/or populate any properties of the visitQueueEntry prior to final saving and completion of the transaction. An initial call to save happens before this is invoked in case this want to use generated data from the first save (for example, the primary key id).

}
return null;
return dao.createOrUpdate(visitQueueEntry);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public class QueueEntry extends BaseChangeableOpenmrsData {
@JoinColumn(name = "patient_id", nullable = false)
private Patient patient;

@Column(name = "patient_queue_number")
private String patientQueueNumber;

@ManyToOne
@JoinColumn(name = "priority", referencedColumnName = "concept_id", nullable = false)
private Concept priority;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.processor;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.openmrs.module.queue.model.QueueEntry;
import org.openmrs.module.queue.model.VisitQueueEntry;
import org.springframework.stereotype.Component;

/**
* Basic implementation of a VisitQueueEntryProcessor which simply returns the primary kye of the
* VisitQueueEntry as a string.
*/
@Slf4j
@Component
public class BasicPatientQueueNumberGenerator implements VisitQueueEntryProcessor {
Copy link
Member Author

Choose a reason for hiding this comment

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

I originally had a different implementation here that was a bit more complex, but in the end I decided to make this as basic as possible. This may mean this is never appropriate for use in any real implementation, but it does serve to provide a default implementation and an example of how one would create a custom generator.


/**
* This populates the given VisitQueueEntry with an appropriate patient queue number
*/
public void beforeSaveVisitQueueEntry(VisitQueueEntry visitQueueEntry) {
QueueEntry queueEntry = visitQueueEntry.getQueueEntry();
if (StringUtils.isBlank(queueEntry.getPatientQueueNumber())) {
queueEntry.setPatientQueueNumber(Integer.toString(visitQueueEntry.getId()));
}
}
}
Loading
Loading