/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.api.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.queryParser.ParseException;
import org.openmrs.Concept;
import org.openmrs.Encounter;
import org.openmrs.Location;
import org.openmrs.Obs;
import org.openmrs.Order;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifier;
import org.openmrs.PatientIdentifierType;
import org.openmrs.PatientProgram;
import org.openmrs.PersonAddress;
import org.openmrs.PersonAttribute;
import org.openmrs.PersonName;
import org.openmrs.Relationship;
import org.openmrs.api.APIException;
import org.openmrs.api.BlankIdentifierException;
import org.openmrs.api.DuplicateIdentifierException;
import org.openmrs.api.EncounterService;
import org.openmrs.api.InsufficientIdentifiersException;
import org.openmrs.api.MissingRequiredIdentifierException;
import org.openmrs.api.ObsService;
import org.openmrs.api.OrderService;
import org.openmrs.api.PatientIdentifierException;
import org.openmrs.api.PatientService;
import org.openmrs.api.PersonService;
import org.openmrs.api.ProgramWorkflowService;
import org.openmrs.api.context.Context;
import org.openmrs.api.db.PatientDAO;
import org.openmrs.order.OrderUtil;
import org.openmrs.patient.IdentifierValidator;
import org.openmrs.patient.impl.LuhnIdentifierValidator;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.validator.PatientIdentifierValidator;
/**
* Default implementation of the patient service. This class should not be used on its own. The
* current OpenMRS implementation should be fetched from the Context via
* <code>Context.getPatientService()</code>
*
* @see org.openmrs.api.context.Context
* @see org.openmrs.api.PatientService
* @see org.openmrs.api.PersonService
*/
public class PatientServiceImpl extends BaseOpenmrsService implements PatientService {
private Log log = LogFactory.getLog(this.getClass());
private PatientDAO dao;
/**
* PatientIdentifierValidators registered through spring's applicationContext-service.xml
*/
private static Map<Class<? extends IdentifierValidator>, IdentifierValidator> identifierValidators = null;
/**
* @see org.openmrs.api.PatientService#setPatientDAO(org.openmrs.api.db.PatientDAO)
*/
public void setPatientDAO(PatientDAO dao) {
this.dao = dao;
}
/**
* Clean up after this class. Set the static var to null so that the classloader can reclaim the
* space.
*
* @see org.openmrs.api.impl.BaseOpenmrsService#onShutdown()
*/
public void onShutdown() {
identifierValidators = null;
}
/**
* @see #savePatient(Patient)
* @deprecated replaced by #savePatient(Patient)
* @see org.openmrs.api.PatientService#createPatient(org.openmrs.Patient)
*/
public Patient createPatient(Patient patient) throws APIException {
return Context.getPatientService().savePatient(patient);
}
/**
* @see org.openmrs.api.PatientService#savePatient(org.openmrs.Patient)
*/
public Patient savePatient(Patient patient) throws APIException {
if (patient.getPatientId() == null)
Context.requirePrivilege(OpenmrsConstants.PRIV_ADD_PATIENTS);
else
Context.requirePrivilege(OpenmrsConstants.PRIV_EDIT_PATIENTS);
checkPatientIdentifiers(patient);
return dao.savePatient(patient);
}
/**
* @see org.openmrs.api.PatientService#getPatient(java.lang.Integer)
*/
public Patient getPatient(Integer patientId) throws APIException {
return dao.getPatient(patientId);
}
/**
* @see #savePatient(Patient)
* @deprecated replaced by #savePatient(Patient)
* @see org.openmrs.api.PatientService#updatePatient(org.openmrs.Patient)
*/
public Patient updatePatient(Patient patient) throws APIException {
return Context.getPatientService().savePatient(patient);
}
/**
* @see org.openmrs.api.PatientService#getAllPatients()
*/
public List<Patient> getAllPatients() throws APIException {
return getAllPatients(false);
}
/**
* @see org.openmrs.api.PatientService#getAllPatients(boolean)
*/
public List<Patient> getAllPatients(boolean includeVoided) throws APIException {
return dao.getAllPatients(includeVoided);
}
/**
* @deprecated replaced by {@link #getPatients(String, String, List, boolean)}
* @see org.openmrs.api.PatientService#getPatients(java.lang.String, java.lang.String,
* java.util.List)
*/
public List<Patient> getPatients(String name, String identifier, List<PatientIdentifierType> identifierTypes)
throws APIException {
return getPatients(name, identifier, identifierTypes, false);
}
/**
* @see org.openmrs.api.PatientService#getPatients(java.lang.String, java.lang.String,
* java.util.List, boolean)
*/
public List<Patient> getPatients(String name, String identifier, List<PatientIdentifierType> identifierTypes,
boolean matchIdentifierExactly) throws APIException {
if (name != null && (name.contains("%") || name.contains("*")))
throw new APIException(Context.getMessageSourceService().getMessage("SearchResults.noWildcardsAllowed"));
if (identifier != null && (identifier.contains("%") || identifier.contains("*")))
throw new APIException(Context.getMessageSourceService().getMessage("SearchResults.noWildcardsAllowed"));
if (identifierTypes == null)
identifierTypes = Collections.emptyList();
return dao.getPatients(name, identifier, identifierTypes, matchIdentifierExactly);
}
/**
* @see org.openmrs.api.PatientService#checkPatientIdentifiers(org.openmrs.Patient)
*/
public void checkPatientIdentifiers(Patient patient) throws PatientIdentifierException {
// check patient has at least one identifier
if (!patient.isVoided() && patient.getActiveIdentifiers().size() < 1)
throw new InsufficientIdentifiersException("At least one nonvoided Patient Identifier is required");
List<PatientIdentifier> identifiers = new Vector<PatientIdentifier>();
identifiers.addAll(patient.getIdentifiers());
List<String> identifiersUsed = new Vector<String>();
List<PatientIdentifierType> requiredTypes = Context.getPatientService().getPatientIdentifierTypes(null, null, true,
null);
if (requiredTypes == null)
requiredTypes = new ArrayList<PatientIdentifierType>();
List<PatientIdentifierType> foundRequiredTypes = new ArrayList<PatientIdentifierType>();
for (PatientIdentifier pi : identifiers) {
if (pi.isVoided())
continue;
try {
checkPatientIdentifier(pi);
}
catch (BlankIdentifierException bie) {
patient.removeIdentifier(pi);
throw bie;
}
// check if this is a required identifier
for (PatientIdentifierType requiredType : requiredTypes) {
if (pi.getIdentifierType().equals(requiredType)) {
foundRequiredTypes.add(requiredType);
requiredTypes.remove(requiredType);
break;
}
}
// TODO: check patient has at least one "sufficient" identifier
// TODO: what makes a patient identifier unique ... can you have the
// same identifier number at different locations? if so, then this
// check duplicate algorithm does not handle this case
// check this patient for duplicate identifiers+identifierType
if (identifiersUsed.contains(pi.getIdentifier() + " id type #: "
+ pi.getIdentifierType().getPatientIdentifierTypeId())) {
patient.removeIdentifier(pi);
throw new DuplicateIdentifierException("This patient has two identical identifiers of type "
+ pi.getIdentifierType().getName() + ": " + pi.getIdentifier() + ", deleting one of them", pi);
} else {
identifiersUsed.add(pi.getIdentifier() + " id type #: "
+ pi.getIdentifierType().getPatientIdentifierTypeId());
}
}
if (requiredTypes.size() > 0) {
String missingNames = "";
for (PatientIdentifierType pit : requiredTypes) {
missingNames += (missingNames.length() > 0) ? ", " + pit.getName() : pit.getName();
}
throw new MissingRequiredIdentifierException("Patient is missing the following required identifier(s): "
+ missingNames);
}
}
/**
* @see org.openmrs.api.PatientService#checkPatientIdentifier(org.openmrs.PatientIdentifier)
* @deprecated use {@link PatientIdentifierValidator#validateIdentifier(PatientIdentifier)}
*/
public void checkPatientIdentifier(PatientIdentifier pi) throws PatientIdentifierException {
PatientIdentifierValidator.validateIdentifier(pi);
}
/**
* @see org.openmrs.api.PatientService#identifierInUse(java.lang.String,
* org.openmrs.PatientIdentifierType, org.openmrs.Patient)
* @deprecated use getPatientByIdentifier(String) instead
*/
public Patient identifierInUse(String identifier, PatientIdentifierType type, Patient ignorePatient) {
// get all patients with this identifier
List<PatientIdentifierType> types = new Vector<PatientIdentifierType>();
types.add(type);
List<Patient> patients = getPatients(null, identifier, types, /* exact name+identifier search */true);
// ignore this patient (loop until no changes made)
while (patients.remove(ignorePatient)) {}
;
if (patients.size() > 0)
return patients.get(0);
return null;
}
/**
* @deprecated replaced by @deprecated replaced by {@link #getPatients(String, String, List)}
* @see org.openmrs.api.PatientService#getPatientsByIdentifier(java.lang.String, boolean)
*/
public List<Patient> getPatientsByIdentifier(String identifier, boolean includeVoided) throws APIException {
if (includeVoided == true)
throw new APIException("Searching on voided patients is no longer allowed");
return getPatients(null, identifier, null);
}
/**
* @deprecated replaced by {@link #getPatients(String, String, List, boolean)}
* @see org.openmrs.api.PatientService#getPatientsByIdentifierPattern(java.lang.String, boolean)
*/
public List<Patient> getPatientsByIdentifierPattern(String identifier, boolean includeVoided) throws APIException {
if (includeVoided == true)
throw new APIException("Searching on voided patients is no longer allowed");
return getPatients(null, identifier, null);
}
/**
* @see org.openmrs.api.PatientService#getPatientsByName(java.lang.String)
* @deprecated replaced by {@link #getPatients(String, String, List, boolean)}
*/
public List<Patient> getPatientsByName(String name) throws APIException {
return getPatients(name, null, null);
}
/**
* @deprecated replaced by {@link #getPatients(String, String, List, boolean)}
*/
public List<Patient> getPatientsByName(String name, boolean includeVoided) throws APIException {
if (includeVoided == true)
throw new APIException("Searching on voided patients is no longer allowed");
return getPatients(name, null, null);
}
/**
* @see org.openmrs.api.PatientService#voidPatient(org.openmrs.Patient, java.lang.String)
*/
public Patient voidPatient(Patient patient, String reason) throws APIException {
// TODO should we move voided attributes from the patient up to the person?
// voiding a patient portion of a person and keeping the user portion doesn't
// seem like a necessary goal
if (patient == null)
return null;
// patient and patientidentifier attributes taken care of by the BaseUnvoidHandler
return savePatient(patient);
}
/**
* @see org.openmrs.api.PatientService#unvoidPatient(org.openmrs.Patient)
*/
public Patient unvoidPatient(Patient patient) throws APIException {
if (patient == null)
return null;
// patient and patientidentifier attributes taken care of by the BaseUnvoidHandler
return savePatient(patient);
}
/**
* @see #voidPatient(org.openmrs.Patient,java.lang.String)
* @deprecated replaced by {@link #purgePatient(Patient)}
*/
public void deletePatient(Patient patient) throws APIException {
Context.getPatientService().purgePatient(patient);
}
/**
* @see org.openmrs.api.PatientService#purgePatient(org.openmrs.Patient)
*/
public void purgePatient(Patient patient) throws APIException {
dao.deletePatient(patient);
}
// patient identifier section
/**
* @see org.openmrs.api.PatientService#getPatientIdentifiers(java.lang.String, java.util.List,
* java.util.List, java.util.List, java.lang.Boolean)
*/
public List<PatientIdentifier> getPatientIdentifiers(String identifier,
List<PatientIdentifierType> patientIdentifierTypes,
List<Location> locations, List<Patient> patients,
Boolean isPreferred) throws APIException {
if (patientIdentifierTypes == null)
patientIdentifierTypes = new Vector<PatientIdentifierType>();
if (locations == null)
locations = new Vector<Location>();
if (patients == null)
patients = new Vector<Patient>();
return dao.getPatientIdentifiers(identifier, patientIdentifierTypes, locations, patients, isPreferred);
}
/**
* @deprecated replaced by {@link #getPatientIdentifiers(String, List, List, List, Boolean)}
* @see org.openmrs.api.PatientService#getPatientIdentifiers(org.openmrs.PatientIdentifierType)
*/
public List<PatientIdentifier> getPatientIdentifiers(PatientIdentifierType pit) throws APIException {
List<PatientIdentifierType> types = new Vector<PatientIdentifierType>();
types.add(pit);
return getPatientIdentifiers(null, types, null, null, null);
}
/**
* @deprecated replaced by {@link #getPatientIdentifiers(String, List, List, List, Boolean)}
* @see org.openmrs.api.PatientService#getPatientIdentifiers(java.lang.String,
* org.openmrs.PatientIdentifierType)
*/
public List<PatientIdentifier> getPatientIdentifiers(String identifier, PatientIdentifierType pit) throws APIException {
List<PatientIdentifierType> types = new Vector<PatientIdentifierType>();
types.add(pit);
return getPatientIdentifiers(identifier, types, null, null, null);
}
/**
* @deprecated replaced by {@link #getPatientIdentifiers(String, List, List, List, Boolean)}
* @see org.openmrs.api.PatientService#getPatientIdentifiers(String, PatientIdentifierType)
*/
public List<PatientIdentifier> getPatientIdentifiers(String identifier, PatientIdentifierType patientIdentifierType,
boolean includeVoided) throws APIException {
if (includeVoided == true)
throw new APIException("Searching on voided identifiers is no longer allowed");
List<PatientIdentifierType> types = new Vector<PatientIdentifierType>();
types.add(patientIdentifierType);
return getPatientIdentifiers(identifier, types, null, null, null);
}
/**
* @deprecated patient identifiers should not be updated directly; rather, after changing
* patient identifiers, use {@link #savePatient(Patient)} to save changes to the
* database
*/
public void updatePatientIdentifier(PatientIdentifier pi) throws APIException {
// this method allows you change only the Identifier type, so let's do
// that and then do a full ID check
Patient p = pi.getPatient();
Set<PatientIdentifier> identifiers = p.getIdentifiers();
for (PatientIdentifier identifier : identifiers) {
if (identifier.getIdentifier().equals(pi.getIdentifier()) && identifier.getLocation().equals(pi.getLocation())) {
identifier.setIdentifierType(pi.getIdentifierType());
break;
}
}
Context.getPatientService().savePatient(p);
}
// end patient identifier section
// patient identifier _type_ section
/**
* TODO: Add changedBy and DateChanged columns to table patient_identifier_type
*
* @see org.openmrs.api.PatientService#savePatientIdentifierType(org.openmrs.PatientIdentifierType)
*/
public PatientIdentifierType savePatientIdentifierType(PatientIdentifierType patientIdentifierType) throws APIException {
return dao.savePatientIdentifierType(patientIdentifierType);
}
/**
* @deprecated replaced by {@link #getAllPatientIdentifierTypes()}
* @see org.openmrs.api.PatientService#getPatientIdentifierTypes()
* @see org.openmrs.api.PatientService#getAllPatientIdentifierTypes()
*/
public List<PatientIdentifierType> getPatientIdentifierTypes() throws APIException {
return getAllPatientIdentifierTypes();
}
/**
* @see org.openmrs.api.PatientService#getAllPatientIdentifierTypes()
*/
public List<PatientIdentifierType> getAllPatientIdentifierTypes() throws APIException {
return getAllPatientIdentifierTypes(false);
}
/**
* @see org.openmrs.api.PatientService#getAllPatientIdentifierTypes(boolean)
*/
public List<PatientIdentifierType> getAllPatientIdentifierTypes(boolean includeRetired) throws APIException {
return dao.getAllPatientIdentifierTypes(includeRetired);
}
/**
* @see org.openmrs.api.PatientService#getPatientIdentifierTypes(java.lang.String,
* java.lang.String, java.lang.Boolean, java.lang.Boolean)
*/
public List<PatientIdentifierType> getPatientIdentifierTypes(String name, String format, Boolean required,
Boolean hasCheckDigit) throws APIException {
return dao.getPatientIdentifierTypes(name, format, required, hasCheckDigit);
}
/**
* @see org.openmrs.api.PatientService#getPatientIdentifierType(java.lang.Integer)
*/
public PatientIdentifierType getPatientIdentifierType(Integer patientIdentifierTypeId) throws APIException {
return dao.getPatientIdentifierType(patientIdentifierTypeId);
}
/**
* @see org.openmrs.api.PatientService#getPatientIdentifierType(java.lang.String)
* @deprecated
*/
public PatientIdentifierType getPatientIdentifierType(String name) throws APIException {
return getPatientIdentifierTypeByName(name);
}
/**
* @see org.openmrs.api.PatientService#getPatientIdentifierTypeByName(java.lang.String)
*/
public PatientIdentifierType getPatientIdentifierTypeByName(String name) throws APIException {
List<PatientIdentifierType> types = getPatientIdentifierTypes(name, null, null, null);
if (types.size() > 0)
return types.get(0);
return null;
}
/**
* @see org.openmrs.api.PatientService#retirePatientIdentifierType(org.openmrs.PatientIdentifierType,
* String)
*/
public PatientIdentifierType retirePatientIdentifierType(PatientIdentifierType patientIdentifierType, String reason)
throws APIException {
if (reason == null || reason.length() < 1)
throw new APIException("A reason is required when retiring an identifier type");
patientIdentifierType.setRetired(true);
patientIdentifierType.setRetiredBy(Context.getAuthenticatedUser());
patientIdentifierType.setDateRetired(new Date());
patientIdentifierType.setRetireReason(reason);
return savePatientIdentifierType(patientIdentifierType);
}
/**
* @see org.openmrs.api.PatientService#unretirePatientIdentifierType(org.openmrs.PatientIdentifierType)
*/
public PatientIdentifierType unretirePatientIdentifierType(PatientIdentifierType patientIdentifierType)
throws APIException {
patientIdentifierType.setRetired(false);
patientIdentifierType.setRetiredBy(null);
patientIdentifierType.setDateRetired(null);
patientIdentifierType.setRetireReason(null);
return savePatientIdentifierType(patientIdentifierType);
}
/**
* @see org.openmrs.api.PatientService#purgePatientIdentifierType(org.openmrs.PatientIdentifierType)
*/
public void purgePatientIdentifierType(PatientIdentifierType patientIdentifierType) throws APIException {
dao.deletePatientIdentifierType(patientIdentifierType);
}
// end patient identifier _type_ section
/**
* @see org.openmrs.api.PatientService#findPatients(java.lang.String, boolean)
* @deprecated
*/
public List<Patient> findPatients(String query, boolean includeVoided) throws APIException {
if (includeVoided == true)
throw new APIException("Searching on voided patients is no longer allowed");
return getPatients(query);
}
/**
* @see org.openmrs.api.PatientService#getPatients(java.lang.String)
*/
public List<Patient> getPatients(String query) throws APIException {
List<Patient> patients = new Vector<Patient>();
String minSearchCharactersStr = Context.getAdministrationService().getGlobalProperty(
OpenmrsConstants.GLOBAL_PROPERTY_MIN_SEARCH_CHARACTERS);
int minSearchCharacters;
try {
minSearchCharacters = Integer.valueOf(minSearchCharactersStr);
}
catch (NumberFormatException e) {
// TODO: Should be an application constant
minSearchCharacters = 3;
}
if (query.length() < minSearchCharacters)
return patients;
// if there is a number in the query string
if (query.matches(".*\\d+.*")) {
log.debug("[Identifier search] Query: " + query);
return getPatients(null, query, null);
} else {
// there is no number in the string, search on name
return getPatients(query, null, null, false);
}
}
/**
* @see org.openmrs.api.PatientService#findPatient(org.openmrs.Patient)
* @see #getPatientByExample(Patient)
* @deprecated
*/
public Patient findPatient(Patient patientToMatch) throws APIException {
return getPatientByExample(patientToMatch);
}
/**
* This default implementation simply looks at the OpenMRS internal id (patient_id). If the id
* is null, assume this patient isn't found. If the patient_id is not null, try and find that id
* in the database
*
* @see org.openmrs.api.PatientService#getPatientByExample(org.openmrs.Patient)
*/
public Patient getPatientByExample(Patient patientToMatch) throws APIException {
if (patientToMatch == null || patientToMatch.getPatientId() == null)
return null;
return getPatient(patientToMatch.getPatientId());
}
/**
* @deprecated use {@link #getDuplicatePatientsByAttributes(List)}
*/
public List<Patient> findDuplicatePatients(Set<String> attributes) throws APIException {
List<String> attributesAsList = new Vector<String>();
attributesAsList.addAll(attributes);
return getDuplicatePatientsByAttributes(attributesAsList);
}
/**
* @see org.openmrs.api.PatientService#getDuplicatePatientsByAttributes(java.util.List)
*/
public List<Patient> getDuplicatePatientsByAttributes(List<String> attributes) throws APIException {
if (attributes == null || attributes.size() < 1) {
throw new APIException("There must be at least one attribute supplied to search on");
}
return dao.getDuplicatePatientsByAttributes(attributes);
}
/**
* 1) Moves object (encounters/obs) pointing to <code>nonPreferred</code> to
* <code>preferred</code> 2) Copies data (gender/birthdate/names/ids/etc) from
* <code>nonPreferred</code> to <code>preferred</code> iff the data is missing or null in
* <code>preferred</code> 3) <code>notPreferred</code> is marked as voided
*
* @param preferred
* @param notPreferred
* @throws APIException
* @see org.openmrs.api.PatientService#mergePatients(org.openmrs.Patient, org.openmrs.Patient)
*/
public void mergePatients(Patient preferred, Patient notPreferred) throws APIException {
log.debug("Merging patients: (preferred)" + preferred.getPatientId() + ", (notPreferred) "
+ notPreferred.getPatientId());
if (preferred.getPatientId().equals(notPreferred.getPatientId())) {
log.debug("Merge operation cancelled: Cannot merge user" + preferred.getPatientId() + " to self");
throw new APIException("Merge operation cancelled: Cannot merge user " + preferred.getPatientId() + " to self");
}
// change all encounters. This will cascade to obs and orders contained in those encounters
// TODO: this should be a copy, not a move
EncounterService es = Context.getEncounterService();
for (Encounter e : es.getEncountersByPatient(notPreferred)) {
e.setPatient(preferred);
log.debug("Merging encounter " + e.getEncounterId() + " to " + preferred.getPatientId());
es.saveEncounter(e);
}
// copy all program enrollments
ProgramWorkflowService programService = Context.getProgramWorkflowService();
for (PatientProgram pp : programService.getPatientPrograms(notPreferred, null, null, null, null, null, false)) {
if (!pp.getVoided()) {
PatientProgram enroll = pp.copy();
enroll.setPatient(preferred);
log.debug("Copying patientProgram " + pp.getPatientProgramId() + " to " + preferred.getPatientId());
programService.savePatientProgram(enroll);
}
}
// copy all relationships
PersonService personService = Context.getPersonService();
for (Relationship rel : personService.getRelationshipsByPerson(notPreferred)) {
if (!rel.isVoided()) {
// skip over this relationship if its just between the preferred and notpreferred patients
if (!((rel.getPersonA().equals(notPreferred) && rel.getPersonB().equals(preferred)) || rel.getPersonB()
.equals(notPreferred)
&& rel.getPersonA().equals(preferred))) {
Relationship tmpRel = rel.copy();
if (tmpRel.getPersonA().equals(notPreferred))
tmpRel.setPersonA(preferred);
if (tmpRel.getPersonB().equals(notPreferred))
tmpRel.setPersonB(preferred);
log.debug("Copying relationship " + rel.getRelationshipId() + " to " + preferred.getPatientId());
personService.saveRelationship(tmpRel);
}
}
}
// move all obs that weren't contained in encounters
// TODO: this should be a copy, not a move
ObsService obsService = Context.getObsService();
for (Obs obs : obsService.getObservationsByPerson(notPreferred)) {
if (obs.getEncounter() == null && !obs.isVoided()) {
obs.setPerson(preferred);
obsService.saveObs(obs, "Merged from patient #" + notPreferred.getPatientId());
}
}
// copy all orders that weren't contained in encounters
OrderService os = Context.getOrderService();
for (Order o : os.getOrdersByPatient(notPreferred)) {
if (o.getEncounter() == null && !o.getVoided()) {
Order tmpOrder = o.copy();
tmpOrder.setPatient(preferred);
os.saveOrder(tmpOrder);
}
}
// move all identifiers
// (must be done after all calls to services above so hbm doesn't try to save things prematurely (hacky)
for (PatientIdentifier pi : notPreferred.getActiveIdentifiers()) {
PatientIdentifier tmpIdentifier = new PatientIdentifier();
tmpIdentifier.setIdentifier(pi.getIdentifier());
tmpIdentifier.setIdentifierType(null); // don't compare identifier
// types.
tmpIdentifier.setLocation(pi.getLocation());
tmpIdentifier.setPatient(preferred);
boolean found = false;
for (PatientIdentifier preferredIdentifier : preferred.getIdentifiers()) {
if (preferredIdentifier.getIdentifier() != null
&& preferredIdentifier.getIdentifier().equals(tmpIdentifier.getIdentifier())
&& preferredIdentifier.getIdentifierType() != null
&& preferredIdentifier.getIdentifierType().equals(tmpIdentifier.getIdentifierType()))
found = true;
}
if (!found) {
tmpIdentifier.setIdentifierType(pi.getIdentifierType());
tmpIdentifier.setCreator(Context.getAuthenticatedUser());
tmpIdentifier.setDateCreated(new Date());
tmpIdentifier.setVoided(false);
tmpIdentifier.setVoidedBy(null);
tmpIdentifier.setVoidReason(null);
tmpIdentifier.setUuid(UUID.randomUUID().toString());
// we don't want to change the preferred identifier of the preferred patient
tmpIdentifier.setPreferred(false);
preferred.addIdentifier(tmpIdentifier);
log.debug("Merging identifier " + tmpIdentifier.getIdentifier() + " to " + preferred.getPatientId());
}
}
// move all names
// (must be done after all calls to services above so hbm doesn't try to save things prematurely (hacky)
for (PersonName newName : notPreferred.getNames()) {
boolean containsName = false;
for (PersonName currentName : preferred.getNames()) {
String given = newName.getGivenName();
String middle = newName.getMiddleName();
String family = newName.getFamilyName();
if ((given != null && given.equals(currentName.getGivenName()))
&& (middle != null && middle.equals(currentName.getMiddleName()))
&& (family != null && family.equals(currentName.getFamilyName()))) {
containsName = true;
}
}
if (!containsName) {
PersonName tmpName = PersonName.newInstance(newName);
tmpName.setPersonNameId(null);
tmpName.setVoided(false);
tmpName.setVoidedBy(null);
tmpName.setVoidReason(null);
// we don't want to change the preferred name of the preferred patient
tmpName.setPreferred(false);
tmpName.setUuid(UUID.randomUUID().toString());
preferred.addName(tmpName);
log.debug("Merging name " + newName.getGivenName() + " to " + preferred.getPatientId());
}
}
// move all addresses
// (must be done after all calls to services above so hbm doesn't try to save things prematurely (hacky)
for (PersonAddress newAddress : notPreferred.getAddresses()) {
boolean containsAddress = false;
for (PersonAddress currentAddress : preferred.getAddresses()) {
String address1 = currentAddress.getAddress1();
String address2 = currentAddress.getAddress2();
String cityVillage = currentAddress.getCityVillage();
if ((address1 != null && address1.equals(newAddress.getAddress1()))
&& (address2 != null && address2.equals(newAddress.getAddress2()))
&& (cityVillage != null && cityVillage.equals(newAddress.getCityVillage()))) {
containsAddress = true;
}
}
if (!containsAddress) {
PersonAddress tmpAddress = (PersonAddress) newAddress.clone();
tmpAddress.setPersonAddressId(null);
tmpAddress.setVoided(false);
tmpAddress.setVoidedBy(null);
tmpAddress.setVoidReason(null);
tmpAddress.setUuid(UUID.randomUUID().toString());
preferred.addAddress(tmpAddress);
log.debug("Merging address " + newAddress.getPersonAddressId() + " to " + preferred.getPatientId());
}
}
// copy person attributes
for (PersonAttribute attr : notPreferred.getAttributes()) {
if (!attr.isVoided()) {
PersonAttribute tmpAttr = attr.copy();
tmpAttr.setPerson(null);
tmpAttr.setUuid(UUID.randomUUID().toString());
preferred.addAttribute(tmpAttr);
}
}
// move all other patient info
if (!"M".equals(preferred.getGender()) && !"F".equals(preferred.getGender()))
preferred.setGender(notPreferred.getGender());
/*
* if (preferred.getRace() == null || preferred.getRace().equals(""))
* preferred.setRace(notPreferred.getRace());
*/
if (preferred.getBirthdate() == null || preferred.getBirthdate().equals("")
|| (preferred.getBirthdateEstimated() && !notPreferred.getBirthdateEstimated())) {
preferred.setBirthdate(notPreferred.getBirthdate());
preferred.setBirthdateEstimated(notPreferred.getBirthdateEstimated());
}
if (preferred.getDeathDate() == null || preferred.getDeathDate().equals(""))
preferred.setDeathDate(notPreferred.getDeathDate());
if (preferred.getCauseOfDeath() == null || preferred.getCauseOfDeath().equals(""))
preferred.setCauseOfDeath(notPreferred.getCauseOfDeath());
// void the non preferred patient
Context.getPatientService().voidPatient(notPreferred, "Merged with patient #" + preferred.getPatientId());
// Save the newly update preferred patient
// This must be called _after_ voiding the nonPreferred patient so that
// a "Duplicate Identifier" error doesn't pop up.
savePatient(preferred);
}
/**
* This is the way to establish that a patient has left the care center. This API call is
* responsible for:
* <ol>
* <li>Closing workflow statuses</li>
* <li>Terminating programs</li>
* <li>Discontinuing orders</li>
* <li>Flagging patient table</li>
* <li>Creating any relevant observations about the patient (if applicable)</li>
* </ol>
*
* @param patient - the patient who has exited care
* @param dateExited - the declared date/time of the patient's exit
* @param reasonForExit - the concept that corresponds with why the patient has been declared as
* exited
* @throws APIException
*/
public void exitFromCare(Patient patient, Date dateExited, Concept reasonForExit) throws APIException {
if (patient == null)
throw new APIException("Attempting to exit from care an invalid patient. Cannot proceed");
if (dateExited == null)
throw new APIException("Must supply a valid dateExited when indicating that a patient has left care");
if (reasonForExit == null)
throw new APIException(
"Must supply a valid reasonForExit (even if 'Unknown') when indicating that a patient has left care");
// need to create an observation to represent this (otherwise how
// will we know?)
saveReasonForExitObs(patient, dateExited, reasonForExit);
// need to terminate any applicable programs
Context.getProgramWorkflowService().triggerStateConversion(patient, reasonForExit, dateExited);
// need to discontinue any open orders for this patient
OrderUtil.discontinueAllOrders(patient, reasonForExit, dateExited);
}
/**
* TODO: Patients should actually be allowed to exit multiple times
*
* @param patient
* @param exitDate
* @param cause
*/
private void saveReasonForExitObs(Patient patient, Date exitDate, Concept cause) throws APIException {
if (patient == null)
throw new APIException("Patient supplied to method is null");
if (exitDate == null)
throw new APIException("Exit date supplied to method is null");
if (cause == null)
throw new APIException("Cause supplied to method is null");
// need to make sure there is an Obs that represents the patient's
// exit
log.debug("Patient is exiting, so let's make sure there's an Obs for it");
String codProp = Context.getAdministrationService().getGlobalProperty("concept.reasonExitedCare");
Concept reasonForExit = Context.getConceptService().getConcept(codProp);
if (reasonForExit != null) {
List<Obs> obssExit = Context.getObsService().getObservationsByPersonAndConcept(patient, reasonForExit);
if (obssExit != null) {
if (obssExit.size() > 1) {
log.error("Multiple reasons for exit (" + obssExit.size() + ")? Shouldn't be...");
} else {
Obs obsExit = null;
if (obssExit.size() == 1) {
// already has a reason for exit - let's edit it.
log.debug("Already has a reason for exit, so changing it");
obsExit = obssExit.iterator().next();
} else {
// no reason for exit obs yet, so let's make one
log.debug("No reason for exit yet, let's create one.");
obsExit = new Obs();
obsExit.setPerson(patient);
obsExit.setConcept(reasonForExit);
Location loc = Context.getLocationService().getDefaultLocation();
if (loc != null)
obsExit.setLocation(loc);
else
log.error("Could not find a suitable location for which to create this new Obs");
}
if (obsExit != null) {
// put the right concept and (maybe) text in this
// obs
obsExit.setValueCoded(cause);
obsExit.setValueCodedName(cause.getName()); // ABKTODO: presume current locale?
obsExit.setObsDatetime(exitDate);
Context.getObsService().saveObs(obsExit, null);
}
}
}
} else {
log.debug("Reason for exit is null - should not have gotten here without throwing an error on the form.");
}
}
/**
* This is the way to establish that a patient has died. In addition to exiting the patient from
* care (see above), this method will also set the appropriate patient characteristics to
* indicate that they have died, when they died, etc.
*
* @param patient - the patient who has died
* @param dateDied - the declared date/time of the patient's death
* @param causeOfDeath - the concept that corresponds with the reason the patient died
* @param otherReason - in case the causeOfDeath is 'other', a place to store more info
* @throws APIException
*/
public void processDeath(Patient patient, Date dateDied, Concept causeOfDeath, String otherReason) throws APIException {
//SQLStateConverter s = null;
if (patient != null && dateDied != null && causeOfDeath != null) {
// set appropriate patient characteristics
patient.setDead(true);
patient.setDeathDate(dateDied);
patient.setCauseOfDeath(causeOfDeath);
this.updatePatient(patient);
saveCauseOfDeathObs(patient, dateDied, causeOfDeath, otherReason);
// exit from program
// first, need to get Concept for "Patient Died"
String strPatientDied = Context.getAdministrationService().getGlobalProperty("concept.patientDied");
Concept conceptPatientDied = Context.getConceptService().getConcept(strPatientDied);
if (conceptPatientDied == null)
log.debug("ConceptPatientDied is null");
exitFromCare(patient, dateDied, conceptPatientDied);
} else {
if (patient == null)
throw new APIException("Attempting to set an invalid patient's status to 'dead'");
if (dateDied == null)
throw new APIException("Must supply a valid dateDied when indicating that a patient has died");
if (causeOfDeath == null)
throw new APIException(
"Must supply a valid causeOfDeath (even if 'Unknown') when indicating that a patient has died");
}
}
/**
* @see org.openmrs.api.PatientService#saveCauseOfDeathObs(org.openmrs.Patient, java.util.Date,
* org.openmrs.Concept, java.lang.String)
*/
public void saveCauseOfDeathObs(Patient patient, Date deathDate, Concept cause, String otherReason) throws APIException {
if (patient == null)
throw new APIException("Patient supplied to method is null");
if (deathDate == null)
throw new APIException("Death date supplied to method is null");
if (cause == null)
throw new APIException("Cause supplied to method is null");
if (!patient.getDead()) {
patient.setDead(true);
patient.setDeathDate(deathDate);
patient.setCauseOfDeath(cause);
}
log.debug("Patient is dead, so let's make sure there's an Obs for it");
// need to make sure there is an Obs that represents the patient's
// cause of death, if applicable
String codProp = Context.getAdministrationService().getGlobalProperty("concept.causeOfDeath");
Concept causeOfDeath = Context.getConceptService().getConcept(codProp);
if (causeOfDeath != null) {
List<Obs> obssDeath = Context.getObsService().getObservationsByPersonAndConcept(patient, causeOfDeath);
if (obssDeath != null) {
if (obssDeath.size() > 1) {
log.error("Multiple causes of death (" + obssDeath.size() + ")? Shouldn't be...");
} else {
Obs obsDeath = null;
if (obssDeath.size() == 1) {
// already has a cause of death - let's edit it.
log.debug("Already has a cause of death, so changing it");
obsDeath = obssDeath.iterator().next();
} else {
// no cause of death obs yet, so let's make one
log.debug("No cause of death yet, let's create one.");
obsDeath = new Obs();
obsDeath.setPerson(patient);
obsDeath.setConcept(causeOfDeath);
Location location = Context.getLocationService().getDefaultLocation();
if (location != null) {
obsDeath.setLocation(location);
} else {
log.error("Could not find a suitable location for which to create this new Obs");
}
}
// put the right concept and (maybe) text in this obs
Concept currCause = patient.getCauseOfDeath();
if (currCause == null) {
// set to NONE
log.debug("Current cause is null, attempting to set to NONE");
String noneConcept = Context.getAdministrationService().getGlobalProperty("concept.none");
currCause = Context.getConceptService().getConcept(noneConcept);
}
if (currCause != null) {
log.debug("Current cause is not null, setting to value_coded");
obsDeath.setValueCoded(currCause);
obsDeath.setValueCodedName(currCause.getName()); // ABKTODO: presume current locale?
Date dateDeath = patient.getDeathDate();
if (dateDeath == null)
dateDeath = new Date();
obsDeath.setObsDatetime(dateDeath);
// check if this is an "other" concept - if so, then
// we need to add value_text
String otherConcept = Context.getAdministrationService().getGlobalProperty("concept.otherNonCoded");
Concept conceptOther = Context.getConceptService().getConcept(otherConcept);
if (conceptOther != null) {
if (conceptOther.equals(currCause)) {
// seems like this is an other concept -
// let's try to get the "other" field info
log.debug("Setting value_text as " + otherReason);
obsDeath.setValueText(otherReason);
} else {
log.debug("New concept is NOT the OTHER concept, so setting to blank");
obsDeath.setValueText("");
}
} else {
log.debug("Don't seem to know about an OTHER concept, so deleting value_text");
obsDeath.setValueText("");
}
Context.getObsService().saveObs(obsDeath, null);
} else {
log.debug("Current cause is still null - aborting mission");
}
}
}
} else {
log.debug("Cause of death is null - should not have gotten here without throwing an error on the form.");
}
}
/**
* @see org.openmrs.api.PatientService#getPatientByUuid(java.lang.String)
*/
public Patient getPatientByUuid(String uuid) throws APIException {
return dao.getPatientByUuid(uuid);
}
public PatientIdentifier getPatientIdentifierByUuid(String uuid) throws APIException {
return dao.getPatientIdentifierByUuid(uuid);
}
/**
* @see org.openmrs.api.PatientService#getPatientIdentifierTypeByUuid(java.lang.String)
*/
public PatientIdentifierType getPatientIdentifierTypeByUuid(String uuid) throws APIException {
return dao.getPatientIdentifierTypeByUuid(uuid);
}
/**
* @see org.openmrs.api.PatientService#getDefaultIdentifierValidator()
*/
public IdentifierValidator getDefaultIdentifierValidator() {
String defaultPIV = Context.getAdministrationService().getGlobalProperty(
OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_PATIENT_IDENTIFIER_VALIDATOR, "");
try {
return identifierValidators.get(Class.forName(defaultPIV));
}
catch (ClassNotFoundException e) {
log.error("Global Property " + OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_PATIENT_IDENTIFIER_VALIDATOR
+ " not set to an actual class.", e);
return identifierValidators.get(LuhnIdentifierValidator.class);
}
}
/**
* @see org.openmrs.api.PatientService#getIdentifierValidator(java.lang.String)
*/
public IdentifierValidator getIdentifierValidator(Class<IdentifierValidator> identifierValidator) {
return identifierValidators.get(identifierValidator);
}
public Map<Class<? extends IdentifierValidator>, IdentifierValidator> getIdentifierValidators() {
if (identifierValidators == null)
identifierValidators = new LinkedHashMap<Class<? extends IdentifierValidator>, IdentifierValidator>();
return identifierValidators;
}
/**
* ADDs identifierValidators, doesn't replace them
*
* @param identifierValidators
*/
public void setIdentifierValidators(Map<Class<? extends IdentifierValidator>, IdentifierValidator> identifierValidators) {
for (Map.Entry<Class<? extends IdentifierValidator>, IdentifierValidator> entry : identifierValidators.entrySet()) {
getIdentifierValidators().put(entry.getKey(), entry.getValue());
}
}
/**
* @see org.openmrs.api.PatientService#getAllIdentifierValidators()
*/
public Collection<IdentifierValidator> getAllIdentifierValidators() {
return identifierValidators.values();
}
/**
* @see org.openmrs.api.PatientService#getIdentifierValidator(java.lang.String)
*/
@SuppressWarnings("unchecked")
public IdentifierValidator getIdentifierValidator(String pivClassName) {
try {
return getIdentifierValidator(((Class<IdentifierValidator>) Context.loadClass(pivClassName)));
}
catch (ClassNotFoundException e) {
log.error("Could not find patient identifier validator " + pivClassName, e);
return getDefaultIdentifierValidator();
}
}
/**
* @see org.openmrs.api.PatientService#isIdentifierInUseByAnotherPatient(org.openmrs.PatientIdentifier)
*/
public boolean isIdentifierInUseByAnotherPatient(PatientIdentifier patientIdentifier) {
return dao.isIdentifierInUseByAnotherPatient(patientIdentifier);
}
/**
* @see org.openmrs.api.PatientService#getPatientIdentifier(java.lang.Integer)
*/
public PatientIdentifier getPatientIdentifier(Integer patientIdentifierId) throws APIException {
return dao.getPatientIdentifier(patientIdentifierId);
}
/**
* @see org.openmrs.api.PatientService#voidPatientIdentifier(org.openmrs.PatientIdentifier,
* java.lang.String)
*/
@Override
public PatientIdentifier voidPatientIdentifier(PatientIdentifier patientIdentifier, String reason) throws APIException {
if (patientIdentifier == null || StringUtils.isBlank(reason))
throw new APIException("patientIdentifier can't be null and the reason should not be an empty string");
return savePatientIdentifier(patientIdentifier);
}
/**
* @see org.openmrs.api.PatientService#savePatientIdentifier(org.openmrs.PatientIdentifier)
*/
public PatientIdentifier savePatientIdentifier(PatientIdentifier patientIdentifier) throws APIException {
//if the argument or the following required fields are not specified
if (patientIdentifier == null || patientIdentifier.getPatient() == null
|| patientIdentifier.getIdentifierType() == null || patientIdentifier.getLocation() == null
|| StringUtils.isBlank(patientIdentifier.getIdentifier()))
throw new APIException("PatientIdentifier argument or one of its required fields is null or invalid");
if (patientIdentifier.getPatientIdentifierId() == null) {
Context.requirePrivilege(OpenmrsConstants.PRIV_ADD_PATIENT_IDENTIFIERS);
} else
Context.requirePrivilege(OpenmrsConstants.PRIV_EDIT_PATIENT_IDENTIFIERS);
return dao.savePatientIdentifier(patientIdentifier);
}
/**
* @see org.openmrs.api.PatientService#purgePatientIdentifier(org.openmrs.PatientIdentifier)
*/
public void purgePatientIdentifier(PatientIdentifier patientIdentifier) throws APIException {
dao.deletePatientIdentifier(patientIdentifier);
}
@Override
public List<Patient> searchPatient(String name) throws ParseException {
return dao.searchPatient(name);
}
}