/**
* 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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.openmrs.util.OpenmrsUtil;
/**
* PatientProgram
*/
public class PatientProgram extends BaseOpenmrsData {
public static final long serialVersionUID = 0L;
// ******************
// Properties
// ******************
private Integer patientProgramId;
private Patient patient;
private Program program;
private Location location;
private Date dateEnrolled;
private Date dateCompleted;
private Concept outcome;
private Set<PatientState> states = new HashSet<PatientState>();
// ******************
// Constructors
// ******************
/** Default Constructor */
public PatientProgram() {
}
/** Constructor with id */
public PatientProgram(Integer patientProgramId) {
setPatientProgramId(patientProgramId);
}
/**
* Does a mostly-shallow copy of this PatientProgram. Does not copy patientProgramId. The
* 'states' property will be deep-copied.
*
* @return a shallow copy of this PatientProgram
*/
public PatientProgram copy() {
return copyHelper(new PatientProgram());
}
/**
* The purpose of this method is to allow subclasses of PatientProgram to delegate a portion of
* their copy() method back to the superclass, in case the base class implementation changes.
*
* @param target a PatientProgram that will have the state of <code>this</code> copied into it
* @return the PatientProgram that was passed in, with state copied into it
*/
protected PatientProgram copyHelper(PatientProgram target) {
target.setPatient(this.getPatient());
target.setProgram(this.getProgram());
target.setLocation(this.getLocation());
target.setDateEnrolled(this.getDateEnrolled());
target.setDateCompleted(target.getDateCompleted());
Set<PatientState> statesCopy = new HashSet<PatientState>();
if (this.getStates() != null) {
for (PatientState s : this.getStates()) {
PatientState stateCopy = s.copy();
stateCopy.setPatientProgram(target);
statesCopy.add(stateCopy);
}
}
target.setStates(statesCopy);
target.setCreator(this.getCreator());
target.setDateCreated(this.getDateCreated());
target.setChangedBy(this.getChangedBy());
target.setDateChanged(this.getDateChanged());
target.setVoided(this.getVoided());
target.setVoidedBy(this.getVoidedBy());
target.setDateVoided(this.getDateVoided());
target.setVoidReason(this.getVoidReason());
return target;
}
// ******************
// Instance methods
// ******************
/**
* Returns true if the associated {@link Patient} is enrolled in the associated {@link Program}
* on the passed {@link Date}
*
* @param onDate - Date to check for PatientProgram enrollment
* @return boolean - true if the associated {@link Patient} is enrolled in the associated
* {@link Program} on the passed {@link Date}
*/
public boolean getActive(Date onDate) {
if (onDate == null) {
onDate = new Date();
}
return !getVoided() && (getDateEnrolled() == null || OpenmrsUtil.compare(getDateEnrolled(), onDate) <= 0)
&& (getDateCompleted() == null || OpenmrsUtil.compare(getDateCompleted(), onDate) > 0);
}
/**
* Returns true if the associated {@link Patient} is currently enrolled in the associated
* {@link Program}
*
* @return boolean - true if the associated {@link Patient} is currently enrolled in the
* associated {@link Program}
*/
public boolean getActive() {
return getActive(null);
}
/**
* Returns the {@link PatientState} associated with this PatientProgram that has an id that
* matches the passed <code>patientStateId</code>
*
* @param patientStateId - The identifier to use to lookup a {@link PatientState}
* @return PatientState that has an id that matches the passed <code>patientStateId</code>
*/
public PatientState getPatientState(Integer patientStateId) {
for (PatientState s : getStates()) {
if (s.getPatientStateId() != null && s.getPatientStateId().equals(patientStateId)) {
return s;
}
}
return null;
}
/**
* Attempts to transition the PatientProgram to the passed {@link ProgramWorkflowState} on the
* passed {@link Date} by ending the most recent {@link PatientState} in the
* {@link PatientProgram} and creating a new one with the passed {@link ProgramWorkflowState}
* This will throw an IllegalArgumentException if the transition is invalid
*
* @param programWorkflowState - The {@link ProgramWorkflowState} to transition to
* @param onDate - The {@link Date} of the transition
* @throws IllegalArgumentException
*/
public void transitionToState(ProgramWorkflowState programWorkflowState, Date onDate) {
PatientState lastState = getCurrentState(programWorkflowState.getProgramWorkflow());
if (lastState != null && onDate == null) {
throw new IllegalArgumentException("You can't change from a non-null state without giving a change date");
}
if (lastState != null && lastState.getEndDate() != null) {
throw new IllegalArgumentException("You can't change out of a state that has an end date already");
}
if (lastState != null && lastState.getStartDate() != null
&& OpenmrsUtil.compare(lastState.getStartDate(), onDate) > 0) {
throw new IllegalArgumentException("You can't change out of a state before that state started");
}
if (lastState != null
&& !programWorkflowState.getProgramWorkflow().isLegalTransition(lastState.getState(), programWorkflowState)) {
throw new IllegalArgumentException("You can't change from state " + lastState.getState() + " to "
+ programWorkflowState);
}
if (lastState != null) {
lastState.setEndDate(onDate);
}
PatientState newState = new PatientState();
newState.setPatientProgram(this);
newState.setState(programWorkflowState);
newState.setStartDate(onDate);
if (programWorkflowState.getTerminal()) {
setDateCompleted(onDate);
}
getStates().add(newState);
}
/**
* Attempts to void the latest {@link PatientState} in the {@link PatientProgram} If earlier
* PatientStates exist, it will try to reset the endDate to null so that the next latest state
* becomes the current {@link PatientState}
*
* @param workflow - The {@link ProgramWorkflow} whose last {@link PatientState} within the
* current {@link PatientProgram} we want to void
* @param voidBy - The user who is voiding the {@link PatientState}
* @param voidDate - The date to void the {@link PatientState}
* @param voidReason - The reason for voiding the {@link PatientState}
* @should void state with endDate null if startDates equal
*/
public void voidLastState(ProgramWorkflow workflow, User voidBy, Date voidDate, String voidReason) {
List<PatientState> states = statesInWorkflow(workflow, false);
if (voidDate == null) {
voidDate = new Date();
}
PatientState last = null;
PatientState nextToLast = null;
if (!states.isEmpty()) {
last = states.get(states.size() - 1);
}
if (states.size() > 1) {
nextToLast = states.get(states.size() - 2);
}
if (last != null) {
last.setVoided(true);
last.setVoidedBy(voidBy);
last.setDateVoided(voidDate);
last.setVoidReason(voidReason);
}
if (nextToLast != null && nextToLast.getEndDate() != null) {
nextToLast.setEndDate(null);
nextToLast.setDateChanged(voidDate);
nextToLast.setChangedBy(voidBy);
}
}
/**
* Returns the current {@link PatientState} for the passed {@link ProgramWorkflow} within this
* {@link PatientProgram}.
*
* @param programWorkflow The ProgramWorkflow whose current {@link PatientState} we want to
* retrieve
* @return PatientState The current {@link PatientState} for the passed {@link ProgramWorkflow}
* within this {@link PatientProgram}
*/
public PatientState getCurrentState(ProgramWorkflow programWorkflow) {
Date now = new Date();
PatientState currentState = null;
for (PatientState state : getSortedStates()) {
//states are sorted with the most current state at the last position
if ((programWorkflow == null || state.getState().getProgramWorkflow().equals(programWorkflow))
&& state.getActive(now)) {
currentState = state;
}
}
return currentState;
}
/**
* Returns a Set<PatientState> of all current {@link PatientState}s for the
* {@link PatientProgram}
*
* @return Set<PatientState> of all current {@link PatientState}s for the {@link PatientProgram}
*/
public Set<PatientState> getCurrentStates() {
Set<PatientState> ret = new HashSet<PatientState>();
Date now = new Date();
for (PatientState state : getStates()) {
if (state.getActive(now)) {
ret.add(state);
}
}
return ret;
}
/**
* Returns a List<PatientState> of all {@link PatientState}s in the passed
* {@link ProgramWorkflow} for the {@link PatientProgram}
*
* @param programWorkflow - The {@link ProgramWorkflow} to check
* @param includeVoided - If true, return voided {@link PatientState}s in the returned
* {@link List}
* @return List<PatientState> of all {@link PatientState}s in the passed {@link ProgramWorkflow}
* for the {@link PatientProgram}
*/
public List<PatientState> statesInWorkflow(ProgramWorkflow programWorkflow, boolean includeVoided) {
List<PatientState> ret = new ArrayList<PatientState>();
for (PatientState st : getSortedStates()) {
if (st.getState().getProgramWorkflow().equals(programWorkflow) && (includeVoided || !st.getVoided())) {
ret.add(st);
}
}
return ret;
}
/** @see Object#toString() */
@Override
public String toString() {
return "PatientProgram(id=" + getPatientProgramId() + ", patient=" + getPatient() + ", program=" + getProgram()
+ ")";
}
// ******************
// Property Access
// ******************
public Concept getOutcome() {
return outcome;
}
public void setOutcome(Concept concept) {
this.outcome = concept;
}
public Date getDateCompleted() {
return dateCompleted;
}
public void setDateCompleted(Date dateCompleted) {
this.dateCompleted = dateCompleted;
}
public Date getDateEnrolled() {
return dateEnrolled;
}
public void setDateEnrolled(Date dateEnrolled) {
this.dateEnrolled = dateEnrolled;
}
public Patient getPatient() {
return patient;
}
public void setPatient(Patient patient) {
this.patient = patient;
}
public Integer getPatientProgramId() {
return patientProgramId;
}
public void setPatientProgramId(Integer patientProgramId) {
this.patientProgramId = patientProgramId;
}
public Program getProgram() {
return program;
}
public void setProgram(Program program) {
this.program = program;
}
public Set<PatientState> getStates() {
return states;
}
public void setStates(Set<PatientState> states) {
this.states = states;
}
/**
* @since 1.5
* @see org.openmrs.OpenmrsObject#getId()
*/
@Override
public Integer getId() {
return getPatientProgramId();
}
/**
* @since 1.5
* @see org.openmrs.OpenmrsObject#setId(java.lang.Integer)
*/
@Override
public void setId(Integer id) {
setPatientProgramId(id);
}
/**
* @since 1.8
* @return the location
*/
public Location getLocation() {
return location;
}
/**
* @since 1.8
* @param location the location to set
*/
public void setLocation(Location location) {
this.location = location;
}
/**
* @return states sorted by {@link PatientState#compareTo(PatientState)}
*/
private List<PatientState> getSortedStates() {
List<PatientState> sortedStates = new ArrayList<PatientState>(getStates());
Collections.sort(sortedStates);
return sortedStates;
}
}