Skip to content

Commit e750fdc

Browse files
committed
UHM-9160 - Allow viewing and editing reason for not performing test
1 parent 0852472 commit e750fdc

File tree

6 files changed

+109
-62
lines changed

6 files changed

+109
-62
lines changed

api/src/main/java/org/openmrs/module/pihapps/PihAppsService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.openmrs.Concept;
1717
import org.openmrs.Encounter;
1818
import org.openmrs.Location;
19+
import org.openmrs.Obs;
1920
import org.openmrs.Order;
2021
import org.openmrs.api.OpenmrsService;
2122
import org.openmrs.module.pihapps.orders.EncounterFulfillingOrders;
@@ -37,7 +38,9 @@ public interface PihAppsService extends OpenmrsService {
3738

3839
EncounterFulfillingOrders getEncounterFulfillingOrders(String encounterUuid);
3940

40-
Encounter getFulfillerEncouterForOrder(Order order);
41+
Encounter getFulfillerEncounterForOrder(Order order);
42+
43+
Obs getReasonOrderNotFulfilled(Order order);
4144

4245
void markOrdersAsNotFulfilled(List<Order> orders, Concept reason);
4346
}

api/src/main/java/org/openmrs/module/pihapps/PihAppsServiceImpl.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ public EncounterFulfillingOrders getEncounterFulfillingOrders(String encounterUu
331331
@Transactional(readOnly = true)
332332
@Authorized(PrivilegeConstants.GET_ENCOUNTERS)
333333
@SuppressWarnings({ "unchecked" })
334-
public Encounter getFulfillerEncouterForOrder(Order order) {
334+
public Encounter getFulfillerEncounterForOrder(Order order) {
335335
Criteria c = sessionFactory.getHibernateSessionFactory().getCurrentSession().createCriteria(Obs.class);
336336
c.add(eq("voided", false));
337337
c.add(eq("person", order.getPatient()));
@@ -346,16 +346,44 @@ public Encounter getFulfillerEncouterForOrder(Order order) {
346346
return l.get(0).getEncounter();
347347
}
348348

349+
@Override
350+
@Transactional(readOnly = true)
351+
@Authorized(PrivilegeConstants.GET_OBS)
352+
@SuppressWarnings({ "unchecked" })
353+
public Obs getReasonOrderNotFulfilled(Order order) {
354+
Criteria c = sessionFactory.getHibernateSessionFactory().getCurrentSession().createCriteria(Obs.class);
355+
c.add(eq("voided", false));
356+
c.add(eq("person", order.getPatient()));
357+
c.add(eq("concept", labOrderConfig.getReasonTestNotPerformedQuestion()));
358+
c.add(eq("order", order));
359+
c.addOrder(org.hibernate.criterion.Order.desc("obsDatetime"));
360+
c.setMaxResults(1);
361+
List<Obs> l = c.list();
362+
if (l == null || l.isEmpty()) {
363+
return null;
364+
}
365+
return l.get(0);
366+
}
367+
349368
@Override
350369
@Transactional
351370
@Authorized(PrivilegeConstants.EDIT_ORDERS)
352371
public void markOrdersAsNotFulfilled(List<Order> orders, Concept reason) {
353372
for (Order order : orders) {
354373
orderService.updateOrderFulfillerStatus(order, Order.FulfillerStatus.EXCEPTION, null);
374+
Obs existingValue = getReasonOrderNotFulfilled(order);
355375
if (reason != null) {
356376
if (labOrderConfig.getReasonTestNotPerformedQuestion() == null) {
357377
throw new IllegalArgumentException("Reason test not performed question is not configured");
358378
}
379+
if (existingValue != null) {
380+
if (reason.equals(existingValue.getValueCoded())) {
381+
continue;
382+
}
383+
else {
384+
obsService.voidObs(existingValue, "Updated by pihAppsService.markOrdersAsNotFulfilled");
385+
}
386+
}
359387
Obs obs = new Obs();
360388
obs.setPerson(order.getPatient());
361389
obs.setObsDatetime(new Date());
@@ -364,8 +392,16 @@ public void markOrdersAsNotFulfilled(List<Order> orders, Concept reason) {
364392
obs.setValueCoded(reason);
365393
obs.setAccessionNumber(order.getAccessionNumber());
366394
obs.setComment("result-entry-form^did-not-perform-dropdown"); // This is here for backwards-compatibility with the labworkflow owa
395+
if (existingValue != null) {
396+
obs.setPreviousVersion(existingValue);
397+
obs.setEncounter(existingValue.getEncounter());
398+
}
399+
367400
obsService.saveObs(obs, "");
368401
}
402+
else {
403+
obsService.voidObs(existingValue, "Voided by pihAppsService.markOrdersAsNotFulfilled");
404+
}
369405
}
370406
}
371407
}

omod/src/main/java/org/openmrs/module/pihapps/rest/OrderWithFulfillerEncounterResource.java renamed to omod/src/main/java/org/openmrs/module/pihapps/rest/OrderWithFulfillerDetailsResource.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.openmrs.module.pihapps.rest;
22

33
import org.openmrs.Encounter;
4+
import org.openmrs.Obs;
45
import org.openmrs.Order;
56
import org.openmrs.api.context.Context;
67
import org.openmrs.module.pihapps.PihAppsService;
@@ -10,11 +11,15 @@
1011
import org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs2_2.OrderResource2_2;
1112

1213
@Resource(name = RestConstants.VERSION_1 + "/order", supportedClass = Order.class, supportedOpenmrsVersions = "*", order = 0)
13-
public class OrderWithFulfillerEncounterResource extends OrderResource2_2 {
14+
public class OrderWithFulfillerDetailsResource extends OrderResource2_2 {
1415

1516
@PropertyGetter("fulfillerEncounter")
1617
public Encounter getFulfillerEncounter(Order order) {
17-
return Context.getService(PihAppsService.class).getFulfillerEncouterForOrder(order);
18+
return Context.getService(PihAppsService.class).getFulfillerEncounterForOrder(order);
1819
}
1920

21+
@PropertyGetter("reasonOrderNotFulfilled")
22+
public Obs getReasonOrderNotFulfilled(Order order) {
23+
return Context.getService(PihAppsService.class).getReasonOrderNotFulfilled(order);
24+
}
2025
}

omod/src/main/webapp/fragments/labs/recordOrderNotFulfilled.gsp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
<script type="text/javascript">
1717
function initializeOrderNotFulfilledForm(formConfig) {
1818
19-
const patientUuid = formConfig.patientUuid;
2019
const orders = formConfig.orders;
20+
const reason = formConfig.reason;
2121
const pihAppsConfig = formConfig.pihAppsConfig;
2222
const onSuccessFunction = formConfig.onSuccessFunction;
2323
@@ -41,6 +41,7 @@
4141
// Populate default values each time form is opened
4242
parentElement.find(".errors-section").html("");
4343
parentElement.find(":input").val("");
44+
reasonPicker.val(reason?.valueCoded?.uuid ?? '')
4445
4546
const formElement = parentElement.find("form");
4647
formElement.off("submit");
@@ -71,9 +72,10 @@
7172
dataType: "json",
7273
success: () => {
7374
onSuccessFunction();
75+
parentElement.find(".action-button").removeAttr("disabled");
7476
},
7577
error: (xhr) => {
76-
jq(".action-button").removeAttr("disabled");
78+
parentElement.find(".action-button").removeAttr("disabled");
7779
const error = xhr?.responseJSON?.error;
7880
const message = error?.translatedMessage ?? error.message ?? error;
7981
errorsSection.html(message);

omod/src/main/webapp/pages/labs/labOrderList.gsp

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
2222
const patientRep = "(uuid,display,person:(display),identifiers:(identifier,preferred,identifierType:(uuid,display,auditInfo:(dateCreated))))";
2323
const conceptRep = "(id,uuid,allowDecimal,display,names:(id,uuid,name,locale,localePreferred,voided,conceptNameType))";
24+
const orderRep = "id,uuid,display,orderNumber,dateActivated,scheduledDate,dateStopped,autoExpireDate,fulfillerStatus,orderType:(id,uuid,display,name),encounter:(id,uuid,display,encounterDatetime),careSetting:(uuid,name,careSettingType,display),accessionNumber,urgency,action,patient:(uuid,display,person:(display),identifiers:(identifier,preferred,identifierType:(uuid,display,auditInfo:(dateCreated)))),concept:" + conceptRep
25+
2426
const labOrderConfigRep = "(labTestOrderType:(uuid),availableLabTestsByCategory:(category:" + conceptRep + ",labTests:" + conceptRep + "),orderStatusOptions:(status,display),fulfillerStatusOptions:(status,display),orderFulfillmentStatusOptions:(status,display),testLocationQuestion:(uuid,answers:(uuid,display)),specimenCollectionEncounterType:(uuid),specimenCollectionEncounterRole:(uuid),estimatedCollectionDateQuestion:(uuid),estimatedCollectionDateAnswer:(uuid),testOrderNumberQuestion:(uuid),labIdentifierConcept:(uuid),specimenReceivedDateQuestion:(uuid),reasonTestNotPerformedQuestion:(uuid,answers:(uuid,display)))";
2527
const pihAppsConfigRep = "dateFormat,dateTimeFormat,primaryIdentifierType:(uuid),labOrderConfig:" + labOrderConfigRep;
2628
@@ -32,30 +34,52 @@
3234
3335
const viewSpecimenEncounter = function(encounterUuid) {
3436
const encounterRep = "id,uuid,patient:" + patientRep + ",encounterDatetime,encounterType:(uuid),location:(uuid,display),encounterProviders:(provider:(uuid,display),encounterRole:(uuid,display)),obs:(uuid,concept:(uuid,datatype:(name)),value,comment,formNamespaceAndPath)";
35-
const orderRep = "id,uuid,display,orderNumber,dateActivated,scheduledDate,dateStopped,autoExpireDate,fulfillerStatus,orderType:(id,uuid,display,name),encounter:(id,uuid,display,encounterDatetime),careSetting:(uuid,name,careSettingType,display),accessionNumber,urgency,action,patient:(uuid,display,person:(display),identifiers:(identifier,preferred,identifierType:(uuid,display,auditInfo:(dateCreated)))),concept:" + conceptRep
3637
const rep = "encounter:(" + encounterRep + "),orders:(" + orderRep + ")";
3738
jq.get(openmrsContextPath + "/ws/rest/v1/pihapps/config?v=custom:(" + pihAppsConfigRep + ")", function(pihAppsConfig) {
3839
jq.get(openmrsContextPath + "/ws/rest/v1/encounterFulfillingOrders/" + encounterUuid + "?v=custom:(" + rep + ")", function (encAndOrders) {
39-
jq("#view-orders-section").hide();
40-
jq("#specimen-edit-emr-id").html(patientUtils.getPreferredIdentifier(encAndOrders.encounter.patient, pihAppsConfig.primaryIdentifierType?.uuid ?? ''));
41-
jq("#specimen-edit-patient-name").html(encAndOrders.encounter.patient.person.display);
40+
jq(".specimen-edit-emr-id").html(patientUtils.getPreferredIdentifier(encAndOrders.encounter.patient, pihAppsConfig.primaryIdentifierType?.uuid ?? ''));
41+
jq(".specimen-edit-patient-name").html(encAndOrders.encounter.patient.person.display);
4242
initializeSpecimenCollectionForm({
4343
patientUuid: encAndOrders.encounter.patient.uuid,
4444
orders: encAndOrders.orders,
4545
encounter: encAndOrders.encounter,
4646
pihAppsConfig: pihAppsConfig,
4747
onSuccessFunction: () => { closeEncounterEdit(); pagingDataTable.updateTable(); }
4848
});
49+
jq("#view-orders-section").hide();
4950
jq("#edit-specimen-encounter-section").show();
5051
});
5152
});
5253
};
5354
55+
const viewOrderNotPerformed = function(orderUuid) {
56+
const rep = orderRep + ",reasonOrderNotFulfilled:(uuid,concept:" + conceptRep + ",valueCoded:" + conceptRep + ")";
57+
jq.get(openmrsContextPath + "/ws/rest/v1/pihapps/config?v=custom:(" + pihAppsConfigRep + ")", function(pihAppsConfig) {
58+
jq.get(openmrsContextPath + "/ws/rest/v1/order/" + orderUuid + "?v=custom:(" + rep + ")", function (order) {
59+
jq(".specimen-edit-emr-id").html(patientUtils.getPreferredIdentifier(order.patient, pihAppsConfig.primaryIdentifierType?.uuid ?? ''));
60+
jq(".specimen-edit-patient-name").html(order.patient.person.display);
61+
initializeOrderNotFulfilledForm({
62+
orders: [order],
63+
reason: order.reasonOrderNotFulfilled,
64+
pihAppsConfig: pihAppsConfig,
65+
onSuccessFunction: () => { closeReasonNotPerformed(); pagingDataTable.updateTable(); }
66+
});
67+
jq("#view-orders-section").hide();
68+
jq("#edit-reason-not-performed-section").show();
69+
});
70+
});
71+
}
72+
5473
const closeEncounterEdit = function() {
5574
jq("#edit-specimen-encounter-section").hide();
5675
jq("#view-orders-section").show();
5776
}
5877
78+
const closeReasonNotPerformed = function() {
79+
jq("#edit-reason-not-performed-section").hide();
80+
jq("#view-orders-section").show();
81+
}
82+
5983
jq(document).ready(function() {
6084
6185
jq.get(openmrsContextPath + "/ws/rest/v1/pihapps/config?v=custom:(" + pihAppsConfigRep + ")", function(pihAppsConfig) {
@@ -74,7 +98,7 @@
7498
const getAccessionNumber = (order) => { return order.accessionNumber; }
7599
const getOrderStatus = (order) => { return patientUtils.getOrderStatusOption(order, orderStatusOptions).display; };
76100
const getFulfillerStatus = (order) => { return patientUtils.getFulfillerStatusOption(order, fulfillerStatusOptions).display; };
77-
const getOrderFulfillmentStatus = (order) => { return patientUtils.getOrderFulfillmentStatusOption(order, orderFulfillmentStatusOptions).display; };
101+
78102
const getLabTest = function(order) {
79103
const urgency = order.urgency === 'STAT' ? '<i class="fas fa-fw fa-exclamation" style="color: red;"></i>' : '';
80104
return urgency + conceptUtils.getConceptShortName(order.concept, window.sessionContext?.locale);
@@ -88,6 +112,14 @@
88112
return "<a href=\"javascript:viewSpecimenEncounter('" + fulfillerEncounter.uuid + "')\">" + specimenDate + "</a>";
89113
};
90114
115+
const getOrderFulfillmentStatus = (order) => {
116+
const statusDisplay = patientUtils.getOrderFulfillmentStatusOption(order, orderFulfillmentStatusOptions).display;
117+
if (order.fulfillerStatus === 'EXCEPTION') {
118+
return "<a href=\"javascript:viewOrderNotPerformed('" + order.uuid + "')\">" + statusDisplay + "</a>";
119+
}
120+
return statusDisplay;
121+
};
122+
91123
const getFilterParameterValues = function() {
92124
return {
93125
"orderType": pihAppsConfig.labOrderConfig.labTestOrderType?.uuid,
@@ -149,6 +181,11 @@
149181
});
150182
</script>
151183
184+
<style>
185+
#edit-specimen-encounter-section { display: none; }
186+
#edit-reason-not-performed-section { display: none; }
187+
</style>
188+
152189
<div id="view-orders-section">
153190
<div class="row justify-content-between">
154191
<div class="col-6">
@@ -249,10 +286,23 @@
249286
<div class="col-6">
250287
<h3>
251288
${ ui.message("pihapps.specimenCollectionDetails") } -
252-
<span id="specimen-edit-patient-name"></span>
253-
(<span id="specimen-edit-emr-id"></span>)
289+
<span class="specimen-edit-patient-name"></span>
290+
(<span class="specimen-edit-emr-id"></span>)
254291
</h3>
255292
</div>
256293
</div>
257294
${ ui.includeFragment("pihapps", "labs/specimenCollectionEncounter", ["id": "specimen-encounter-section"])}
295+
</div>
296+
297+
<div id="edit-reason-not-performed-section">
298+
<div class="row justify-content-between">
299+
<div class="col-6">
300+
<h3>
301+
${ ui.message("pihapps.removeSelectedOrders") } -
302+
<span class="specimen-edit-patient-name"></span>
303+
(<span class="specimen-edit-emr-id"></span>)
304+
</h3>
305+
</div>
306+
</div>
307+
${ ui.includeFragment("pihapps", "labs/recordOrderNotFulfilled", ["id": "reason-not-performed-section"])}
258308
</div>

omod/src/main/webapp/pages/labs/labPatientReception.gsp

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ ${ ui.includeFragment("coreapps", "patientHeader", [ patient: patient.patient ])
9696
if (selectedOrders.length > 0) {
9797
jq("#view-orders-section").hide();
9898
initializeOrderNotFulfilledForm({
99-
patientUuid: patientUuid,
10099
orders: selectedOrders,
101100
pihAppsConfig: pihAppsConfig,
102101
onSuccessFunction: () => {
@@ -143,57 +142,9 @@ ${ ui.includeFragment("coreapps", "patientHeader", [ patient: patient.patient ])
143142
</script>
144143
145144
<style>
146-
.col {
147-
white-space: nowrap;
148-
}
149-
.info-and-paging-row {
150-
padding-top: 5px;
151-
}
152-
.paging-navigation {
153-
padding-left: 10px;
154-
cursor: pointer;
155-
}
156-
.dataTables_wrapper {
157-
min-height: unset;
158-
}
159145
#order-actions-section {
160146
padding-top: 20px;
161147
}
162-
.form-field-label {
163-
line-height: 3;
164-
}
165-
.form-field-widgets {
166-
padding-left: 20px;
167-
}
168-
.form-field-widgets label {
169-
display: none;
170-
}
171-
.form-field-widgets p {
172-
display: inline;
173-
}
174-
.dialog {
175-
width: 80%;
176-
height: 90%;
177-
}
178-
.dialog select option {
179-
font-size: 1.0em;
180-
}
181-
form input, form select, form textarea, .form input, .form select, .form textarea {
182-
min-width: auto;
183-
width: fit-content;
184-
}
185-
.table-header div {
186-
font-weight: bold;
187-
}
188-
.form-header {
189-
padding-top: 10px;
190-
font-weight: bold;
191-
font-size: large;
192-
}
193-
.errors-section {
194-
font-weight: bold;
195-
color: red;
196-
}
197148
#process-orders-section {
198149
display:none;
199150
}

0 commit comments

Comments
 (0)