Order Entry API

This is documentation for the reworked order entry API, so it only applies to Reference Application 2.1+ and Platform 1.10+

Overview

The order entry API was refactored with OpenMRS Platform 1.10 and designed to allow handling the relatively complex ordering workflows in a typical clinical setting.  For a more detailed technical notes see API Support for Order Entry (Design Page)

It is designed to address a set of specific use cases – e.g., placing an order, revising it, discontinuing it, and being able to look up a patient's active orders.

Definitions

CareSetting

Care settings define the scope of orders.  For many OpenMRS implementations used in outpatient clinics or in a hospital (but not both), a single care settings will suffice for all orders.  Implementations that use OpenMRS in inpatient and outpatient settings, will likely want to use two care settings in order to separate inpatient (hospital) and outpatient (clinic) orders.  Each order must be belong to a care setting and is considered an active order only within that care setting. The core API ships with 2 care settings – i.e INPATIENT and OUTPATIENT, but an implementation or module can define others.

OrderFrequency

The frequency at which an Order's action should be repeated, e.g. TWICE DAILY or EVERY 6 HOURS. This is associated to a Concept for i18n, synonyms, mappings, etc, but it contains additional details an electronic system can use to understand its meaning. OrderFrequency has frequencyPerDay per day field that is intended for computing purposes. E.g. if the prescription says take 'Tylenol 500mg 2 tablets twice a week for 1 month'. The order frequency in this case is 'twice a week' but a computer doesn't know how to quantify it, therefore the admin would have to set the frequencyPerDay field for this order frequency which is 2/7, that way in the system we can compute the total tablets the patient needs for the entire month which would be frequencyPerDay multiplied by duration in days i.e 2/7*30 which gives us 8.

OrderType

Order types are used to categorize and sub categorize orders.  For example, order types allow us to distinguish between drug orders and test orders as well as between a serology lab test and a microbiology lab test.  In the 1.10 API, the basic types of orders (drug vs. test) are defined by Java classes.  A single Java class may be used for multiple subcategories of orders (e.g., a single class may use metadata to serve the needs of various types of lab orders).  The API will constrain an Order object to be of the Java type associated to its order type, including its subclasses.

OrderNumberGenerator Interface

The interface implemented by the order number generator.  Each order gets a unique order number (in HL7, this is the "placer number") that can be shared with external systems, used to track the order through its clinical workflow, and referenced by external systems that need to communicate information about an order with OpenMRS.  Using an interface allows implementations to define their own order numbering scheme.  We will see later how you can author and register your own custom order number generators.

OrderContext

Contains contextual information like the OrderType, CareSetting and any other custom attributes that are passed to the service layer when placing a new Order.  Using an order context allows a client to create multiple orders without having to set contextual information on each order.  Custom attributes can be used to, for example, pass information through to a custom order number generator. For an example of the OrderContext, see Advanced Example Usage Of OrderContext And OrderNumberGenerator.

DosingType

Defines the dosing structure for a given drug order, note that in OpenMRS platform 1.10 dosingType was changed from String to Class<? extends DosingInstructions>.  As of 1.10, the platform comes with two implementations of the DosingInstructions interface.

  • SimpleDosingInstructions – simple, structured dosing, where all parts of the dosing instructions are codified
  • FreeTextDosingInstructions – free text dosing, where the instructions can be anything

In the future, additional dosing types may be introduced for additional structured dosing options (e.g., variable dosing, tapered dosing, IV Fluid dosing, etc.)

DosingInstructions

