Skip to content

Commit

Permalink
AM-151: Add new "CANCELLED_AND_NEED_RESCHEDULE" AppointmentStatus
Browse files Browse the repository at this point in the history
  • Loading branch information
mogoodrich committed Mar 11, 2014
1 parent ef6763e commit f56e1a2
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@
*/
package org.openmrs.module.appointmentscheduling;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.openmrs.BaseOpenmrsData;
import org.openmrs.BaseOpenmrsMetadata;
import org.openmrs.BaseOpenmrsObject;
import org.openmrs.Patient;
import org.openmrs.Visit;

import java.io.Serializable;

/**
* It is a model class. It should extend either {@link BaseOpenmrsObject} or
* {@link BaseOpenmrsMetadata}.
Expand All @@ -29,25 +34,64 @@ public class Appointment extends BaseOpenmrsData implements Serializable {

private static final long serialVersionUID = 1L;

// TODO confirm that "WALK-IN" should be considered active
public enum AppointmentStatus {
SCHEDULED("Scheduled"), RESCHEDULED("Rescheduled"), WALKIN("Walk-In"), CANCELLED("Cancelled"), WAITING("Waiting"), INCONSULTATION(
"In-Consultation"), COMPLETED("Completed"), MISSED("Missed");
SCHEDULED("Scheduled", true, false), RESCHEDULED("Rescheduled", true, false), WALKIN("Walk-In", true, true), CANCELLED(
"Cancelled", false, false), WAITING("Waiting", true, true), INCONSULTATION("In-Consultation", true, true), COMPLETED(
"Completed", true, false), MISSED("Missed", false, false), CANCELLED_AND_NEEDS_RESCHEDULE(
"Cancelled and Needs Reschedule", false, false);

private final String name;

/**
* Whether or not an appointment with this status should be considered "cancelled" Right now
* we consider CANCELLED, CANCELLED_AND_NEEDS_RESCHEDULE, and MISSED appts as cancelled
*/
private Boolean cancelled;

/**
* Whether or not this appointment represents an "active" appointment, where active=patient
* checked-in and present within the health facility
*/
private Boolean active;

private AppointmentStatus(final String name, final Boolean cancelled, final Boolean active) {
this.name = name;
this.cancelled = cancelled;
this.active = active;
}

public String getName() {
return this.name;
}

private AppointmentStatus(final String name) {
this.name = name;
public Boolean isCancelled() {
return this.cancelled;
}

public Boolean isActive() {
return this.active;
}

@Override
public String toString() {
return name;
}

public static List<AppointmentStatus> filter(Predicate predicate) {
List<AppointmentStatus> appointmentStatuses = new ArrayList(Arrays.asList(AppointmentStatus.values())); // need to assign to a new array because Arrays.asList is fixed length
CollectionUtils.filter(appointmentStatuses, predicate);
return appointmentStatuses;
}

public static Predicate cancelledPredicate = new Predicate() {

@Override
public boolean evaluate(Object o) {
return ((AppointmentStatus) o).isCancelled();
}
};

}

