4 * List procedure orders and reports, and fetch new reports and their results.
7 * @link http://www.open-emr.org
8 * @author Rod Roark <rod@sunsetsystems.com>
9 * @author Brady Miller <brady.g.miller@gmail.com>
10 * @author Tyler Wrenn <tyler@tylerwrenn.com>
11 * @author Jerry Padgett <sjpadgett@gmail.com>
12 * @copyright Copyright (c) 2013-2016 Rod Roark <rod@sunsetsystems.com>
13 * @copyright Copyright (c) 2017-2019 Brady Miller <brady.g.miller@gmail.com>
14 * @copyright Copyright (c) 2020 Tyler Wrenn <tyler@tylerwrenn.com>
15 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
20 require_once("../globals.php");
21 require_once("$srcdir/patient.inc");
22 require_once("$srcdir/options.inc.php");
23 if (file_exists("$include_root/procedure_tools/quest/QuestResultClient.php")) {
24 require_once("$include_root/procedure_tools/quest/QuestResultClient.php");
26 require_once("./receive_hl7_results.inc.php");
27 require_once("./gen_hl7_order.inc.php");
29 $form_patient = !empty($_POST['form_patient']);
30 $processing_lab = $_REQUEST['form_lab_id'] ??
'';
32 if (!isset($_REQUEST['form_refresh']) && !isset($_REQUEST['form_process_labs']) && !isset($_REQUEST['form_manual'])) {
36 use OpenEMR\Common\Acl\AclMain
;
37 use OpenEMR\Core\Header
;
40 * Get a list item title, translating if required.
42 * @param string $listid List identifier.
43 * @param string $value List item identifier.
44 * @return string The item's title.
46 function getListItem($listid, $value)
49 "SELECT title FROM list_options " .
50 "WHERE list_id = ? AND option_id = ? AND activity = 1",
51 array($listid, $value)
53 $tmp = xl_list_label($lrow['title']);
55 $tmp = (($value === '') ?
'' : "($value)");
62 * Adapt text to be suitable as the contents of a table cell.
64 * @param string $s Input text.
65 * @return string Output text.
67 function myCellText($s)
76 // Check authorization.
77 $thisauth = AclMain
::aclCheckCore('patients', 'med');
79 die(xlt('Not authorized'));
84 // Send selected unsent orders if requested. This does not support downloading
85 // very well as it will only send the first of those.
86 if ($_POST['form_xmit']) {
87 foreach ($_POST['form_cb'] as $formid) {
88 $row = sqlQuery("SELECT lab_id FROM procedure_order WHERE procedure_order_id = ?", array($formid));
89 $ppid = (int)$row['lab_id'];
91 $errmsg = gen_hl7_order($formid, $hl7);
93 $errmsg = send_hl7_order($ppid, $hl7);
100 sqlStatement("UPDATE procedure_order SET date_transmitted = NOW() WHERE procedure_order_id = ?", array($formid));
107 <?php Header
::setupHeader(['datetime-picker']); ?
>
108 <title
><?php
echo xlt('Procedure Orders and Reports'); ?
></title
>
114 background
-color
: var(--gray
);
121 background
-color
: var(--gray200
);
131 a
, a
:visited
, a
:hover
{
137 var dlgtitle
= <?php
echo xlj("Match Patient") ?
>;
139 function openResults(orderid
) {
140 top
.restoreSession();
141 // Open results in a new window. The options parameter serves to defeat Firefox's
142 // "open windows in a new tab", which is what we want because the doc may want to
143 // see the results concurrently with other stuff like related patient notes.
144 // Opening in the other frame is not good enough because if they then do related
145 // patients notes it will wipe out this script's list. We need 3 viewports.
146 window
.open('single_order_results.php?orderid=' +
encodeURIComponent(orderid
), '_blank', 'toolbar=0,location=0,menubar=0,scrollbars=yes');
148 // To open results in the same frame:
149 // document.location.href = 'single_order_results.php?orderid=' + orderid;
151 // To open results in the "other" frame:
153 // var othername = (w.name == 'RTop') ? 'RBot' : 'RTop';
154 // w.parent.left_nav.forceDual();
155 // w.parent.left_nav.loadFrame('ore1', othername, 'orders/single_order_results.php?orderid=' + orderid);
158 // Invokes the patient matching dialog.
159 // args is a PHP-serialized array of patient attributes.
160 // The dialog script will directly insert the selected pid value, or 0,
161 // into the value of the form field named "[select][$key]".
163 function openPtMatch(args
) {
164 top
.restoreSession();
165 dlgopen('patient_match_dialog.php?key=' +
encodeURIComponent(args
), '_blank', 850, 400, '', dlgtitle
);
168 function openPatient(pid
) {
169 top
.restoreSession();
170 document
.location
.href
= "../patient_file/summary/demographics.php?set_pid=" +
encodeURIComponent(pid
);
174 $
('.datepicker').datetimepicker({
175 <?php
$datetimepicker_timepicker = false; ?
>
176 <?php
$datetimepicker_showseconds = false; ?
>
177 <?php
$datetimepicker_formatInput = false; ?
>
178 <?php
require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?
>
179 <?php
// can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
181 $
("#wait").addClass('d-none');
185 $
("#wait").removeClass('d-none');
191 <body onsubmit
="doWait(event)">
192 <div
class="page-header ml-2">
193 <h2
><?php
echo xlt('Procedure Orders and Reports'); ?
></h2
>
195 <form
class="form-inline" method
='post' action
='list_reports.php' enctype
='multipart/form-data'>
196 <div
class="container">
197 <!-- This might be set by the results window
: -->
198 <input
class="d-none row" type
='text' name
='form_external_refresh' value
='' />
199 <div
class="form-row">
201 <div
class="form-group">
202 <div
class="input-group-btn input-group-append">
203 <button
class='btn btn-primary' name
='form_process_labs'
204 title
="Click to process pending results from selected Labs."
205 value
="true"><?php
echo xlt('Process Results For'); ?
><i
class="ml-1 btn-transmit"></i
>
207 <select name
='form_lab_id' id
='form_lab_id' class='form-control'>
208 <option value
="0"><?php
echo xlt('All Labs'); ?
></option
>
211 $ppres = sqlStatement("SELECT ppid, name, npi FROM procedure_providers ORDER BY name, ppid");
212 while ($pprow = sqlFetchArray($ppres)) {
216 echo "<option value='" . attr($pprow['ppid']) . "'";
217 if ($pprow['ppid'] == $processing_lab) {
220 if (stripos($pprow['npi'], 'QUEST') !== false) {
221 $pprow['name'] = "Quest Diagnostics";
224 echo ">" . text($pprow['name']) . "</option>";
229 <div
class="form-group">
230 <div
class="input-group-append">
231 <input name
='form_max_results' id
='form_max_results' class='form-control'
232 style
="max-width:75px;margin-left:20px;"
233 type
="number" title
="<?php echo xla('Max number of results to process at a time per Lab') ?>"
234 step
="1" min
="0" max
="50"
235 value
="<?php echo attr($_REQUEST['form_max_results'] ?: 10); ?>" />
236 <span
class="input-group-text"><?php
echo xlt('Results Per Lab'); ?
></span
>
238 <div
class="form-check form-check-inline ml-2">
239 <input
class="form-check-input" type
='checkbox' name
='form_patient' id
="ck_patient" value
='1'
240 <?php
if ($form_patient) {
243 <label
class="input-group-text form-check-label" for="ck_patient"><?php
echo xlt('Current Patient Only'); ?
></label
>
245 <span
class="d-none" id
="wait"><?php
echo xlt("Working") . ' .. ';?
>
246 <i
class="fa fa-cog fa-spin fa-2x"></i
>
252 <div
class="clearfix"></div
>
253 <?php
if ($errmsg) { ?
>
254 <span
class='text-danger'><?php
echo text($errmsg); ?
></span
>
259 $info = array('select' => array());
260 // We skip match/delete processing if this is just a refresh, because that
261 // might be a nasty surprise.
262 if (empty($_POST['form_external_refresh'])) {
263 // Get patient matching selections from this form if there are any.
264 if (is_array($_POST['select'])) {
265 foreach ($_POST['select'] as $selkey => $selval) {
266 $info['select'][$selkey] = $selval;
269 // Get file delete requests from this form if there are any.
270 if (is_array($_POST['delete'])) {
271 foreach ($_POST['delete'] as $delkey => $dummy) {
272 $info[$delkey] = array('delete' => true);
277 // is this a manual run
278 if (isset($_REQUEST['form_manual'])) {
279 $info['orphaned_order'] = "R";
281 // Attempt to post any incoming results.
282 if ($_REQUEST['form_process_labs'] ||
$info['orphaned_order'] == "R") {
283 $errmsg = poll_hl7_results($info, $processing_lab);
285 // echo "<!--\n"; // debugging
286 // print_r($info); // debugging
287 // echo "-->\n"; // debugging
289 // Display a row for each required patient matching decision or message.
293 $orphan_orders = false;
295 // Generate HTML to request patient matching.
296 if (is_array($info['match'])) {
297 foreach ($info['match'] as $matchkey => $matchval) {
299 $s .= " <tr class='detail'>\n";
300 $s .= " <td> </td>\n";
301 $s .= " <td> </td>\n";
302 $s .= " <td><a href='javascript:openPtMatch(" . attr_js($matchkey) . ")'>";
303 $tmp = unserialize($matchkey, ['allowed_classes' => false]);
304 $s .= xlt('Click to match patient') . ' "' . text($tmp['lname']) . ', ' . text($tmp['fname']) . '"';
307 $s .= " <td style='width:1%'><input type='text' name='select[" .
308 attr($matchkey) . "]' size='3' value=0 " .
309 "style='background-color:transparent' readonly /></td>\n";
314 foreach ($info as $infokey => $infoval) {
315 if ($infokey == 'match' ||
$infokey == 'select') {
320 if (is_array($infoval['mssgs'])) {
321 foreach ($infoval['mssgs'] as $message) {
322 $s .= " <tr class='detail'>\n";
323 if (substr($message, 0, 1) == '*') {
325 // Error message starts with '*'
327 $s .= " <td><input type='checkbox' name='delete[" . attr($infokey) . "]' value='1' /></td>\n";
328 $s .= " <td>" . text($infokey) . "</td>\n";
330 $s .= " <td> </td>\n";
331 $s .= " <td> </td>\n";
333 $s .= " <td colspan='2' class='bg-danger'>" . text(substr($message, 1)) . "</td>\n";
335 // Informational message starts with '>'
336 $s .= " <td> </td>\n";
337 $s .= " <td>" . text($infokey) . "</td>\n";
338 $s .= " <td colspan='2' class='bg-success'>" . text(substr($message, 1)) . "</td>\n";
346 if ($matchreqs ||
$errors) {
347 $orphan_orders = true;
348 echo "<h4 class='bg-success text-white mb-0'>";
349 echo xlt('Incoming results requiring attention:');
353 echo "<div class='table-responsive'><table class='table table-sm mb-0'>\n";
355 echo " <tr class='head'>\n";
356 echo " <th>" . xlt('Delete') . "</th>\n";
357 echo " <th>" . xlt('Processing Lab Name/Internal Lab Id/Results File Name') . "</th>\n";
358 echo " <th>" . xlt('Message') . "</th>\n";
359 echo " <th>" . xlt('Match') . "</th>\n";
360 echo " </tr></thead><tbody id='wait'>\n";
362 echo "</tbody></table>\n";
364 if ($matchreqs ||
$errors) { ?
>
365 <div
class="" data
-toggle
="collapse" data
-target
="#help">
366 <i
class="fa fa-plus"></i
><span id
="wait"> <strong
>Help
</strong
></span
>
368 <div
class="collapse bg-warning" id
="help">
369 <?php
if ($matchreqs) { ?
>
371 Click the returned patient line item to pull up the patient matching dialog to verify
if a match is indicated
or select to create a
new patient
. The match column shows the selected patient ID
, or if 0, will create a
new patient
. Simply clicking Resolve Orphans again without selecting a patient will automatically create a
new patient based on the orders patient information
. After verifying patients
, click Resolve Orphans one last time to complete transactions
. A reminder message will be sent to the provider associated with order
.
375 Checkboxes indicate
if you want to reject
and delete the HL7 file
. Clicking the Resolve Orphans button will determine
if the patient associated with the manual orders exists
and will then create a
new encounter
if one doesn
't exist within 30 days of the orphaned orders order date, then will attach the new order to it. If the patient does not exist and there could be a potential match with existing patients then, on return, you will be given an opportunity to match or create a new patient to assign the orphaned order. Otherwise, if there is not any ambiguity that the concerning patient of the order does not currently exist, then the patient will be automatically created and logged to messages and results returned below for review.
382 if ($orphan_orders) {
383 echo "<tr><td><button class='btn btn
-add btn
-danger
' type='submit
' name='form_manual
'>" . xlt('Resolve Orphans
') . "</button></td></tr>";
385 echo "</tr></table>";
388 // If there was a fatal error display that.
390 echo "<span class='text
-danger
'>" . text($errmsg) . "</span><br />\n";
393 $form_from_date = empty($_POST['form_from_date
']) ? '' : trim($_POST['form_from_date
']);
394 $form_to_date = empty($_POST['form_to_date
']) ? '' : trim($_POST['form_to_date
']);
396 $form_reviewed = empty($_POST['form_reviewed
']) ? 3 : (int)$_POST['form_reviewed
'];
397 $form_patient = !empty($_POST['form_patient
']);
398 $form_provider = empty($_POST['form_provider
']) ? '' : (int)$_POST['form_provider
'];
399 $form_lab_search = empty($_POST['form_lab_search
']) ? '' : (int)$_POST['form_lab_search
'];
402 <div class="form-row my-2">
403 <div class="col-md input-group">
404 <label class='col
-form
-label
' for="form_from_date"><?php echo xlt('From
'); ?>:</label>
405 <input type='text
' size='9' name='form_from_date
' id='form_from_date
' class='form
-control datepicker
' value='<?php
echo attr($form_from_date); ?
>' title='<?php
echo xla('yyyy-mm-dd'); ?
>' placeholder='<?php
echo xla('yyyy-mm-dd'); ?
>' />
407 <div class="col-md input-group">
408 <label class='col
-form
-label
' for="form_to_date"><?php echo xlt('To
{{Range
}}'); ?>:</label>
409 <input type='text
' size='9' name='form_to_date
' id='form_to_date
' class='form
-control datepicker
' value='<?php
echo attr($form_to_date); ?
>' title='<?php
echo xla('yyyy-mm-dd'); ?
>' placeholder='<?php
echo xla('yyyy-mm-dd'); ?
>' />
412 <select class="col-md form-control" name='form_reviewed
'>
417 '2' => xl('Reviewed
'),
418 '3' => xl('Received
, unreviewed
'),
419 '4' => xl('Sent
, not received
'),
420 '5' => xl('Not sent
'),
423 echo "<option value='" . attr($key) . "'";
424 if ($key == $form_reviewed) {
427 echo ">" . text($value) . "</option>\n";
434 generate_form_field(array('data_type
' => 10, 'field_id
' => 'provider
',
435 'empty_title
' => '-- All Providers
--'), $form_provider);
439 <select name='form_lab_search
' id='form_lab_search
' class='form
-control
'>
440 <option value="0"><?php echo xlt('All Labs
'); ?></option>
442 $ppres = sqlStatement("SELECT ppid, name FROM procedure_providers ORDER BY name, ppid");
443 while ($pprow = sqlFetchArray($ppres)) {
444 echo "<option value='" . attr($pprow['ppid']) . "'";
445 if ($pprow['ppid
'] == $form_lab_search) {
448 echo ">" . text($pprow['name
']) . "</option>";
454 <button type="submit" class="btn btn-outline-primary btn-search float-left" name='form_refresh
'><?php echo xlt('Filter
'); ?></button>
458 <div class="container-fluid">
459 <table class="table table-bordered table-condensed table-striped table-hover">
462 <th colspan='2'><?php echo xlt('Patient
'); ?></th>
463 <th colspan='3'><?php echo xlt('Order
'); ?></th>
464 <th colspan='2'><?php echo xlt('Procedure
'); ?></th>
465 <th colspan='2'><?php echo xlt('Report
'); ?></th>
468 <th><?php echo xlt('Name
'); ?></th>
469 <th><?php echo xlt('ID
'); ?></th>
470 <th><?php echo xlt('Date
'); ?></th>
471 <th><?php echo xlt('ID
'); ?></th>
472 <th><?php echo xlt('Lab
'); ?></th>
473 <th><?php echo xlt('Code
'); ?></th>
474 <th><?php echo xlt('Description
'); ?></th>
475 <th><?php echo xlt('Date
'); ?></th>
476 <th><?php echo xlt('Status
'); ?></th>
480 if ($start_form !== true) {
482 "po.patient_id, po.procedure_order_id, po.date_ordered, po.date_transmitted, po.lab_id, pp.npi, " .
483 "pc.procedure_order_seq, pc.procedure_code, pc.procedure_name, pc.do_not_send, " .
484 "pr.procedure_report_id, pr.date_report, pr.date_report_tz, pr.report_status, pr.review_status";
487 "LEFT JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id AND " .
488 "pr.procedure_order_seq = pc.procedure_order_seq";
491 "po.date_ordered, po.procedure_order_id, " .
492 "pc.do_not_send, pc.procedure_order_seq, pr.procedure_report_id";
495 $sqlBindArray = array();
497 if (!empty($form_from_date)) {
498 $where .= " AND po.date_ordered >= ?";
499 $sqlBindArray[] = $form_from_date;
502 if (!empty($form_to_date)) {
503 $where .= " AND po.date_ordered <= ?";
504 $sqlBindArray[] = $form_to_date;
508 $where .= " AND po.patient_id = ?";
509 $sqlBindArray[] = $pid;
512 if ($form_provider) {
513 $where .= " AND po.provider_id = ?";
514 $sqlBindArray[] = $form_provider;
517 if ($form_lab_search > 0) {
518 $where .= " AND po.lab_id = ?";
519 $sqlBindArray[] = $form_lab_search;
522 if ($form_reviewed == 2) {
523 $where .= " AND pr.procedure_report_id IS NOT NULL AND pr.review_status = 'reviewed
'";
524 } else if ($form_reviewed == 3) {
525 $where .= " AND pr.procedure_report_id IS NOT NULL AND pr.review_status != 'reviewed
'";
526 } else if ($form_reviewed == 4) {
527 $where .= " AND po.date_transmitted IS NOT NULL AND pr.procedure_report_id IS NULL";
528 } else if ($form_reviewed == 5) {
529 $where .= " AND po.date_transmitted IS NULL AND pr.procedure_report_id IS NULL";
533 "pd.fname, pd.mname, pd.lname, pd.pubpid, $selects " .
534 "FROM procedure_order AS po " .
535 "LEFT JOIN procedure_order_code AS pc ON pc.procedure_order_id = po.procedure_order_id " .
536 "LEFT JOIN procedure_providers AS pp ON po.lab_id = pp.ppid " .
537 "LEFT JOIN patient_data AS pd ON pd.pid = po.patient_id $joins " .
539 "ORDER BY pd.lname, pd.fname, pd.mname, po.patient_id, $orderby";
541 $res = sqlStatement($query, $sqlBindArray);
551 while ($row = sqlFetchArray($res)) {
552 $patient_id = empty($row['patient_id
']) ? 0 : ($row['patient_id
'] + 0);
553 $order_id = empty($row['procedure_order_id
']) ? 0 : ($row['procedure_order_id
'] + 0);
554 $order_seq = empty($row['procedure_order_seq
']) ? 0 : ($row['procedure_order_seq
'] + 0);
555 $date_ordered = empty($row['date_ordered
']) ? '' : $row['date_ordered
'];
556 $date_transmitted = empty($row['date_transmitted
']) ? '' : $row['date_transmitted
'];
557 $procedure_code = empty($row['procedure_code
']) ? '' : $row['procedure_code
'];
558 $procedure_name = empty($row['procedure_name
']) ? '' : $row['procedure_name
'];
559 $report_id = empty($row['procedure_report_id
']) ? 0 : ($row['procedure_report_id
'] + 0);
560 $date_report = empty($row['date_report
']) ? '' : substr($row['date_report
'], 0, 16);
561 $date_report_suf = empty($row['date_report_tz
']) ? '' : (' ' . $row['date_report_tz
']);
562 $report_status = empty($row['report_status
']) ? '' : $row['report_status
'];
563 $review_status = empty($row['review_status
']) ? '' : $row['review_status
'];
564 $report_lab = empty($row['npi
']) ? '' : $row['npi
'];
566 // Sendable procedures sort first, so this also applies to the order on an order ID change.
567 $sendable = isset($row['procedure_order_seq
']) && $row['do_not_send
'] == 0;
569 $ptname = $row['lname
'];
570 if ($row['fname
'] || $row['mname
']) {
571 $ptname .= ', ' . $row['fname
'] . ' ' . $row['mname
'];
574 if ($lastpoid != $order_id || $lastpcid != $order_seq) {
578 //$bgcolor = "#" . (($encount & 1) ? "ddddff" : "ffdddd");
579 echo " <tr class='detail
'>\n";
581 // Generate patient columns.
582 if ($lastptid != $patient_id) {
584 echo " <td class='text
-primary
' onclick='openPatient(" . attr_js($patient_id) . ")' style='cursor
: pointer
;'>";
587 echo " <td>" . text($row['pubpid
']) . "</td>\n";
589 echo " <td colspan='2'> </td>";
592 // Generate order columns.
593 if ($lastpoid != $order_id) {
596 // Checkbox to support sending unsent orders, disabled if sent.
597 echo "<input type='checkbox
' name='form_cb
[" . attr($order_id) . "]' value='" . attr($order_id) . "' ";
598 if ($date_transmitted || !$sendable) {
606 // Order date comes with a link to open results in the same frame.
607 echo "<a href='javascript
:openResults(" . attr_js($order_id) . ")' ";
608 echo "title='" . xla('Click for results') . "'>";
609 echo text($date_ordered);
612 // Order ID comes with a link to open the manifest in a new window/tab.
613 echo "<a href='" . $GLOBALS['webroot'];
614 echo "/interface/orders
/order_manifest
.php?orderid
=";
615 echo attr_url($order_id);
616 echo "' target='_blank
' onclick='top
.restoreSession()' ";
617 echo "title='" . xla('Click for order summary') . "'>";
618 echo text($order_id);
620 echo " <td>" . text($report_lab) . "</td>\n";
622 echo " <td colspan='3' style='background
-color
:transparent
'> </td>";
625 // Generate procedure columns.
626 if ($order_seq && $lastpcid != $order_seq) {
628 echo " <td>" . text($procedure_code) . "</td>\n";
629 echo " <td>" . text($procedure_name) . "</td>\n";
631 echo " <td><s>" . text($procedure_code) . "</s></td>\n";
632 echo " <td><s>" . text($procedure_name) . "</s></td>\n";
635 echo " <td colspan='2' style='background
-color
:transparent
'> </td>";
638 // Generate report columns.
640 echo " <td>" . text($date_report . $date_report_suf) . "</td>\n";
641 echo " <td title='" . xla('Check mark indicates reviewed') . "'>";
642 echo myCellText(getListItem('proc_rep_status
', $report_status));
643 if ($review_status == 'reviewed
') {
644 echo " ✓"; // unicode check mark character
649 echo " <td colspan='2' style='background
-color
:transparent
'> </td>";
654 $lastptid = $patient_id;
655 $lastpoid = $order_id;
656 $lastpcid = $order_seq;
661 <?php if ($num_checkboxes) { ?>
662 <button type="submit" class="btn btn-primary btn-transmit" name='form_xmit
'
663 value='<?php
echo xla('Transmit Selected Orders'); ?
>'><?php echo xlt('Transmit Selected Orders
'); ?>