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.php");
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 use OpenEMR\Common\Acl\AclMain
;
30 use OpenEMR\Common\Twig\TwigContainer
;
31 use OpenEMR\Core\Header
;
33 // Check authorization.
34 $thisauth = AclMain
::aclCheckCore('patients', 'med');
36 echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Procedure Orders and Reports")]);
40 $form_patient = !empty($_POST['form_patient']);
41 $processing_lab = $_REQUEST['form_lab_id'] ??
'';
43 if (!isset($_REQUEST['form_refresh']) && !isset($_REQUEST['form_process_labs']) && !isset($_REQUEST['form_manual'])) {
48 * Get a list item title, translating if required.
50 * @param string $listid List identifier.
51 * @param string $value List item identifier.
52 * @return string The item's title.
54 function getListItem($listid, $value)
57 "SELECT title FROM list_options " .
58 "WHERE list_id = ? AND option_id = ? AND activity = 1",
59 array($listid, $value)
61 $tmp = xl_list_label($lrow['title']);
63 $tmp = (($value === '') ?
'' : "($value)");
70 * Adapt text to be suitable as the contents of a table cell.
72 * @param string $s Input text.
73 * @return string Output text.
75 function myCellText($s)
86 // Send selected unsent orders if requested. This does not support downloading
87 // very well as it will only send the first of those.
88 if (!empty($_POST['form_xmit'])) {
89 foreach ($_POST['form_cb'] as $formid) {
90 $row = sqlQuery("SELECT lab_id FROM procedure_order WHERE procedure_order_id = ?", array($formid));
91 $ppid = (int)$row['lab_id'];
93 $errmsg = gen_hl7_order($formid, $hl7);
95 $errmsg = send_hl7_order($ppid, $hl7);
102 sqlStatement("UPDATE procedure_order SET date_transmitted = NOW() WHERE procedure_order_id = ?", array($formid));
109 <?php Header
::setupHeader(['datetime-picker']); ?
>
110 <title
><?php
echo xlt('Procedure Orders and Reports'); ?
></title
>
116 background
-color
: var(--gray
);
123 background
-color
: var(--gray200
);
133 a
, a
:visited
, a
:hover
{
139 var dlgtitle
= <?php
echo xlj("Match Patient") ?
>;
141 function openResults(orderid
) {
142 top
.restoreSession();
143 // Open results in a new window. The options parameter serves to defeat Firefox's
144 // "open windows in a new tab", which is what we want because the doc may want to
145 // see the results concurrently with other stuff like related patient notes.
146 // Opening in the other frame is not good enough because if they then do related
147 // patients notes it will wipe out this script's list. We need 3 viewports.
148 window
.open('single_order_results.php?orderid=' +
encodeURIComponent(orderid
), '_blank', 'toolbar=0,location=0,menubar=0,scrollbars=yes');
150 // To open results in the same frame:
151 // document.location.href = 'single_order_results.php?orderid=' + orderid;
153 // To open results in the "other" frame:
155 // var othername = (w.name == 'RTop') ? 'RBot' : 'RTop';
156 // w.parent.left_nav.forceDual();
157 // w.parent.left_nav.loadFrame('ore1', othername, 'orders/single_order_results.php?orderid=' + orderid);
160 // Invokes the patient matching dialog.
161 // args is a PHP-serialized array of patient attributes.
162 // The dialog script will directly insert the selected pid value, or 0,
163 // into the value of the form field named "[select][$key]".
165 function openPtMatch(args
) {
166 top
.restoreSession();
167 dlgopen('patient_match_dialog.php?key=' +
encodeURIComponent(args
), '_blank', 850, 400, '', dlgtitle
);
170 function openPatient(pid
) {
171 top
.restoreSession();
172 document
.location
.href
= "../patient_file/summary/demographics.php?set_pid=" +
encodeURIComponent(pid
);
176 $
('.datepicker').datetimepicker({
177 <?php
$datetimepicker_timepicker = false; ?
>
178 <?php
$datetimepicker_showseconds = false; ?
>
179 <?php
$datetimepicker_formatInput = false; ?
>
180 <?php
require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?
>
181 <?php
// can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
183 $
("#wait").addClass('d-none');
187 $
("#wait").removeClass('d-none');
193 <body onsubmit
="doWait(event)">
194 <div
class="page-header ml-2">
195 <h2
><?php
echo xlt('Procedure Orders and Reports'); ?
></h2
>
197 <form
class="form-inline" method
='post' action
='list_reports.php' enctype
='multipart/form-data'>
198 <div
class="container">
199 <!-- This might be set by the results window
: -->
200 <input
class="d-none row" type
='text' name
='form_external_refresh' value
='' />
201 <div
class="form-row">
203 <div
class="form-group">
204 <div
class="input-group-btn input-group-append">
205 <button
class='btn btn-primary' name
='form_process_labs'
206 title
="Click to process pending results from selected Labs."
207 value
="true"><?php
echo xlt('Process Results For'); ?
><i
class="ml-1 btn-transmit"></i
>
209 <select name
='form_lab_id' id
='form_lab_id' class='form-control'>
210 <option value
="0"><?php
echo xlt('All Labs'); ?
></option
>
212 $ppres = sqlStatement("SELECT ppid, name, npi FROM procedure_providers ORDER BY name, ppid");
213 while ($pprow = sqlFetchArray($ppres)) {
214 echo "<option value='" . attr($pprow['ppid']) . "'";
215 if ($pprow['ppid'] == $processing_lab) {
218 if (stripos($pprow['npi'], 'QUEST') !== false) {
219 $pprow['name'] = "Quest Diagnostics";
221 echo ">" . text($pprow['name']) . "</option>";
226 <div
class="form-group">
227 <div
class="input-group-append">
228 <input name
='form_max_results' id
='form_max_results' class='form-control'
229 style
="max-width:75px;margin-left:20px;"
230 type
="number" title
="<?php echo xla('Max number of results to process at a time per Lab') ?>"
231 step
="1" min
="0" max
="50"
232 value
="<?php echo attr($_REQUEST['form_max_results'] ?? 10); ?>" />
233 <span
class="input-group-text"><?php
echo xlt('Results Per Lab'); ?
></span
>
235 <div
class="form-check form-check-inline ml-2">
236 <input
class="form-check-input" type
='checkbox' name
='form_patient' id
="ck_patient" value
='1'
237 <?php
if ($form_patient) {
240 <label
class="input-group-text form-check-label" for="ck_patient"><?php
echo xlt('Current Patient Only'); ?
></label
>
242 <span
class="d-none" id
="wait"><?php
echo xlt("Working") . ' .. ';?
>
243 <i
class="fa fa-cog fa-spin fa-2x"></i
>
249 <div
class="clearfix"></div
>
250 <?php
if ($errmsg) { ?
>
251 <span
class='text-danger'><?php
echo text($errmsg); ?
></span
>
256 $info = array('select' => array());
257 // We skip match/delete processing if this is just a refresh, because that
258 // might be a nasty surprise.
259 if (empty($_POST['form_external_refresh'])) {
260 // Get patient matching selections from this form if there are any.
261 if (!empty($_POST['select']) && is_array($_POST['select'])) {
262 foreach ($_POST['select'] as $selkey => $selval) {
263 $info['select'][urldecode($selkey)] = $selval;
266 // Get file delete requests from this form if there are any.
267 if (!empty($_POST['delete']) && is_array($_POST['delete'])) {
268 foreach ($_POST['delete'] as $delkey => $dummy) {
269 $info[$delkey] = array('delete' => true);
274 // is this a manual run
275 if (isset($_REQUEST['form_manual'])) {
276 $info['orphaned_order'] = "R";
278 // Attempt to post any incoming results.
279 if (!empty($_REQUEST['form_process_labs']) ||
(!empty($info['orphaned_order']) && $info['orphaned_order'] == "R")) {
280 $errmsg = poll_hl7_results($info, $processing_lab);
282 // echo "<!--\n"; // debugging
283 // print_r($info); // debugging
284 // echo "-->\n"; // debugging
286 // Display a row for each required patient matching decision or message.
290 $orphan_orders = false;
292 // Generate HTML to request patient matching.
293 if (!empty($info['match']) && is_array($info['match'])) {
294 foreach ($info['match'] as $matchkey => $matchval) {
296 $s .= " <tr class='detail'>\n";
297 $s .= " <td> </td>\n";
298 $s .= " <td> </td>\n";
299 $s .= " <td><a href='javascript:openPtMatch(" . attr_js($matchkey) . ")'>";
300 $tmp = unserialize($matchkey, ['allowed_classes' => false]);
301 $s .= xlt('Click to match patient') . ' "' . text($tmp['lname']) . ', ' . text($tmp['fname']) . '"';
304 $s .= " <td style='width:1%'><input type='text' name='select[" .
305 attr($matchkey) . "]' size='3' value=0 " .
306 "style='background-color:transparent' readonly /></td>\n";
311 foreach ($info as $infokey => $infoval) {
312 if ($infokey == 'match' ||
$infokey == 'select') {
317 if (is_array($infoval) && !empty($infoval['mssgs'])) {
318 foreach ($infoval['mssgs'] as $message) {
319 $s .= " <tr class='detail'>\n";
320 if (substr($message, 0, 1) == '*') {
322 // Error message starts with '*'
324 $s .= " <td><input type='checkbox' name='delete[" . attr($infokey) . "]' value='1' /></td>\n";
325 $s .= " <td>" . text($infokey) . "</td>\n";
327 $s .= " <td> </td>\n";
328 $s .= " <td> </td>\n";
330 $s .= " <td colspan='2' class='bg-danger'>" . text(substr($message, 1)) . "</td>\n";
332 // Informational message starts with '>'
333 $s .= " <td> </td>\n";
334 $s .= " <td>" . text($infokey) . "</td>\n";
335 $s .= " <td colspan='2' class='bg-success'>" . text(substr($message, 1)) . "</td>\n";
343 if ($matchreqs ||
$errors) {
344 $orphan_orders = true;
345 echo "<h4 class='bg-success text-white mb-0'>";
346 echo xlt('Incoming results requiring attention:');
350 echo "<div class='table-responsive'><table class='table table-sm mb-0'>\n";
352 echo " <tr class='head'>\n";
353 echo " <th>" . xlt('Delete') . "</th>\n";
354 echo " <th>" . xlt('Processing Lab Name/Internal Lab Id/Results File Name') . "</th>\n";
355 echo " <th>" . xlt('Message') . "</th>\n";
356 echo " <th>" . xlt('Match') . "</th>\n";
357 echo " </tr></thead><tbody id='wait'>\n";
359 echo "</tbody></table>\n";
361 if ($matchreqs ||
$errors) { ?
>
362 <div
class="" data
-toggle
="collapse" data
-target
="#help">
363 <i
class="fa fa-plus"></i
><span id
="wait"> <strong
>Help
</strong
></span
>
365 <div
class="collapse bg-warning" id
="help">
366 <?php
if ($matchreqs) { ?
>
368 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
.
372 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.
379 if ($orphan_orders) {
380 echo "<tr><td><button class='btn btn
-add btn
-danger
' type='submit
' name='form_manual
'>" . xlt('Resolve Orphans
') . "</button></td></tr>";
382 echo "</tr></table>";
385 // If there was a fatal error display that.
387 echo "<span class='text
-danger
'>" . text($errmsg) . "</span><br />\n";
390 $form_from_date = empty($_POST['form_from_date
']) ? '' : trim($_POST['form_from_date
']);
391 $form_to_date = empty($_POST['form_to_date
']) ? '' : trim($_POST['form_to_date
']);
393 $form_reviewed = empty($_POST['form_reviewed
']) ? 3 : (int)$_POST['form_reviewed
'];
394 $form_patient = !empty($_POST['form_patient
']);
395 $form_provider = empty($_POST['form_provider
']) ? '' : (int)$_POST['form_provider
'];
396 $form_lab_search = empty($_POST['form_lab_search
']) ? '' : (int)$_POST['form_lab_search
'];
399 <div class="form-row my-2">
400 <div class="col-md input-group">
401 <label class='col
-form
-label
' for="form_from_date"><?php echo xlt('From
'); ?>:</label>
402 <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'); ?
>' />
404 <div class="col-md input-group">
405 <label class='col
-form
-label
' for="form_to_date"><?php echo xlt('To
{{Range
}}'); ?>:</label>
406 <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'); ?
>' />
409 <select class="col-md form-control" name='form_reviewed
'>
414 '2' => xl('Reviewed
'),
415 '3' => xl('Received
, unreviewed
'),
416 '4' => xl('Sent
, not received
'),
417 '5' => xl('Not sent
'),
420 echo "<option value='" . attr($key) . "'";
421 if ($key == $form_reviewed) {
424 echo ">" . text($value) . "</option>\n";
431 generate_form_field(array('data_type
' => 10, 'field_id
' => 'provider
',
432 'empty_title
' => '-- All Providers
--'), $form_provider);
436 <select name='form_lab_search
' id='form_lab_search
' class='form
-control
'>
437 <option value="0"><?php echo xlt('All Labs
'); ?></option>
439 $ppres = sqlStatement("SELECT ppid, name FROM procedure_providers ORDER BY name, ppid");
440 while ($pprow = sqlFetchArray($ppres)) {
441 echo "<option value='" . attr($pprow['ppid']) . "'";
442 if ($pprow['ppid
'] == $form_lab_search) {
445 echo ">" . text($pprow['name
']) . "</option>";
451 <button type="submit" class="btn btn-outline-primary btn-search float-left" name='form_refresh
'><?php echo xlt('Filter
'); ?></button>
455 <div class="container-fluid">
456 <table class="table table-bordered table-condensed table-striped table-hover">
459 <th colspan='2'><?php echo xlt('Patient
'); ?></th>
460 <th colspan='3'><?php echo xlt('Order
'); ?></th>
461 <th colspan='2'><?php echo xlt('Procedure
'); ?></th>
462 <th colspan='2'><?php echo xlt('Report
'); ?></th>
465 <th><?php echo xlt('Name
'); ?></th>
466 <th><?php echo xlt('ID
'); ?></th>
467 <th><?php echo xlt('Date
'); ?></th>
468 <th><?php echo xlt('ID
'); ?></th>
469 <th><?php echo xlt('Lab
'); ?></th>
470 <th><?php echo xlt('Code
'); ?></th>
471 <th><?php echo xlt('Description
'); ?></th>
472 <th><?php echo xlt('Date
'); ?></th>
473 <th><?php echo xlt('Status
'); ?></th>
477 if ($start_form !== true) {
479 "po.patient_id, po.procedure_order_id, po.date_ordered, po.date_transmitted, po.lab_id, pp.npi, " .
480 "pc.procedure_order_seq, pc.procedure_code, pc.procedure_name, pc.do_not_send, " .
481 "pr.procedure_report_id, pr.date_report, pr.date_report_tz, pr.report_status, pr.review_status";
484 "LEFT JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id AND " .
485 "pr.procedure_order_seq = pc.procedure_order_seq";
488 "po.date_ordered, po.procedure_order_id, " .
489 "pc.do_not_send, pc.procedure_order_seq, pr.procedure_report_id";
492 $sqlBindArray = array();
494 if (!empty($form_from_date)) {
495 $where .= " AND po.date_ordered >= ?";
496 $sqlBindArray[] = $form_from_date;
499 if (!empty($form_to_date)) {
500 $where .= " AND po.date_ordered <= ?";
501 $sqlBindArray[] = $form_to_date;
505 $where .= " AND po.patient_id = ?";
506 $sqlBindArray[] = $pid;
509 if ($form_provider) {
510 $where .= " AND po.provider_id = ?";
511 $sqlBindArray[] = $form_provider;
514 if ($form_lab_search > 0) {
515 $where .= " AND po.lab_id = ?";
516 $sqlBindArray[] = $form_lab_search;
519 if ($form_reviewed == 2) {
520 $where .= " AND pr.procedure_report_id IS NOT NULL AND pr.review_status = 'reviewed
'";
521 } elseif ($form_reviewed == 3) {
522 $where .= " AND pr.procedure_report_id IS NOT NULL AND pr.review_status != 'reviewed
'";
523 } elseif ($form_reviewed == 4) {
524 $where .= " AND po.date_transmitted IS NOT NULL AND pr.procedure_report_id IS NULL";
525 } elseif ($form_reviewed == 5) {
526 $where .= " AND po.date_transmitted IS NULL AND pr.procedure_report_id IS NULL";
530 "pd.fname, pd.mname, pd.lname, pd.pubpid, $selects " .
531 "FROM procedure_order AS po " .
532 "LEFT JOIN procedure_order_code AS pc ON pc.procedure_order_id = po.procedure_order_id " .
533 "LEFT JOIN procedure_providers AS pp ON po.lab_id = pp.ppid " .
534 "LEFT JOIN patient_data AS pd ON pd.pid = po.patient_id $joins " .
536 "ORDER BY pd.lname, pd.fname, pd.mname, po.patient_id, $orderby";
538 $res = sqlStatement($query, $sqlBindArray);
548 while ($row = sqlFetchArray($res)) {
549 $patient_id = empty($row['patient_id
']) ? 0 : ($row['patient_id
'] + 0);
550 $order_id = empty($row['procedure_order_id
']) ? 0 : ($row['procedure_order_id
'] + 0);
551 $order_seq = empty($row['procedure_order_seq
']) ? 0 : ($row['procedure_order_seq
'] + 0);
552 $date_ordered = empty($row['date_ordered
']) ? '' : $row['date_ordered
'];
553 $date_transmitted = empty($row['date_transmitted
']) ? '' : $row['date_transmitted
'];
554 $procedure_code = empty($row['procedure_code
']) ? '' : $row['procedure_code
'];
555 $procedure_name = empty($row['procedure_name
']) ? '' : $row['procedure_name
'];
556 $report_id = empty($row['procedure_report_id
']) ? 0 : ($row['procedure_report_id
'] + 0);
557 $date_report = empty($row['date_report
']) ? '' : substr($row['date_report
'], 0, 16);
558 $date_report_suf = empty($row['date_report_tz
']) ? '' : (' ' . $row['date_report_tz
']);
559 $report_status = empty($row['report_status
']) ? '' : $row['report_status
'];
560 $review_status = empty($row['review_status
']) ? '' : $row['review_status
'];
561 $report_lab = empty($row['npi
']) ? '' : $row['npi
'];
563 // Sendable procedures sort first, so this also applies to the order on an order ID change.
564 $sendable = isset($row['procedure_order_seq
']) && $row['do_not_send
'] == 0;
566 $ptname = $row['lname
'];
567 if ($row['fname
'] || $row['mname
']) {
568 $ptname .= ', ' . $row['fname
'] . ' ' . $row['mname
'];
571 if ($lastpoid != $order_id || $lastpcid != $order_seq) {
575 //$bgcolor = "#" . (($encount & 1) ? "ddddff" : "ffdddd");
576 echo " <tr class='detail
'>\n";
578 // Generate patient columns.
579 if ($lastptid != $patient_id) {
581 echo " <td class='text
-primary
' onclick='openPatient(" . attr_js($patient_id) . ")' style='cursor
: pointer
;'>";
584 echo " <td>" . text($row['pubpid
']) . "</td>\n";
586 echo " <td colspan='2'> </td>";
589 // Generate order columns.
590 if ($lastpoid != $order_id) {
593 // Checkbox to support sending unsent orders, disabled if sent.
594 echo "<input type='checkbox
' name='form_cb
[" . attr($order_id) . "]' value='" . attr($order_id) . "' ";
595 if ($date_transmitted || !$sendable) {
603 // Order date comes with a link to open results in the same frame.
604 echo "<a href='javascript
:openResults(" . attr_js($order_id) . ")' ";
605 echo "title='" . xla('Click for results') . "'>";
606 echo text($date_ordered);
609 // Order ID comes with a link to open the manifest in a new window/tab.
610 echo "<a href='" . $GLOBALS['webroot'];
611 echo "/interface/orders
/order_manifest
.php?orderid
=";
612 echo attr_url($order_id);
613 echo "' target='_blank
' onclick='top
.restoreSession()' ";
614 echo "title='" . xla('Click for order summary') . "'>";
615 echo text($order_id);
617 echo " <td>" . text($report_lab) . "</td>\n";
619 echo " <td colspan='3' style='background
-color
:transparent
'> </td>";
622 // Generate procedure columns.
623 if ($order_seq && $lastpcid != $order_seq) {
625 echo " <td>" . text($procedure_code) . "</td>\n";
626 echo " <td>" . text($procedure_name) . "</td>\n";
628 echo " <td><s>" . text($procedure_code) . "</s></td>\n";
629 echo " <td><s>" . text($procedure_name) . "</s></td>\n";
632 echo " <td colspan='2' style='background
-color
:transparent
'> </td>";
635 // Generate report columns.
637 echo " <td>" . text($date_report . $date_report_suf) . "</td>\n";
638 echo " <td title='" . xla('Check mark indicates reviewed') . "'>";
639 echo myCellText(getListItem('proc_rep_status
', $report_status));
640 if ($review_status == 'reviewed
') {
641 echo " ✓"; // unicode check mark character
646 echo " <td colspan='2' style='background
-color
:transparent
'> </td>";
651 $lastptid = $patient_id;
652 $lastpoid = $order_id;
653 $lastpcid = $order_seq;
658 <?php if (!empty($num_checkboxes)) { ?>
659 <button type="submit" class="btn btn-primary btn-transmit" name='form_xmit
'
660 value='<?php
echo xla('Transmit Selected Orders'); ?
>'><?php echo xlt('Transmit Selected Orders
'); ?>