Encapsulates dosing information – i.e., the dosing type and the associated field(s) needed for that dosing type to describe how the medication should be administered.  Conceptually, when a dosing type is set for a given drug order, certain dosing related fields become required while other may become irrelevant. The DosingInstructions object helps to abstract away from the developer the burden of knowing which dosing information fields are required for a given a dosing type, because once they create the DosingInstructions object and call setDosingInstructions(DrugOrder) method on it, the relevant fields on the order are set accordingly based on the dosing type. Implementations of DosingInstructions are also responsible for providing validation logic for dosing related fields and setting the auto expire date where necessary. The API comes with two implementations of the DosingInstructions interface: SimpleDosingInstructions and FreeTextDosingInstructions. SimpleDosingInstructions is implemented in such a way that it will calculate and set the autoExpireDate property if null based on the startDate and the duration, if duration is specified as a recurring interval e.g 3 times, then the frequency will be used to determine the actual duration when computing the autoExpireDate.

Action

An enumeration of possible actions that can be taken on an orderable. "NEW" is the default value for any newly placed order, other actions include "REVISE" to revise an existing order, and "DISCONTINUE" to discontinue an order.  Additional actions we anticipate adding would include "RENEW" to re-issue an existing order, "CONTINUE" to continue/extend an order without re-issuing it, and "SUPPLEMENT" to supplement an existing order.  We do not plan to implement a "HOLD" action, since, while occasionally used in various clinical settings, HOLD orders are inherently dangerous and a source of medical errors, where discontinuing and restarting a medicine is much safer for the patient. In theory, a developer should rarely have to set the action field on an order because it can be automatically set if the API is used in the recommended way as we will see later in the code examples.

Urgency

Describes the urgency and/or timing that the order should be carried out.  This is represented as an enumeration with the values: "STAT" to drop everything and do immediately, "ROUTINE", and "ON_SCHEDULED_DATE" for a certain date.  Additional urgencies we will introduce later could include BEFORE_DATE, AFTER_DATE, BEFORE_DATETIME, ON_DATETIME, AFTER_DATETIME.

Order Number

A unique identifier every order is required to have.  In HL7, this is called a "placer number."  It is the responsibility of the API to ensure that every order is assigned a number that can be used when referencing the order from external systems.

Previous Order

When a new order is created from an existing order (e.g., a discontinuation or change in dose), this property links back to the order that was cloned; therefore, it is the order that proceeds a Discontinuation or Revised order. Note that once an order has been placed, it cannot be revised as the same order, so any edits to the instructions/dosing or an order to discontinue an existing order gets a new order number.

Active Order

It is an order that meets the criteria below as of a specified date which we will refer to asOfDate, if none is specified, it defaults to current date:

    • Not Voided
    • Not a Discontinuation Order – i.e one where its action field is not set to DISCONTINUE
    • Started – the startDate is before or equal to the asOfDate
    • Not stopped – treating null as "the end of time", the first of dateStopped and autoExpireDate occurs after asOfDate

Orderable

Currently the only domain objects that are considered as orderables are all drugs and any concepts with a concept classes that is mapped to an order type via the order_type_class_map database table.  When searching for orders, users should only see items that are orderable.

Things To Note

Orders Are Immutable

Making changes to an existing Order object by directly manipulating its field values is not allowed otherwise the API will spit out the order. Instead, you need to revise the existing order, for an example see the Code Examples section below

Order types are inferred

A developer should never have to set the orderType field on an order, the API is capable of inferring order types based on the order object's java type(true for only DrugOrders and TestOrders) or the concept class of the ordered concept, this is possible because of the entries in the order_type_class_map table. It is required for the system admin to set these mappings.

Concept are inferred for drug orders

When placing DrugOrders, there is no need to set the concept field because it will be inferred from the ordered drug by the API.

Order actions are inferred

As we mentioned earlier, developers will rarely have to set the order action field.  Instead, they should use the convenience methods on Order class and its subclasses when managing orders – i.e Order.cloneForDiscontinuing()) and Order.cloneForRevision(). These methods automatically set the appropriate action on the returned order.

Real-time vs Retrospective API

