Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OEUI 311 #273

Merged
merged 7 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.3.0, the module supports a collecting an "order reason" for Lab Orders. Order reasons are specified at
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm planning on doing release before merging this in, and this is my target next version, will update if that changes

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
7 changes: 7 additions & 0 deletions app/js/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,10 @@ export const DISCONTINUE_ACTIVE_DRUG_ORDER = 'DISCONTINUE_ACTIVE_DRUG_ORDER';
export const DISCONTINUE_ORDER = 'DISCONTINUE_ORDER';
export const DISCONTINUE_ORDER_SUCCEDED = 'DISCONTINUE_ORDER_SUCCEDED';
export const SET_REDIRECT_TO_ADD_RESULTS = "SET_REDIRECT_TO_ADD_RESULTS";
export const FETCH_ORDER_REASONS_GLOBAL_PROPERTY_FAILURE = 'FETCH_ORDER_REASONS_GLOBAL_PROPERTY_FAILURE';
export const FETCH_ORDER_REASONS_GLOBAL_PROPERTY_SUCCESS = 'FETCH_ORDER_REASONS_GLOBAL_PROPERTY_SUCCESS';
export const FETCH_ORDER_REASONS_GLOBAL_PROPERTY_LOADING = 'FETCH_ORDER_REASONS_GLOBAL_PROPERTY_LOADING';
export const FETCH_ORDER_REASONS_FAILURE = 'FETCH_ORDER_REASONS_FAILURE';
export const FETCH_ORDER_REASONS_SUCCESS = 'FETCH_ORDER_REASONS_SUCCESS';
export const FETCH_ORDER_REASONS_LOADING = 'FETCH_ORDER_REASONS_LOADING';
export const SET_LAB_ORDER_REASON = 'SET_LAB_ORDER_REASON';
10 changes: 8 additions & 2 deletions app/js/actions/draftActions.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { DELETE_DRAFT_DRUG_ORDER_SUCCESS, 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';
import {
removeTestFromDraft,
removeTestPanelFromDraft,
deleteDraftLabOrder,
} from '../actions/draftLabOrderAction';
} from './draftLabOrderAction';
import { deleteAllDrugDraftOrders } from './draftTableAction';
import constants from '../utils/constants';

Expand Down Expand Up @@ -54,3 +54,9 @@ export const discardTestsInDraft = (test = {}) => (dispatch) => {
dispatch(deleteDraftLabOrder());
return dispatch(deleteAllDrugDraftOrders());
};

export const setLabOrderReason = ({ order, orderReason }) => ({
type: SET_LAB_ORDER_REASON,
order,
orderReason,
});
9 changes: 9 additions & 0 deletions app/js/actions/fetchOrderReasons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import axiosInstance from '../config';

const fetchOrderReasons = conceptUUID => ({
type: 'FETCH_ORDER_REASONS',

Check warning on line 4 in app/js/actions/fetchOrderReasons.js

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected indentation of 2 spaces but found 4
payload: axiosInstance.get(`/concept/${conceptUUID}`),

Check warning on line 5 in app/js/actions/fetchOrderReasons.js

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected indentation of 2 spaces but found 4

});

export default fetchOrderReasons;
59 changes: 59 additions & 0 deletions app/js/actions/fetchOrderReasonsGlobalProperty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import axiosInstance from '../config';
import loading from "./loading";
import networkError from "./networkError";
import {
FETCH_ORDER_REASONS_GLOBAL_PROPERTY_FAILURE,

Check warning on line 5 in app/js/actions/fetchOrderReasonsGlobalProperty.js

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected indentation of 2 spaces but found 4
FETCH_ORDER_REASONS_GLOBAL_PROPERTY_SUCCESS,

Check warning on line 6 in app/js/actions/fetchOrderReasonsGlobalProperty.js

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected indentation of 2 spaces but found 4
} from "./actionTypes";
import fetchOrderReasons from "./fetchOrderReasons";


