Skip to content

Commit

Permalink
OEUI-311: Support Order Reason for Lab Orders
Browse files Browse the repository at this point in the history
(fix tests and linting, add docs)
  • Loading branch information
mogoodrich committed Dec 12, 2024
1 parent a8847f0 commit c851c42
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 215 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,15 @@ Then administrator should make the following one time configurations using the a
6. Add global property `orderentryowa.labOrderAutoExpireTimeInDays` with a value of `30` days or any other number
7. Add global propery Lab Orderables Concept Set `orderentryowa.labOrderablesConceptSet`, whose value is the UUID of a concept set of Class LabSet and whose Set Members are other LabSet concept sets or concept of Class Test.

### Additional features

As of version 1.30, the module supports a collecting an "order reason" for Lab Orders. Order reasons are specified at
individual test or panel level via global property `orderentryowa.orderReasonsMap` that supports a pipe-delimited
lists of panel or panel uuids mapping tests to concept sets that provides the reason for ordering. A single set of
tests/panels can also be mapped to the same concept set. For example:

uuid-of-test-a=uuid-of-concept-set-that-contains-potential-reasons-for-test-a|uuid-of-test-b,uuid-of-panel-a=uuid-of-concept-set-that contains potential-reasons-for-test-b-and-panel-a


**NB:** Not having any of the above configurations will result into an error notice. Please check more information [here](https://wiki.openmrs.org/display/projects/Order+Entry+UI+Administrator+Guide)

Expand Down
14 changes: 6 additions & 8 deletions app/js/actions/draftActions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {DELETE_DRAFT_DRUG_ORDER_SUCCESS, SET_LAB_ORDER_REASON, TOGGLE_DRAFT_LAB_ORDER_URGENCY} from './actionTypes';
import { DELETE_DRAFT_DRUG_ORDER_SUCCESS, SET_LAB_ORDER_REASON, TOGGLE_DRAFT_LAB_ORDER_URGENCY } from './actionTypes';
import { DRUG_ORDER } from '../components/orderEntry/orderTypes';
import { selectDrugSuccess } from './drug';
import { setSelectedOrder } from './orderAction';
Expand Down Expand Up @@ -55,10 +55,8 @@ export const discardTestsInDraft = (test = {}) => (dispatch) => {
return dispatch(deleteAllDrugDraftOrders());
};

export const setLabOrderReason = ({order, orderReason}) => {
return {
type: SET_LAB_ORDER_REASON,
order: order,
orderReason: orderReason
}
}
export const setLabOrderReason = ({ order, orderReason }) => ({
type: SET_LAB_ORDER_REASON,
order,
orderReason,
});
20 changes: 8 additions & 12 deletions app/js/actions/fetchOrderReasonsGlobalProperty.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,32 @@ export const fetchOrderReasonsGlobalProperty = () => (dispatch) => {
dispatch(loading('FETCH_ORDER_REASONS_GLOBAL_PROPERTY', true));
return axiosInstance.get(`systemsetting?v=custom:(value)&q=orderentryowa.orderReasonsMap`)
.then((response) => {

const orderReasonsMap = {};
const reasonSetUuids = [];

if (response.data.results.length > 0) {

response.data.results[0].value.split('|').map((element) => {
response.data.results[0].value.split('|').forEach((element) => {
const orderables = element.split('=')[0];
const reasonSetUuid = element.split('=')[1];
orderables.split(',').map((orderable) => {
orderables.split(',').forEach((orderable) => {
orderReasonsMap[orderable] = {
setUuid: reasonSetUuid,
members: [],
}
})
};
});
if (!reasonSetUuids.includes(reasonSetUuid)) {
reasonSetUuids.push(reasonSetUuid);
}
})
});

dispatch(fetchOrderReasonsGlobalPropertySuccess(orderReasonsMap));

reasonSetUuids.map((uuid) => {
reasonSetUuids.forEach((uuid) => {
dispatch(fetchOrderReasons(uuid));
})

});
dispatch(loading('FETCH_ORDER_REASON_GLOBAL_PROPERTY', false));
}
}).
catch((error) => {
}).catch((error) => {
if (!error.response) dispatch(networkError('Network error occurred'));
else dispatch(fetchOrderReasonsGlobalPropertyFailure(error));
dispatch(loading('FETCH_ORDER_REASONS_GLOBAL_PROPERTY', false));
Expand Down
132 changes: 73 additions & 59 deletions app/js/components/Draft.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,28 @@ import { getConceptShortName } from '../utils/helpers';
import fetchOrderReasonsGlobalProperty from "../actions/fetchOrderReasonsGlobalProperty";

export class Draft extends PureComponent {

componentDidMount() {
this.props.dispatch(fetchOrderReasonsGlobalProperty());
}

renderDraftList = () => {
let draftType;
const { draftOrders, handleDraftDiscard, locale, orderReasonsMap } = this.props;
const {
draftOrders,
handleDraftDiscard,
locale,
orderReasonsMap,
} = this.props;
return draftOrders.map((order) => {
const isPanel = !!order.set;
const isOtherOrderType = !!order.type;
const orderReasons = orderReasonsMap && orderReasonsMap[order.uuid] || null;
const orderReasons =
orderReasonsMap && orderReasonsMap[order.uuid] ? orderReasonsMap[order.uuid] : null;

// set default order reason if not set
if (orderReasons && orderReasons.members && orderReasons.members.length > 0 && order.orderReason === undefined) {
this.props.setLabOrderReason({orderReason: orderReasons.members[0].uuid, order: order})
if (orderReasons && orderReasons.members && orderReasons.members.length > 0
&& order.orderReason === undefined) {
this.props.setLabOrderReason({ orderReason: orderReasons.members[0].uuid, order });
}

if (isPanel) {
Expand All @@ -46,60 +52,63 @@ export class Draft extends PureComponent {
);

return (
<span>
<li className="draft-list small-font" key={`draft-order-${order.id}`}>
<span className="order-status">{!order.action ? 'NEW' : order.action}</span>
<span className="draft-name">{ orderName }</span>
<div className="action-btn-wrapper">
<span className="action-btn">
{ order.type !== 'drugorder' ?
<div>
<IconButton
iconClass={iconClass}
iconTitle="Urgency"
dataContext={order}
onClick={this.props.toggleDraftLabOrderUrgency}
icon="&#x25B2;"
id="draft-toggle-btn icon-btn-anchor"
/>
</div>:
<span>
<li className="draft-list small-font" key={`draft-order-${order.id}`}>
<span className="order-status">{!order.action ? 'NEW' : order.action}</span>
<span className="draft-name">{ orderName }</span>
<div className="action-btn-wrapper">
<span className="action-btn">
{ order.type !== 'drugorder' ?
<div>
<IconButton
iconClass="icon-pencil"
iconTitle="EDIT"
dataContext={order}
onClick={this.props.editDraftDrugOrder}
id="draft-toggle-btn icon-btn-anchor"
/>
}
</span>
<span className="action-btn right">
iconClass={iconClass}
iconTitle="Urgency"
dataContext={order}
onClick={this.props.toggleDraftLabOrderUrgency}
icon="&#x25B2;"
id="draft-toggle-btn icon-btn-anchor"
/>
</div> :
<IconButton
iconClass="icon-remove"
iconTitle="Discard"
id="draft-discard-btn"
dataContext={{ order, draftType }}
onClick={handleDraftDiscard}
iconClass="icon-pencil"
iconTitle="EDIT"
dataContext={order}
onClick={this.props.editDraftDrugOrder}
id="draft-toggle-btn icon-btn-anchor"
/>
</span>
</div>
}
</span>
<span className="action-btn right">
<IconButton
iconClass="icon-remove"
iconTitle="Discard"
id="draft-discard-btn"
dataContext={{ order, draftType }}
onClick={handleDraftDiscard}
/>
</span>
</div>
</li>

{ order.type !== 'drugorder' && orderReasons && orderReasons.members && orderReasons.members.length > 0 &&
<li key={`draft-order-reason-${order.id}`}>
<FormattedMessage
id="app.orders.reason"
defaultMessage="Order Reason"
description="Reason for order" />
<select
id="orderReason"
name="orderReason"
value={order.orderReason}
onChange={event =>
this.props.setLabOrderReason({ orderReason: event.target.value, order })}>
{orderReasons.members.map(reason => (
<option value={reason.uuid}>{reason.display}</option>
))}
</select>
</li>

{ order.type !== 'drugorder' && orderReasons && orderReasons.members && orderReasons.members.length > 0 &&
<li key={`draft-order-reason-${order.id}`}>
<FormattedMessage
id="app.orders.reason"
defaultMessage="Order Reason"
description="Reason for order" />
<select id="orderReason" name="orderReason" value={order.orderReason}
onChange={(event) => this.props.setLabOrderReason({orderReason: event.target.value, order: order})}>
{orderReasons.members.map((reason) => (
<option value={reason.uuid}>{reason.display}</option>
))}
</select>
</li>
}

</span>
}
</span>
);
});
}
Expand All @@ -117,7 +126,7 @@ export class Draft extends PureComponent {
className="button confirm right modified-btn"
value={addResults}
disabled={isDisabled}
/>)
/>);
}


Expand All @@ -136,7 +145,7 @@ export class Draft extends PureComponent {
className={`button ${showAddResultsButton ? 'right' : ''} cancel modified-btn`}
value={draftOrders.length > 1 ? discardAll : discard}
disabled={isDisabled}
/>)
/>);
}
renderSubmitButton = () => {
const {
Expand All @@ -152,7 +161,7 @@ export class Draft extends PureComponent {
className={`button confirm ${!showAddResultsButton ? 'right' : ''} modified-btn`}
value={save}
disabled={isDisabled}
/>)
/>);
}

render() {
Expand Down Expand Up @@ -186,6 +195,11 @@ export class Draft extends PureComponent {
}

Draft.propTypes = {
dispatch: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
intl: PropTypes.object.isRequired,
locale: PropTypes.string.isRequired,
orderReasonsMap: PropTypes.object.isRequired,
draftOrders: PropTypes.arrayOf(PropTypes.any).isRequired,
editDraftDrugOrder: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
Expand All @@ -197,7 +211,7 @@ Draft.propTypes = {

Draft.defaultProps = {
showAddResultsButton: false,
}
};

const mapStateToProps = state => ({
isLoading: state.createOrderReducer.status.loading,
Expand Down
2 changes: 1 addition & 1 deletion app/js/components/orderEntry/OrderEntryPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ const actionCreators = {
createOrder,
setContext,
setRedirectToAddResults,
setLabOrderReason
setLabOrderReason,
};

const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch);
Expand Down
7 changes: 3 additions & 4 deletions app/js/reducers/draftReducer/draftLabOrderReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,10 @@ export default (state = initialState.draftReducer, action) => {
...state.draftLabOrders,
orders: state.draftLabOrders.orders.map((draftOrder) => {
if (draftOrder.uuid === order.uuid) {
return { ...draftOrder, orderReason: orderReason };
} else {
return draftOrder;
return { ...draftOrder, orderReason };
}
})
return draftOrder;
}),
},
};
}
Expand Down
19 changes: 8 additions & 11 deletions app/js/reducers/orderReasonsReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,16 @@ export default (state = initialState.orderReasons, action) => {
}

case FETCH_ORDER_REASONS_SUCCESS: {

Object.keys(state.orderReasonsMap).forEach(
(orderReason) => {
if (state.orderReasonsMap[orderReason].setUuid === action.payload.data.uuid) {
state.orderReasonsMap[orderReason].members = action.payload.data.setMembers;
const newOrderReasonsMap = state.orderReasonsMap;
Object.keys(newOrderReasonsMap).forEach((orderReason) => {
if (newOrderReasonsMap[orderReason].setUuid === action.payload.data.uuid) {
newOrderReasonsMap.members = action.payload.data.setMembers;
}
}
)
});

return {
...state
...state,
orderReasonsMap: newOrderReasonsMap,
};
}

Expand All @@ -53,16 +52,14 @@ export default (state = initialState.orderReasons, action) => {
};
}

case FETCH_ORDER_REASONS_FAILURE : {
case FETCH_ORDER_REASONS_FAILURE: {
return {
...state,
errorMessage: action.payload,
error: true,
loading: false,
};
}


default:
return state;
}
Expand Down
1 change: 1 addition & 0 deletions tests/components/Draft.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ props = {
handleDraftDiscard: jest.fn(),
toggleDraftLabOrderUrgency: jest.fn(),
editDraftDrugOrder: jest.fn(),
dispatch: jest.fn(),
locale: 'en',
};

Expand Down
Loading

0 comments on commit c851c42

Please sign in to comment.