The API introduced in Platform 1.10 is primarily a real-time API that can interoperate with external systems, while still preserving patient safety. This means that as soon as an order is signed and activated, the API assumes that it may be communicated to external systems (e.g. for dispensing or med administration), and acted upon outside of OpenMRS's control or knowledge. A consequence of this is that the real-time API does not allow you to edit an order after it has been signed. (The API can't guarantee no external system has carried out that order, e.g. given the patient a medication, and editing the OpenMRS order record might lead to giving the patient an overdose of a drug.)

In Platform 1.12 we introduce a retrospective API that allows you to make changes required in a retrospective data entry workflow, but not allowed by the real-time API. See the OrderService.saveRetrospectiveOrder method. The retrospective API bypasses assumptions made for patient safety in the real-time API, so if you use it, you must be aware that OpenMRS cannot guarantee that any order you are changing has not already been fulfilled or acted on by an external system.

Code Examples

Retrieve A Patient's Active Orders

List<Order> activeOrders = Context.getOrderService().getActiveOrders(patient, null, null, null);

The code snippet above will fetch all active orders for the patient as of the current date, the getActiveOrders(..) method provides extra arguments to allow you to filter on OrderType, CareSetting, or specify an asOfDate instead of today.

Place A Plain Order

Order order = new Order();
order.setPatient(patient);
order.setConcept(concept);
order.setCareSetting(outpatient);
order.setOrderer(provider);
order.setEncounter(encounter);
//It is optional to set the startDate, the API will set it to the current date by default
order.setStartDate(new Date());

order = Context.getOrderService().saveOrder(order, orderContext);

 

Place A DrugOrder

// Paracetamol 500mg, q6h, for 7 days
DrugOrder order = new DrugOrder();
order.setEncounter(encounter);
order.setPatient(patient);
order.setCareSetting(outpatient);
order.setOrderer(provider);
order.setDrug(paracetamol500mgTabs);
order.setDosingType(SimpleDosingInstructions.class);
order.setDose(2);
order.setDoseUnits(tablets);
order.setFrequency(threeTimesADay);
order.setDuration(7);
order.setDurationUnits(days);
order.setQuantity(42.0);
order.setQuantityUnits(tablets);
order.setRoute(oral);
order.setNumRefills(3);

Context.getOrderService().saveOrder(order, null);
We don't set the concept field because the API will infer it from the drug. When building custom user interfaces, the possible choices for dose units, quantity units, duration units, routes and frequencies should be restricted to those returned by OrderService.getDrugDosingUnits(), OrderService.getDrugDispensingUnits(), OrderService.getDurationUnits()OrderService.getDrugRoutes() and OrderService.getOrderFrequencies(false) respectively.

Place A TestOrder

TestOrder order = new TestOrder();
order.setPatient(patient);
order.setConcept(cd4Count);
order.setOrderer(provider);
order.setCareSetting(outpatient);
order.setEncounter(encounter);

//The fields below are optional, you only set them where they apply
order.setFrequency(everyThreeMonths);
order.setSpecimenSource(blood);
order.setNumberOfRepeats(3);
order.setClinicalHistory("Patient tends to have a higher cd4 count when taking certain medications");

Context.getOrderService().saveOrder(order, null);

When building custom user interfaces, the possible choices for specimen source concept should be restricted to concepts returned by OrderService.getTestSpecimenSources()

Revise An Order

OrderService  orderService = Context.getOrderService();
Order originalOrder = orderService.getOrder(111);
Order revisedOrder = originalOrder.cloneForRevision();
revisedOrder.setInstructions("Take after a meal");
revisedOrder.setOrderer(provider);
revisedOrder.setEncounter(encounter);

orderService.saveOrder(revisedOrder, null);

Discontinue An Order

By calling OrderService.discontinueOrder
OrderService  orderService = Context.getOrderService();
Order orderToDiscontinue = orderService.getOrder(1);
Order discontinueOrder = orderService.discontinueOrder(orderToDiscontinue, "Test if I can discontinue this", null, provider, encounter);