export const fetchOrderReasonsGlobalPropertySuccess = value => ({
type: FETCH_ORDER_REASONS_GLOBAL_PROPERTY_SUCCESS,

Check warning on line 12 in app/js/actions/fetchOrderReasonsGlobalProperty.js

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected indentation of 2 spaces but found 4
payload: value,

Check warning on line 13 in app/js/actions/fetchOrderReasonsGlobalProperty.js

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected indentation of 2 spaces but found 4
});

export const fetchOrderReasonsGlobalPropertyFailure = error => ({
type: FETCH_ORDER_REASONS_GLOBAL_PROPERTY_FAILURE,

Check warning on line 17 in app/js/actions/fetchOrderReasonsGlobalProperty.js

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected indentation of 2 spaces but found 4
payload: error,

Check warning on line 18 in app/js/actions/fetchOrderReasonsGlobalProperty.js

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected indentation of 2 spaces but found 4
});


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('|').forEach((element) => {
const orderables = element.split('=')[0];
const reasonSetUuid = element.split('=')[1];
orderables.split(',').forEach((orderable) => {
orderReasonsMap[orderable] = {
setUuid: reasonSetUuid,
members: [],
};
});
if (!reasonSetUuids.includes(reasonSetUuid)) {
reasonSetUuids.push(reasonSetUuid);
}
});

dispatch(fetchOrderReasonsGlobalPropertySuccess(orderReasonsMap));

reasonSetUuids.forEach((uuid) => {
dispatch(fetchOrderReasons(uuid));
});
dispatch(loading('FETCH_ORDER_REASON_GLOBAL_PROPERTY', false));
}
}).catch((error) => {
if (!error.response) dispatch(networkError('Network error occurred'));
else dispatch(fetchOrderReasonsGlobalPropertyFailure(error));
dispatch(loading('FETCH_ORDER_REASONS_GLOBAL_PROPERTY', false));
});
};


export default fetchOrderReasonsGlobalProperty;
123 changes: 86 additions & 37 deletions app/js/components/Draft.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,32 @@ import { injectIntl, FormattedMessage } from 'react-intl';
import constants from '../utils/constants';
import IconButton from './button/IconButton';
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 } = 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] ? 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 });
}

if (isPanel) {
draftType = 'panel';
Expand All @@ -34,40 +52,64 @@ export class Draft extends PureComponent {
);

return (
<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' ?
<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> :
<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">
<IconButton
iconClass={iconClass}
iconTitle="Urgency"
dataContext={order}
onClick={this.props.toggleDraftLabOrderUrgency}
icon="&#x25B2;"
id="draft-toggle-btn icon-btn-anchor"
/> :
<IconButton
iconClass="icon-pencil"
iconTitle="EDIT"
dataContext={order}
onClick={this.props.editDraftDrugOrder}
id="draft-toggle-btn icon-btn-anchor"
iconClass="icon-remove"
iconTitle="Discard"
id="draft-discard-btn"
dataContext={{ order, draftType }}
onClick={handleDraftDiscard}
/>
}
</span>
<span className="action-btn right">
<IconButton
iconClass="icon-remove"
iconTitle="Discard"
id="draft-discard-btn"
dataContext={{ order, draftType }}
onClick={handleDraftDiscard}
/>
</span>
</div>
</li>);
</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>
}
</span>
);
});
}

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


Expand All @@ -103,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 @@ -119,7 +161,7 @@ export class Draft extends PureComponent {
className={`button confirm ${!showAddResultsButton ? 'right' : ''} modified-btn`}
value={save}
disabled={isDisabled}
/>)
/>);
}

render() {
Expand Down Expand Up @@ -153,20 +195,27 @@ 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,
handleDraftDiscard: PropTypes.func.isRequired,
setLabOrderReason: PropTypes.func.isRequired,
toggleDraftLabOrderUrgency: PropTypes.func.isRequired,
showAddResultsButton: PropTypes.bool,
};

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

