OrderServiceImpl.java
/**
* 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.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.ConceptClass;
import org.openmrs.Drug;
import org.openmrs.DrugOrder;
import org.openmrs.Encounter;
import org.openmrs.GenericDrug;
import org.openmrs.Order;
import org.openmrs.Order.OrderAction;
import org.openmrs.Orderable;
import org.openmrs.Patient;
import org.openmrs.User;
import org.openmrs.api.APIException;
import org.openmrs.api.OrderService;
import org.openmrs.api.context.Context;
import org.openmrs.api.db.OrderDAO;
import org.openmrs.order.DrugOrderSupport;
import org.openmrs.order.RegimenSuggestion;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;
import org.springframework.transaction.annotation.Transactional;
/**
* Default implementation of the Order-related services class. This class should not be invoked by
* itself. Spring injection is used to inject this implementation into the ServiceContext. Which
* implementation is injected is determined by the spring application context file:
* /metadata/api/spring/applicationContext.xml
*
* @see org.openmrs.api.OrderService
*/
@Transactional
public class OrderServiceImpl extends BaseOpenmrsService implements OrderService {
protected final Log log = LogFactory.getLog(getClass());
protected OrderDAO dao;
/**
* Used to store the last used order number
*/
private static Integer orderNumberCounter = -1;
public OrderServiceImpl() {
}
/**
* @see org.openmrs.api.OrderService#setOrderDAO(org.openmrs.api.db.OrderDAO)
*/
@Override
public void setOrderDAO(OrderDAO dao) {
this.dao = dao;
}
/**
* @see org.openmrs.api.OrderService#saveOrder(org.openmrs.Order)
*/
@Override
public Order saveOrder(Order order) throws APIException {
checkIfModifyingSavedOrderNumber(order);
//No editing of orders. We instead create a new one.
if (order.getOrderId() != null) {
Order newOrder = new Order();
newOrder.setConcept(order.getConcept());
newOrder.setPatient(order.getPatient());
newOrder.setOrderNumber(order.getOrderNumber());
newOrder.setStartDate(new Date());
newOrder.setUuid(UUID.randomUUID().toString());
return saveOrderWithLesserValidation(newOrder);
} else
return saveOrderWithLesserValidation(order);
}
/**
* @see org.openmrs.api.OrderService#purgeOrder(org.openmrs.Order)
*/
@Override
public void purgeOrder(Order order) throws APIException {
purgeOrder(order, false);
}
/**
* @see org.openmrs.api.OrderService#purgeOrder(Order)
*/
public void purgeOrder(Order order, boolean cascade) throws APIException {
if (cascade) {
// TODO delete other order stuff before deleting this order
// (like DrugOrder?)
throw new APIException("Cascade purging of Orders is not written yet");
}
dao.deleteOrder(order);
}
/**
* @see org.openmrs.api.OrderService#voidOrder(org.openmrs.Order, java.lang.String)
*/
@Override
public Order voidOrder(Order order, String voidReason) throws APIException {
return dao.saveOrder(order);
}
/**
* @see org.openmrs.api.OrderService#unvoidOrder(org.openmrs.Order)
*/
@Override
public Order unvoidOrder(Order order) throws APIException {
return saveOrderWithLesserValidation(order);
}
/**
* @see org.openmrs.api.OrderService#getOrder(java.lang.Integer)
*/
@Override
@Transactional(readOnly = true)
public Order getOrder(Integer orderId) throws APIException {
return getOrder(orderId, Order.class);
}
/**
* @see org.openmrs.api.OrderService#getOrder(java.lang.Integer, java.lang.Class)
*/
@Override
@Transactional(readOnly = true)
public <o extends Order> o getOrder(Integer orderId, Class<o> orderClassType) throws APIException {
return dao.getOrder(orderId, orderClassType);
}
/**
* @see org.openmrs.api.OrderService#getOrders(java.lang.Class, java.util.List, java.util.List,
* java.util.List, java.util.List, java.util.Date, java.util.List, java.util.List)
*/
@Transactional(readOnly = true)
public <Ord extends Order> List<Ord> getOrders(Class<Ord> orderClassType, List<Patient> patients,
List<Concept> concepts, List<User> orderers, List<Encounter> encounters, Date asOfDate,
List<OrderAction> actionsToInclude, List<OrderAction> actionsToExclude) {
return getOrders(orderClassType, patients, concepts, orderers, encounters, asOfDate, actionsToInclude,
actionsToExclude, false);
}
private <Ord extends Order> List<Ord> getOrders(Class<Ord> orderClassType, List<Patient> patients,
List<Concept> concepts, List<User> orderers, List<Encounter> encounters, Date asOfDate,
List<OrderAction> actionsToInclude, List<OrderAction> actionsToExclude, boolean includeVoided) {
if (orderClassType == null)
throw new APIException("orderClassType cannot be null. An order type of Order.class or a subclass is required");
return dao.getOrders(orderClassType, patients, concepts, orderers, encounters, asOfDate, actionsToInclude,
actionsToExclude, includeVoided);
}
/**
* @see org.openmrs.api.OrderService#getOrdersByPatient(org.openmrs.Patient)
*/
@Override
@Transactional(readOnly = true)
public List<Order> getOrdersByPatient(Patient patient) throws APIException {
return getOrdersByPatient(patient, false);
}
/**
* @see org.openmrs.api.OrderService#getDrugOrdersByPatient(org.openmrs.Patient, boolean)
*/
@SuppressWarnings("unchecked")
@Override
@Transactional(readOnly = true)
public List<DrugOrder> getDrugOrdersByPatient(Patient patient, boolean includeVoided) {
if (patient == null)
throw new APIException("Unable to get drug orders if not given a patient");
List<Patient> patients = new Vector<Patient>();
patients.add(patient);
// TODO: could use this call to get only ACTIVE. Getting complete is done with asOfDate.
// with those two calls you can get rid of all this extra logic
List<DrugOrder> drugOrders = getOrders(DrugOrder.class, patients, null, null, null, null, null, null);
// loop over the drug orders and add them if they are within the current desired order
if (drugOrders != null) {
if (includeVoided)
return drugOrders;
//TODO should be done with a query
//remove voided ones
List<DrugOrder> ret = new ArrayList<DrugOrder>();
for (DrugOrder drugOrder : drugOrders) {
if (!drugOrder.getVoided())
ret.add(drugOrder);
}
return ret;
}
// default return if no drug orders were found in the database
return Collections.EMPTY_LIST;
}
/**
* @see org.openmrs.api.OrderService#getDrugOrdersByPatient(org.openmrs.Patient)
*/
@Override
@Transactional(readOnly = true)
public List<DrugOrder> getDrugOrdersByPatient(Patient patient) throws APIException {
List<Patient> patients = new Vector<Patient>();
patients.add(patient);
return getOrders(DrugOrder.class, patients, null, null, null, null, null, null);
}
/**
* @see org.openmrs.api.OrderService#getStandardRegimens()
*/
@Override
@Transactional(readOnly = true)
public List<RegimenSuggestion> getStandardRegimens() {
DrugOrderSupport dos = null;
List<RegimenSuggestion> standardRegimens = null;
try {
dos = DrugOrderSupport.getInstance();
}
catch (Exception e) {
log.error("Error getting instance of DrugOrderSupport object", e);
}
if (dos != null) {
standardRegimens = dos.getStandardRegimens();
} else {
log.error("DrugOrderSupport object is null after new instance");
}
return standardRegimens;
}
/**
* @see org.openmrs.api.OrderService#getOrderByUuid(java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public Order getOrderByUuid(String uuid) throws APIException {
return dao.getOrderByUuid(uuid);
}
/**
* @see org.openmrs.api.OrderService#getOrderByOrderNumber(java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public Order getOrderByOrderNumber(String orderNumber) {
return dao.getOrderByOrderNumber(orderNumber);
}
/**
* @see org.openmrs.api.OrderService#getOrderHistoryByConcept(org.openmrs.Patient,
* org.openmrs.Concept)
*/
@Override
@Transactional(readOnly = true)
public List<Order> getOrderHistoryByConcept(Patient patient, Concept concept) {
if (patient == null)
throw new IllegalArgumentException("patient is required");
List<Concept> concepts = new Vector<Concept>();
concepts.add(concept);
List<Patient> patients = new Vector<Patient>();
patients.add(patient);
return getOrders(Order.class, patients, concepts, null, null, null, null, null);
}
/**
* @see org.openmrs.api.OrderService#getActiveOrdersByPatient(org.openmrs.Patient,
* java.util.Date)
*/
@Override
@Transactional(readOnly = true)
public List<Order> getActiveOrdersByPatient(Patient p, Date date) throws APIException {
if (p == null)
throw new IllegalArgumentException("patient is required");
if (date == null)
date = new Date();
List<Patient> patients = new Vector<Patient>();
patients.add(p);
return getOrders(Order.class, patients, null, null, null, date, null, Arrays.asList(OrderAction.DISCONTINUE));
}
/**
* @see org.openmrs.api.OrderService#getActiveDrugOrdersByPatient(org.openmrs.Patient,
* java.util.Date)
*/
@Override
@Transactional(readOnly = true)
public List<DrugOrder> getActiveDrugOrdersByPatient(Patient p, Date date) {
if (p == null)
throw new IllegalArgumentException("patient is required");
if (date == null)
date = new Date();
List<Patient> patients = new Vector<Patient>();
patients.add(p);
// TODO: add "NOT discontinued" action to this call
return getOrders(DrugOrder.class, patients, null, null, null, date, null, Arrays.asList(OrderAction.DISCONTINUE));
}
/**
* @see org.openmrs.api.OrderService#getOrderables(java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public List<Orderable<?>> getOrderables(String query) throws APIException {
if (query == null)
throw new IllegalArgumentException("Orderable concept name is required");
List<Orderable<?>> result = new ArrayList<Orderable<?>>();
// then look for concepts that are drugs
List<Concept> concepts = Context.getConceptService().getConceptsByName(query);
if (concepts != null) {
for (Concept concept : concepts) {
if (concept.getConceptClass().getUuid().equals(ConceptClass.DRUG_UUID))
result.add(new GenericDrug(concept));
}
}
// and next to try to find drugs by name
List<Drug> drugs = Context.getConceptService().getDrugs(query);
if (drugs != null) {
for (Drug drug : drugs) {
if (!drug.isRetired())
result.add(drug);
}
}
return result;
}
/**
* @see org.openmrs.api.OrderService#discontinueOrder(org.openmrs.Order, java.lang.String,
* org.openmrs.User, java.util.Date)
*/
@Override
public Order discontinueOrder(Order order, String reason, User user, Date discontinueDate) throws APIException {
order.setDiscontinuedReason(reason);
return doDiscontinueOrder(order, user, discontinueDate);
}
/**
* @see org.openmrs.api.OrderService#discontinueOrder(org.openmrs.Order, java.lang.String)
*/
@Override
public Order discontinueOrder(Order order, String reason) throws APIException {
return discontinueOrder(order, reason, null, null);
}
/**
* Utility method that discontinues an order
*
* @param oldOrder the order to discontinue
* @param user the user discontinuing the order, default to authenticated user
* @param discontinueDate the date when to discontinue the order, default to now. Note that this
* method only uses the passed in discontinue date if it is a future date and the
* order is not yet activated
* @return the discontinued order
* @throws APIException
*/
private Order doDiscontinueOrder(Order oldOrder, User user, Date discontinueDate) throws APIException {
if (user == null)
user = Context.getAuthenticatedUser();
if (discontinueDate == null)
discontinueDate = new Date();
else if (discontinueDate.after(new Date()))
throw new APIException("Cannot discontinue an order in the future");
if (oldOrder.getDiscontinued())
throw new APIException("Cannot discontinue an order that is already discontinued");
if (oldOrder.getAutoExpireDate() != null && OpenmrsUtil.compare(discontinueDate, oldOrder.getAutoExpireDate()) > 0)
throw new APIException("Cannot discontinue an order after its autoexpire date");
oldOrder.setDiscontinued(Boolean.TRUE);
oldOrder.setDiscontinuedBy(user);
oldOrder.setDiscontinuedDate(discontinueDate);
saveOrderWithLesserValidation(oldOrder);
Order newOrder = new Order();
newOrder.setOrderNumber(getNewOrderNumber());
newOrder.setConcept(oldOrder.getConcept());
newOrder.setPatient(oldOrder.getPatient());
newOrder.setPreviousOrderNumber(oldOrder.getOrderNumber());
newOrder.setOrderAction(OrderAction.DISCONTINUE);
newOrder.setStartDate(new Date());
newOrder.setUuid(UUID.randomUUID().toString());
Context.getOrderService().saveOrder(newOrder);
return oldOrder;
}
/**
* Convenience method to be called within the API to enable persisting changes in an existing
* order while surpassing certain validation constraints e.g when discontinuing an order
*
* @param order
* @return
* @throws APIException
*/
private Order saveOrderWithLesserValidation(Order order) throws APIException {
checkIfModifyingSavedOrderNumber(order);
return dao.saveOrder(order);
}
private void checkIfModifyingSavedOrderNumber(Order order) {
String orderNumberInDatabase = dao.getOrderNumberInDatabase(order);
if (orderNumberInDatabase != null && !orderNumberInDatabase.equals(order.getOrderNumber()))
throw new APIException("Cannot modify the orderNumber of a saved order");
}
/**
* @see org.openmrs.api.OrderService#getOrderable(java.lang.String)
*/
@Transactional(readOnly = true)
public Orderable<?> getOrderable(String identifier) throws APIException {
if (identifier == null)
throw new IllegalArgumentException("Orderable identifier is required");
Integer numericIdentifier = GenericDrug.getNumericIdentifier(identifier);
if (numericIdentifier != null) {
Concept concept = Context.getConceptService().getConcept(numericIdentifier);
if (concept != null) {
return new GenericDrug(concept);
}
}
numericIdentifier = Drug.getNumericIdentifier(identifier);
if (numericIdentifier != null) {
Drug drug = Context.getConceptService().getDrug(numericIdentifier);
if (drug != null) {
return drug;
}
}
//Do we have other types to check?
return null;
}
/**
* @see org.openmrs.api.OrderService#getNewOrderNumber()
*/
@Override
@Transactional(readOnly = true)
public String getNewOrderNumber() {
Integer next;
synchronized (orderNumberCounter) {
if (orderNumberCounter < 0) {
// we've just started up, so we need to fetch this from the DAO
Integer temp = dao.getHighestOrderId();
orderNumberCounter = temp == null ? 0 : temp;
}
orderNumberCounter += 1;
next = orderNumberCounter;
}
return Context.getAdministrationService().getGlobalProperty(OpenmrsConstants.GP_ORDER_ENTRY_ORDER_NUMBER_PREFIX,
OpenmrsConstants.ORDER_NUMBER_DEFAULT_PREFIX)
+ next;
}
/**
* @see org.openmrs.api.OrderService#getDrugOrdersByPatientAndIngredient(org.openmrs.Patient,
* org.openmrs.Concept)
*/
@Transactional(readOnly = true)
public List<DrugOrder> getDrugOrdersByPatientAndIngredient(Patient patient, Concept ingredient) throws APIException {
if (patient == null)
throw new IllegalArgumentException("patient is required");
if (ingredient == null)
throw new IllegalArgumentException("ingredient is required");
return dao.getDrugOrdersByPatientAndIngredient(patient, ingredient);
}
/**
* @see org.openmrs.api.OrderService#getOrdersByPatient(org.openmrs.Patient, java.lang.Boolean)
*/
@Transactional(readOnly = true)
public List<Order> getOrdersByPatient(Patient patient, boolean includeVoided) throws APIException {
if (patient == null)
throw new APIException("Unable to get orders if I am not given a patient");
List<Patient> patients = new ArrayList<Patient>();
patients.add(patient);
return getOrders(Order.class, patients, null, null, null, null, null, null, includeVoided);
}
/**
* @see org.openmrs.api.OrderService#getOrdersByEncounter(org.openmrs.Encounter)
*/
@Transactional(readOnly = true)
public List<Order> getOrdersByEncounter(Encounter encounter) throws APIException {
if (encounter == null)
throw new APIException("Unable to get orders if I am not given an encounter");
List<Encounter> encounters = new ArrayList<Encounter>();
encounters.add(encounter);
return getOrders(Order.class, null, null, null, encounters, null, null, null);
}
/**
* @see org.openmrs.api.OrderService#getOrdersByOrderer(org.openmrs.User)
*/
@Transactional(readOnly = true)
public List<Order> getOrdersByOrderer(User orderer) throws APIException {
if (orderer == null)
throw new APIException("Unable to get orders if I am not given an orderer");
List<User> orderers = new ArrayList<User>();
orderers.add(orderer);
return getOrders(Order.class, null, null, orderers, null, null, null, null);
}
}