Alternatively, you can call the method that takes in a concept for the discontinue reason as shown below

 

OrderService  orderService = Context.getOrderService();
Order orderToDiscontinue = orderService.getOrder(1);
Order discontinueOrder = orderService.discontinueOrder(orderToDiscontinue, discontinueReasonConcept, null, provider, encounter);
By Saving a Discontinuation Order
OrderService  orderService = Context.getOrderService();
Order orderToDiscontinue = orderService.getOrder(1);
Order discontinuationOrder = orderToDiscontinue.cloneForDiscontinuing();
discontinuationOrder.setOrderer(orderer);
discontinuationOrder.setEncounter(encounter);

orderService.saveOrder(discontinuationOrder, null);

Custom Order Number Generators

As mentioned earlier, you can write and register custom order number generators as spring beans, the class has to implement the OrderNumberGenerator interface as shown below

@Component("timestampOrderNumberGenerator")
public class TimestampOrderNumberGenerator implements OrderNumberGenerator {
    
    @Override
    public String getNewOrderNumber(OrderContext orderContext) {
        return new Long(System.currentTimeMillis()).toString();
    }
}

This is not enough, this generator needs to be configured by the user as the preferred via the order.orderNumberGeneratorBeanId Global Property, by setting the global property value to timestampOrderNumberGenerator which is the bean Id of the generator.

For module developers using XML based spring configuration, you can register the spring bean in the moduleApplicationContext.xml file with the appropriate bean id.

 

For a more advance example where you can support users to enter pre-generated order numbers and usage of the OrderContext see Advanced Example Usage Of OrderContext And OrderNumberGenerator

Web Services REST API

Retrieve A Patient's Active Orders

GET .../CONTEXT-PATH/ws/rest/v1/order?patient=PATIENT-UUID

In addition to the standard OpenMRS REST API request parameters, for filtering purposes you can specify the optional request parameters below :

t : Specifies the order type, for drug or test orders the value would be drugorder or testorder respectively.

caresetting : The uuid of the care setting of the orders to match

asOfDate : Only orders active as of the specified date would be returned

status : Allowed values are restricted to the enum values INACTIVE (only return voided orders) and ANY (return all). Note that this has a different behavior from the standard includeAll parameter that is used to include or exclude voided orders.

Retrieve Orderable Concepts

This is only supported in REST Web Services module versions 12.19.0 and above, make a GET request for the orderables as shown below:

GET .../CONTEXT-PATH/ws/rest/v1/orderable

For more details see the orderable resource in the REST API documentation in the legacy UI by navigating to System Administration -> Advanced Administration, select API Documentation from the REST Web Services section.

Place A Plain Order

POST .../CONTEXT-PATH/ws/rest/v1/order
 
{
  "type" : "order",
  "patient" : "PATIENT-UUID",
  "concept" : "CONCEPT-UUID", //MUST be from the list of available orderables, see above for how to fetch orderables
  "careSetting" : "CARESETTING-UUID",
  "encounter" : "ENCOUNTER-UUID"
}

For a full list of the properties you can specify for a plain order, see the order resource in the REST API documentation in the legacy UI by navigating to System Administration -> Advanced Administration, select API Documentation from the REST Web Services section.

Place A DrugOrder

POST .../CONTEXT-PATH/ws/rest/v1/order
 
{
  "type" : "drugorder",
  "patient" : "PATIENT-UUID",
  "concept" : "CONCEPT-UUID", //This is optional if a drug is specified, MUST be from the list of orderables, see above for how to fetch orderables
  "careSetting" : "CARESETTING-UUID",
  "encounter" : "ENCOUNTER-UUID",
  "frequency" : "FREQUENCY-UNITS-UUID",
  "drug" : "DRUG-UUID", //This is optional if a concept is specified
  "dosingType" : "org.openmrs.SimpleDosingInstructions",
  "dose" : double,
  "doseUnits" : "DOSING-UINTS-UUID", //This is a concept uuid
  "quantity" : double,
  "quantityUnits" : "QUANTITY-UNITS-UUID", //This is a concept uuid
  "duration" : int,
  "durationUnits" : "DURATIONS-UNITS-UUID", //This is a concept uuid
  "numRefills" : int,
  "route" : "ROUTE-UUID", //This is a concept uuid
  "brandName" : string,
  "dispenseAsWritten" : boolean
}