const mapStateToProps = state => ({
isLoading: state.createOrderReducer.status.loading,
orderReasonsMap: state.orderReasonsReducer.orderReasonsMap,
});

export default connect(mapStateToProps)(injectIntl(Draft));
Expand Down
5 changes: 5 additions & 0 deletions app/js/components/orderEntry/OrderEntryPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { loadGlobalProperties, APP_GLOBAL_PROPERTIES } from "../../utils/globalP
import {
editDraftDrugOrder,
toggleDraftLabOrderUrgency,
setLabOrderReason,
discardTestsInDraft,
} from '../../actions/draftActions';
import imageLoader from '../../../img/loading.gif';
Expand Down Expand Up @@ -175,6 +176,7 @@ export class OrderEntryPage extends PureComponent {
careSetting: this.props.inpatientCareSetting.uuid,
encounter: this.props.encounterType.uuid,
orderer: this.props.sessionReducer.currentProvider.uuid,
orderReason: order.orderReason,
patient: this.props.patient.uuid,
type: 'testorder',
urgency: order.urgency || 'ROUTINE',
Expand Down Expand Up @@ -385,6 +387,7 @@ export class OrderEntryPage extends PureComponent {
}
editDraftDrugOrder={this.props.editDraftDrugOrder}
locale={this.props.sessionReducer.locale}
setLabOrderReason={this.props.setLabOrderReason}
showAddResultsButton={this.state.addResultsUrl}
/>
</div>
Expand Down Expand Up @@ -492,6 +495,7 @@ OrderEntryPage.propTypes = {
draftLabOrders: PropTypes.object.isRequired,
draftDrugOrders: PropTypes.arrayOf(PropTypes.any).isRequired,
toggleDraftLabOrderUrgency: PropTypes.func.isRequired,
setLabOrderReason: PropTypes.func.isRequired,
discardTestsInDraft: PropTypes.func.isRequired,
createOrder: PropTypes.func.isRequired,
setContext: PropTypes.func.isRequired,
Expand Down Expand Up @@ -577,6 +581,7 @@ const actionCreators = {
createOrder,
setContext,
setRedirectToAddResults,
setLabOrderReason,
};

const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch);
Expand Down
18 changes: 18 additions & 0 deletions app/js/reducers/draftReducer/draftLabOrderReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DELETE_TEST_FROM_DRAFT_LAB_ORDER,
DELETE_PANEL_FROM_DRAFT_LAB_ORDER,
DELETE_ALL_ITEMS_IN_DRAFT_LAB_ORDER,
SET_LAB_ORDER_REASON,
} from '../../actions/actionTypes';
import initialState from '../initialState';

Expand Down Expand Up @@ -121,6 +122,23 @@ export default (state = initialState.draftReducer, action) => {
};
}

case SET_LAB_ORDER_REASON: {
const { order, orderReason } = action;
return {
...state,
draftLabOrders: {
...state.draftLabOrders,
orders: state.draftLabOrders.orders.map((draftOrder) => {
if (draftOrder.uuid === order.uuid) {
return { ...draftOrder, orderReason };
}
return draftOrder;
}),
},
};
}


case DELETE_PANEL_FROM_DRAFT_LAB_ORDER: {
const panel = action.orders;
const panelTests = action.orders.setMembers;
Expand Down
2 changes: 2 additions & 0 deletions app/js/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import orderSelectionReducer from './orderSelectionReducer';
import draftReducer from './draftReducer';
import contextReducer from "./contextReducer";
import addResultsReducer from "./addResultsReducer";
import orderReasonsReducer from "./orderReasonsReducer";

export default combineReducers({
openmrs: reducers,
Expand Down Expand Up @@ -59,4 +60,5 @@ export default combineReducers({
orderSelectionReducer,
contextReducer,
addResultsReducer,
orderReasonsReducer,
});
Loading
Loading