private Integer appointmentId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ public interface AppointmentService extends OpenmrsService {
* @param reason the reason why the appointment block is voided.
* @return the appointment block that has been voided.
* @should void given appointment block
* @should void all associated time slots
*/
AppointmentBlock voidAppointmentBlock(AppointmentBlock appointmentBlock, String reason);

Expand All @@ -235,6 +236,7 @@ public interface AppointmentService extends OpenmrsService {
* @return a list of appointment block objects.
* @should get all appointment blocks which have contains in a given date interval and
* corresponds to a given locations, provider and appointment type.
* @should not return voided appointment blocks
*/
@Transactional(readOnly = true)
List<AppointmentBlock> getAppointmentBlocks(Date fromDate, Date toDate, String locations, Provider provider,
Expand Down Expand Up @@ -435,15 +437,17 @@ List<AppointmentBlock> getAppointmentBlocks(Date fromDate, Date toDate, String l
List<Appointment> getAppointmentsInTimeSlot(TimeSlot timeSlot);

/**
* Should retrieve all appointments in the given time slot that are not missed or cancelled
* Should retrieve all appointments in the given time slot that do not have a status that means
* the appointment has been cancelled (ie status=CANCELLED, CANCELLED_AND_NEEDS_RESCHEDULE,
* MISSED, etc)
*
* @param timeSlot the time slot to search by.
* @return the appointments in the given time slo
* @should not return missed and cancelled appointments.
* @should not return missed, cancelled, and needs_reschedule appointments.
* @should not return voided appointments
*/
@Transactional(readOnly = true)
List<Appointment> getAppointmentsInTimeSlotExcludingMissedAndCancelled(TimeSlot timeSlot);
List<Appointment> getAppointmentsInTimeSlotThatAreNotCancelled(TimeSlot timeSlot);

/**
* Gets a count of the number of appointments in a time slot
Expand All @@ -456,15 +460,17 @@ List<AppointmentBlock> getAppointmentBlocks(Date fromDate, Date toDate, String l
Integer getCountOfAppointmentsInTimeSlot(TimeSlot timeSlot);

/**
* Gets a count of the number of appointments in a time slot that are not missed or cancelled
* Gets a count of all appointments in the given time slot that do not have a status that means
* the appointment has been cancelled (ie status=CANCELLED, CANCELLED_AND_NEEDS_RESCHEDULE,
* MISSED, etc)
*
* @param timeSlot the time slot to search by.
* @return the count of appointments in the given time slot
* @should not count missed and cancelled appointments.
* @should not count missed, cancelled and needs rescheduled appointments.
* @should not count voided appointments
*/
@Transactional(readOnly = true)
Integer getCountOfAppointmentsInTimeSlotExcludingMissedAndCancelled(TimeSlot timeSlot);
Integer getCountOfAppointmentsInTimeSlotThatAreNotCancelled(TimeSlot timeSlot);

/**
* Should retrieve all time slots in the given appointment block.
Expand Down Expand Up @@ -576,6 +582,7 @@ List<TimeSlot> getTimeSlotsByConstraintsIncludingFull(AppointmentType appointmen
* @param timeSlot the given time slot.
* @return The amount of minutes left in the given time slot. Returns null if the given time
* slot was null;
* @should ignore appointments with statuses that reflect a "cancelled" appointment
*/
@Transactional(readOnly = true)
Integer getTimeLeftInTimeSlot(TimeSlot timeSlot);
Expand Down Expand Up @@ -721,8 +728,8 @@ public Map<Provider, Double> getAverageHistoryDurationByConditionsPerProvider(Da

/**
* Given an appointment block, this method creates a ScheduledAppointmentBlock convenience
* object that contains all the appointments in the block that are not CANCELLED or MISSED, as
* well as the remaining time available in the blocks
* object that contains all the appointments in the block that are not voided or in one of the
* "cancelled" states
*
* @param appointmentBlock
* @return
Expand All @@ -731,9 +738,8 @@ public Map<Provider, Double> getAverageHistoryDurationByConditionsPerProvider(Da
ScheduledAppointmentBlock createScheduledAppointmentBlock(AppointmentBlock appointmentBlock);

/**
* Gets all scheduled appointment blocks for a certain day at a certain location Ignores any
* blocks within the time period that *do not* have any appointments that are not CANCELLED or
* MISSED
* Gets all scheduled appointment blocks for a certain day at a certain location. Ignores any
* appointments that are voided or in one of the "cancelled" states
*
* @param location
* @param date
Expand All @@ -742,16 +748,6 @@ public Map<Provider, Double> getAverageHistoryDurationByConditionsPerProvider(Da
@Transactional(readOnly = true)
List<ScheduledAppointmentBlock> getDailyAppointmentBlocks(Location location, Date date);

/**
* Calculate the unallocated minutes in the time slot. As follows: Number minutes in time slot
* minus minutes allocated for all appts in the time slot that aren't CANCELLED or MISSED
*
* @param timeSlot
* @return
*/
@Transactional(readOnly = true)
Integer calculateUnallocatedMinutesInTimeSlot(TimeSlot timeSlot);

/**
* Books a new appointment
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ public List<Appointment> getAppointmentsByAppointmentBlock(AppointmentBlock appo
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(mappedClass);
criteria.createAlias("timeSlot", "time_slot");
criteria.add(Restrictions.eq("time_slot.appointmentBlock", appointmentBlock));
// skip cancelled and missed appointment blocks
criteria.add(Restrictions.and(Restrictions.ne("status", CANCELLED), Restrictions.ne("status", MISSED)));
criteria.add(Restrictions.eq("voided", false));

return criteria.list();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
package org.openmrs.module.appointmentscheduling.api.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
Expand All @@ -27,12 +26,12 @@
import java.util.Map;
import java.util.Set;

import org.joda.time.DateTime;
import org.joda.time.Minutes;
import org.openmrs.module.appointmentscheduling.ScheduledAppointmentBlock;
import org.openmrs.module.appointmentscheduling.StudentT;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.joda.time.Minutes;
import org.openmrs.Location;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifier;
Expand All @@ -46,6 +45,8 @@
import org.openmrs.module.appointmentscheduling.AppointmentBlock;
import org.openmrs.module.appointmentscheduling.AppointmentStatusHistory;
import org.openmrs.module.appointmentscheduling.AppointmentType;
import org.openmrs.module.appointmentscheduling.ScheduledAppointmentBlock;
import org.openmrs.module.appointmentscheduling.StudentT;
import org.openmrs.module.appointmentscheduling.TimeSlot;
import org.openmrs.module.appointmentscheduling.api.AppointmentService;
import org.openmrs.module.appointmentscheduling.api.db.AppointmentBlockDAO;
Expand Down Expand Up @@ -91,7 +92,7 @@ public AppointmentTypeDAO getAppointmentTypeDAO() {
/**
* @see org.openmrs.module.appointmentscheduling.api.AppointmentService#getAllAppointmentTypes()
*/
@Override
@Override
@Transactional(readOnly = true)
public Set<AppointmentType> getAllAppointmentTypes() {
HashSet set = new HashSet();
Expand Down Expand Up @@ -242,6 +243,7 @@ public AppointmentBlock voidAppointmentBlock(AppointmentBlock appointmentBlock,
/**
* @see org.openmrs.module.appointmentscheduling.api.AppointmentService#unvoidAppointmentBlock(org.openmrs.AppointmentBlock)
*/
// TODO what should this do regarding voided time slots within this block?
public AppointmentBlock unvoidAppointmentBlock(AppointmentBlock appointmentBlock) {
return saveAppointmentBlock(appointmentBlock);
}
Expand Down Expand Up @@ -408,11 +410,9 @@ public List<Appointment> getAppointmentsInTimeSlot(TimeSlot timeSlot) {
}

@Override
public List<Appointment> getAppointmentsInTimeSlotExcludingMissedAndCancelled(TimeSlot timeSlot) {
return getAppointmentDAO().getAppointmentsInTimeSlotByStatus(
timeSlot,
Arrays.asList(AppointmentStatus.COMPLETED, AppointmentStatus.INCONSULTATION, AppointmentStatus.RESCHEDULED,
AppointmentStatus.SCHEDULED, AppointmentStatus.WAITING, AppointmentStatus.WALKIN));
public List<Appointment> getAppointmentsInTimeSlotThatAreNotCancelled(TimeSlot timeSlot) {
return getAppointmentDAO().getAppointmentsInTimeSlotByStatus(timeSlot,
AppointmentStatus.filter(AppointmentStatus.cancelledPredicate));
}

@Override
Expand All @@ -421,11 +421,9 @@ public Integer getCountOfAppointmentsInTimeSlot(TimeSlot timeSlot) {
}

@Override
public Integer getCountOfAppointmentsInTimeSlotExcludingMissedAndCancelled(TimeSlot timeSlot) {
return getAppointmentDAO().getCountOfAppointmentsInTimeSlotByStatus(
timeSlot,
Arrays.asList(AppointmentStatus.COMPLETED, AppointmentStatus.INCONSULTATION, AppointmentStatus.RESCHEDULED,
AppointmentStatus.SCHEDULED, AppointmentStatus.WAITING, AppointmentStatus.WALKIN));
public Integer getCountOfAppointmentsInTimeSlotThatAreNotCancelled(TimeSlot timeSlot) {
return getAppointmentDAO().getCountOfAppointmentsInTimeSlotByStatus(timeSlot,
AppointmentStatus.filter(AppointmentStatus.cancelledPredicate));
}

@Override
Expand Down Expand Up @@ -565,27 +563,20 @@ public List<String> getPatientIdentifiersRepresentation(Patient patient) {
@Override
@Transactional(readOnly = true)
public Integer getTimeLeftInTimeSlot(TimeSlot timeSlot) {
Integer timeLeft = null;

if (timeSlot == null)
return timeLeft;

Date startDate = timeSlot.getStartDate();
Date endDate = timeSlot.getEndDate();
if (timeSlot == null) {
return null;
}

//Calculate total number of minutes in the time slot.
timeLeft = (int) ((endDate.getTime() / 60000) - (startDate.getTime() / 60000));
Integer minutes = Minutes.minutesBetween(new DateTime(timeSlot.getStartDate()), new DateTime(timeSlot.getEndDate()))
.getMinutes();

//Subtract from time left the amounts of minutes already scheduled
//Should not take into consideration cancelled or missed appointments
List<Appointment> appointments = getAppointmentsInTimeSlot(timeSlot);
for (Appointment appointment : appointments) {
if (!appointment.isVoided() && appointment.getStatus() != AppointmentStatus.CANCELLED
&& appointment.getStatus() != AppointmentStatus.MISSED)
timeLeft -= appointment.getAppointmentType().getDuration();
for (Appointment appointment : Context.getService(AppointmentService.class)
.getAppointmentsInTimeSlotThatAreNotCancelled(timeSlot)) {
minutes = minutes - appointment.getAppointmentType().getDuration();
}

return timeLeft;
return minutes;
}

@Override
Expand Down Expand Up @@ -922,22 +913,19 @@ public List<ScheduledAppointmentBlock> getDailyAppointmentBlocks(Location locati
@Transactional(readOnly = true)
public ScheduledAppointmentBlock createScheduledAppointmentBlock(AppointmentBlock appointmentBlock) {
List<Appointment> appointmentList = getAppointmentDAO().getAppointmentsByAppointmentBlock(appointmentBlock);
return new ScheduledAppointmentBlock(appointmentList, appointmentBlock);
}

@Override
@Transactional(readOnly = true)
public Integer calculateUnallocatedMinutesInTimeSlot(TimeSlot timeSlot) {

Integer minutes = Minutes.minutesBetween(new DateTime(timeSlot.getStartDate()), new DateTime(timeSlot.getEndDate()))
.getMinutes();

for (Appointment appointment : Context.getService(AppointmentService.class)
.getAppointmentsInTimeSlotExcludingMissedAndCancelled(timeSlot)) {
minutes = minutes - appointment.getAppointmentType().getDuration();
}
// only include appointments that aren't "cancelled"
CollectionUtils.filter(appointmentList, new Predicate() {

List<AppointmentStatus> cancelledStatuses = AppointmentStatus.filter(AppointmentStatus.cancelledPredicate);

@Override
public boolean evaluate(Object o) {
return cancelledStatuses.contains(((Appointment) o).getStatus());
}
});

return minutes;
return new ScheduledAppointmentBlock(appointmentList, appointmentBlock);
}

@Override
Expand All @@ -948,13 +936,12 @@ public Appointment bookAppointment(Appointment appointment, Boolean allowOverboo
if (appointment.getId() != null) {
throw new APIException("Cannot book appointment that has already been persisted");
}

// annoying that we have to do this, since it will be called during save, but otherwise we might get a NPE below if time slot or appointment type == null
// annoying that we have to do this, since it will be called during save, but otherwise we might get a NPE below if time slot or appointment type == null
ValidateUtil.validate(appointment);

if (!allowOverbook) {
if (calculateUnallocatedMinutesInTimeSlot(appointment.getTimeSlot()) < appointment.getAppointmentType()
.getDuration()) {
if (getTimeLeftInTimeSlot(appointment.getTimeSlot()) < appointment.getAppointmentType().getDuration()) {
throw new TimeSlotFullException();
}
}
Expand Down
Loading

0 comments on commit f56e1a2

Please sign in to comment.