Note that all properties on a plain order apply to a drug order too.


Place A TestOrder

POST .../CONTEXT-PATH/ws/rest/v1/order
 
{
  "type" : "testorder",
  "patient" : "PATIENT-UUID",
  "concept" : "CONCEPT-UUID", //MUST be from the list of available orderables, see above for how to fetch orderables
  "careSetting" : "CARESETTING-UUID",
  "encounter" : "ENCOUNTER-UUID",
  "frequency" : "FREQUENCY-UNITS-UUID",
  "clinicalHistory" : string,
  "specimenSource" : "SPECIMEN-SOURCE-UUID", //This is a concept uuid
  "numberOfRepeats" : int,
  "laterality": string //Possible values can only be one of these num values LEFT, RIGHT, BILATERAL
}

Note that all properties on a plain order apply to a test order too.


Revise An Order

This is achieved by placing a new order of the appropriate type with the action property set to REVISE (MUST be uppercase), the previousOrder property set to the uuid of the order you wish to revise and of course setting the new values of the other properties you intent to change. Typically, you want to provide a reason for the revision which can be a concept for the orderReason property (uuid of the concept) or plain text for the orderReasonNonCoded property. For example, if we wanted to change the instructions of an existing test order below is what the request and the submitted payload would look like.

POST .../CONTEXT-PATH/ws/rest/v1/order
 
{
  "type" : "testorder",
  "action" : "REVISE",
  "previousOrder" : "UUID-OF-THE-ORDER-TO-REVISE",
  "patient" : "PATIENT-UUID",
  "careSetting" : "CARESETTING-UUID",
  "concept" : "CONCEPT-UUID",
  "encounter" : "ENCOUNTER-UUID",
  "dateActivated": date, //Optional, defaults to now
  "instructions" : "These are the updated instructions",
  "orderReasonNonCoded" : "Changed instructions"
}

Note that the values of the type, patient, careSetting and concept properties of the revised order MUST match those of the order you wish to revise, this implies you always want to copy their values from it. For retrospective data entry where you wish to set the date when the actual revision took place, you can specify the dateActivated property value otherwise it defaults to now.

Discontinue An Order

This is achieved by placing a new order of the appropriate type with the action property set to DISCONTINUE (MUST be uppercase), the previousOrder property set to the uuid of the order you wish to discontinue. Typically you want to provide a reason for the discontinuation which can be a concept for the orderReason property (uuid of the concept) or plain text for the orderReasonNonCoded property. For example, if we wanted to stop an existing test order below is what the request and the submitted payload would look like.

POST .../CONTEXT-PATH/ws/rest/v1/order
 
{
  "type" : "testorder",
  "action" : "DISCONTINUE",
  "previousOrder" : "UUID-OF-THE-ORDER-TO-REVISE",
  "patient" : "PATIENT-UUID",
  "careSetting" : "CARESETTING-UUID",
  "concept" : "CONCEPT-UUID",
  "encounter" : "ENCOUNTER-UUID",,
  "dateActivated": date, //Optional, defaults to now
  "orderReasonNonCoded" : "Patient is allergic"
}

Note that the values of the typepatientcareSetting and concept properties of the discontinuation order MUST match those of the order you wish to discontinue, this implies you always want to copy their values from it. For retrospective data entry where you wish to set the date when the actual discontinuation took place, you can specify the dateActivated property value otherwise it defaults to now.