Changes to support electronic lab ordering and results.
authorRod Roark <rod@sunsetsystems.com>
Fri, 28 Sep 2012 02:32:17 +0000 (27 19:32 -0700)
committerRod Roark <rod@sunsetsystems.com>
Tue, 29 Jan 2013 18:17:09 +0000 (29 10:17 -0800)
19 files changed:
interface/forms/procedure_order/new.php
interface/forms/procedure_order/report.php
interface/main/left_nav.php
interface/orders/gen_hl7_order.inc.php [new file with mode: 0644]
interface/orders/list_reports.php [new file with mode: 0644]
interface/orders/load_compendium.php [new file with mode: 0644]
interface/orders/orders_results.php
interface/orders/pending_orders.php
interface/orders/procedure_provider_edit.php [new file with mode: 0644]
interface/orders/procedure_provider_list.php [new file with mode: 0644]
interface/orders/procedure_stats.php
interface/orders/qoe.inc.php [new file with mode: 0644]
interface/orders/receive_hl7_results.inc.php [new file with mode: 0644]
interface/orders/single_order_results.php [new file with mode: 0644]
interface/orders/types.php
interface/orders/types_ajax.php
interface/orders/types_edit.php
sql/4_1_1-to-4_1_2_upgrade.sql
sql/database.sql

index 4755f3f..c2737ed 100644 (file)
@@ -1,10 +1,23 @@
 <?php
-// Copyright (C) 2010 Rod Roark <rod@sunsetsystems.com>
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
+/**
+* Encounter form for entering procedure orders.
+*
+* Copyright (C) 2010-2013 Rod Roark <rod@sunsetsystems.com>
+*
+* LICENSE: This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://opensource.org/licenses/gpl-license.php>.
+*
+* @package   OpenEMR
+* @author    Rod Roark <rod@sunsetsystems.com>
+*/
 
 require_once("../../globals.php");
 require_once("$srcdir/api.inc");
@@ -12,6 +25,8 @@ require_once("$srcdir/forms.inc");
 require_once("$srcdir/options.inc.php");
 require_once("$srcdir/formdata.inc.php");
 require_once("$srcdir/formatting.inc.php");
+require_once("../../orders/qoe.inc.php");
+require_once("../../orders/gen_hl7_order.inc.php");
 
 // Defaults for new orders.
 $row = array(
@@ -47,17 +62,19 @@ function QuotedOrNull($fld) {
 
 $formid = formData('id', 'G') + 0;
 
-// If Save was clicked, save the info.
+// If Save or Transmit was clicked, save the info.
 //
-if ($_POST['bn_save']) {
+if ($_POST['bn_save'] || $_POST['bn_xmit']) {
+  $ppid = formData('form_lab_id') + 0;
 
   $sets =
-    "procedure_type_id = " . (formData('form_proc_type') + 0)           . ", " .
     "date_ordered = " . QuotedOrNull(formData('form_date_ordered'))     . ", " .
     "provider_id = " . (formData('form_provider_id') + 0)               . ", " .
+    "lab_id = " . $ppid                                                 . ", " .
     "date_collected = " . QuotedOrNull(formData('form_date_collected')) . ", " .
     "order_priority = '" . formData('form_order_priority')              . "', " .
     "order_status = '" . formData('form_order_status')                  . "', " .
+    "diagnoses = '" . formData('form_diagnoses')                        . "', " .
     "patient_instructions = '" . formData('form_patient_instructions')  . "', " .
     "patient_id = '" . $pid                                             . "', " .
     "encounter_id = '" . $encounter                                     . "'";
@@ -74,11 +91,83 @@ if ($_POST['bn_save']) {
   //
   else {
     $query = "INSERT INTO procedure_order SET $sets";
-    $newid = sqlInsert($query);
-    addForm($encounter, "Procedure Order", $newid, "procedure_order", $pid, $userauthorized);
+    $formid = sqlInsert($query);
+    addForm($encounter, "Procedure Order", $formid, "procedure_order", $pid, $userauthorized);
+  }
+
+  // Remove any existing procedures and their answers for this order and
+  // replace them from the form.
+
+  sqlStatement("DELETE FROM procedure_answers WHERE procedure_order_id = ?",
+    array($formid));
+  sqlStatement("DELETE FROM procedure_order_code WHERE procedure_order_id = ?",
+    array($formid));
+
+  for ($i = 0; isset($_POST['form_proc_type'][$i]); ++$i) {
+    $ptid = $_POST['form_proc_type'][$i] + 0;
+    if ($ptid <= 0) continue;
+
+    $prefix = "ans$i" . "_";
+
+    $poseq = sqlInsert("INSERT INTO procedure_order_code SET ".
+      "procedure_order_id = ?, " .
+      "procedure_code = (SELECT procedure_code FROM procedure_type WHERE procedure_type_id = ?), " .
+      "procedure_name = (SELECT name FROM procedure_type WHERE procedure_type_id = ?)",
+      array($formid, $ptid, $ptid));
+
+    $qres = sqlStatement("SELECT " .
+      "q.procedure_code, q.question_code, q.options, q.fldtype " .
+      "FROM procedure_type AS t " .
+      "JOIN procedure_questions AS q ON q.lab_id = t.lab_id " .
+      "AND q.procedure_code = t.procedure_code AND q.activity = 1 " .
+      "WHERE t.procedure_type_id = ? " .
+      "ORDER BY q.seq, q.question_text", array($ptid));
+
+    while ($qrow = sqlFetchArray($qres)) {
+      $options = trim($qrow['options']);
+      $qcode = trim($qrow['question_code']);
+      $fldtype = $qrow['fldtype'];
+      $data = '';
+      if ($fldtype == 'G') {
+        if ($_POST["G1_$prefix$qcode"]) {
+          $data = $_POST["G1_$prefix$qcode"] * 7 + $_POST["G2_$prefix$qcode"];
+        }
+      }
+      else {
+        $data = $_POST["$prefix$qcode"];
+      }
+      if (!isset($data) || $data === '') continue;
+      if (!is_array($data)) $data = array($data);
+      foreach ($data as $datum) {
+        // Note this will auto-assign the seq value.
+        sqlStatement("INSERT INTO procedure_answers SET ".
+          "procedure_order_id = ?, " .
+          "procedure_order_seq = ?, " .
+          "question_code = ?, " .
+          "answer = ?",
+          array($formid, $poseq, $qcode, strip_escape_custom($datum)));
+      }
+    }
+  }
+
+  $alertmsg = '';
+  if ($_POST['bn_xmit']) {
+    $hl7 = '';
+    $alertmsg = gen_hl7_order($formid, $hl7);
+    if (empty($alertmsg)) {
+      $alertmsg = send_hl7_order($ppid, $hl7);
+    }
   }
 
+  // TBD: Implement and set a transmit date in the order.
+  // Add code elsewhere to show a warning if a previously transmitted order is opened.
+
   formHeader("Redirecting....");
+  if ($alertmsg) {
+    echo "\n<script language='Javascript'>alert('";
+    echo addslashes(xl('Transmit failed') . ': ' . $alertmsg);
+    echo "')</script>\n";
+  }
   formJump();
   formFooter();
   exit;
@@ -86,14 +175,16 @@ if ($_POST['bn_save']) {
 
 if ($formid) {
   $row = sqlQuery ("SELECT * FROM procedure_order WHERE " .
-    "procedure_order_id = '$formid' AND activity = '1'") ;
+    "procedure_order_id = ?",
+    array($formid)) ;
 }
 
 $enrow = sqlQuery("SELECT p.fname, p.mname, p.lname, fe.date FROM " .
   "form_encounter AS fe, forms AS f, patient_data AS p WHERE " .
-  "p.pid = '$pid' AND f.pid = '$pid' AND f.encounter = '$encounter' AND " .
+  "p.pid = ? AND f.pid = p.pid AND f.encounter = ? AND " .
   "f.formdir = 'newpatient' AND f.deleted = 0 AND " .
-  "fe.id = f.form_id LIMIT 1");
+  "fe.id = f.form_id LIMIT 1",
+  array($pid, $encounter));
 ?>
 <html>
 <head>
@@ -124,20 +215,102 @@ td {
 <script language='JavaScript'>
 
 // This invokes the find-procedure-type popup.
-var ptvarname;
-function sel_proc_type(varname) {
+// formseq = 0-relative index in the form.
+var gbl_formseq;
+function sel_proc_type(formseq) {
  var f = document.forms[0];
- if (typeof varname == 'undefined') varname = 'form_proc_type';
- ptvarname = varname;
- dlgopen('../../orders/types.php?popup=1&order=' + f[ptvarname].value, '_blank', 800, 500);
+ // if (!f.form_lab_id.value) {
+ //  alert('<?php echo addslashes(xl('Please select a procedure provider')); ?>');
+ //  return;
+ // }
+ gbl_formseq = formseq;
+ var ptvarname = 'form_proc_type[' + formseq + ']';
+ dlgopen('../../orders/types.php?popup=1' +
+  '&labid=' + f.form_lab_id.value +
+  '&order=' + f[ptvarname].value +
+  '&formid=<?php echo $formid; ?>' +
+  '&formseq=' + formseq,
+  '_blank', 800, 500);
 }
 
 // This is for callback by the find-procedure-type popup.
 // Sets both the selected type ID and its descriptive name.
 function set_proc_type(typeid, typename) {
  var f = document.forms[0];
+ var ptvarname = 'form_proc_type[' + gbl_formseq + ']';
+ var ptdescname = 'form_proc_type_desc[' + gbl_formseq + ']';
  f[ptvarname].value = typeid;
- f[ptvarname + '_desc'].value = typename;
+ f[ptdescname].value = typename;
+}
+
+// This is also for callback by the find-procedure-type popup.
+// Sets the contents of the table containing the form fields for questions.
+function set_proc_html(s, js) {
+ document.getElementById('qoetable[' + gbl_formseq + ']').innerHTML = s;
+ eval(js);
+}
+
+// New lab selected so clear all procedures and questions from the form.
+function lab_id_changed() {
+ var f = document.forms[0];
+ for (var i = 0; true; ++i) {
+  var ix = '[' + i + ']';
+  if (!f['form_proc_type' + ix]) break;
+  f['form_proc_type' + ix].value = '-1';
+  f['form_proc_type_desc' + ix].value = '';
+  document.getElementById('qoetable' + ix).innerHTML = '';
+ }
+}
+
+// Add a line for entry of another procedure.
+function addProcLine() {
+ var f = document.forms[0];
+ var table = document.getElementById('proctable');
+ // Compute i = next procedure index.
+ var i = 0;
+ for (; f['form_proc_type[' + i + ']']; ++i);
+ var row = table.insertRow(table.rows.length);
+ var cell = row.insertCell(0);
+ cell.vAlign = 'top';
+ cell.innerHTML = "<b><?php echo addslashes(xl('Procedure')) ?> " + (i + 1) + ":</b>";
+ var cell = row.insertCell(1);
+ cell.vAlign = 'top';
+ cell.innerHTML =
+  "<input type='text' size='50' name='form_proc_type_desc[" + i + "]'" +
+  " onclick='sel_proc_type(" + i + ")'" +
+  " onfocus='this.blur()'" +
+  " title='<?php echo xla('Click to select the desired procedure'); ?>'" +
+  "  style='width:100%;cursor:pointer;cursor:hand' readonly />" +
+  " <input type='hidden' name='form_proc_type[" + i + "]' value='-1' />" +
+  " <div style='width:95%;' id='qoetable[" + i + "]'></div>";
+ sel_proc_type(i);
+ return false;
+}
+
+// The name of the form field for find-code popup results.
+var rcvarname;
+
+// This is for callback by the find-code popup.
+// Appends to or erases the current list of related codes.
+function set_related(codetype, code, selector, codedesc) {
+ var f = document.forms[0];
+ var s = f[rcvarname].value;
+ if (code) {
+  if (s.length > 0) s += ';';
+  s += codetype + ':' + code;
+ } else {
+  s = '';
+ }
+ f[rcvarname].value = s;
+}
+
+// This invokes the find-code popup.
+function sel_related(varname) {
+ rcvarname = varname;
+ // codetype is just to make things easier and avoid mistakes.
+ // Might be nice to have a lab parameter for acceptable code types.
+ // Also note the controlling script here runs from interface/patient_file/encounter/.
+ dlgopen('find_code_popup.php?codetype=ICD9', '_blank', 500, 400);
 }
 
 </script>
@@ -159,42 +332,38 @@ function set_proc_type(typeid, typename) {
 <center>
 
 <p>
-<table border='1' width='95%'>
+<table border='1' width='95%' id='proctable'>
 
+ <tr>
+  <td width='1%' valign='top' nowrap><b><?php xl('Ordering Provider','e'); ?>:</b></td>
+  <td valign='top'>
 <?php
-$ptid = -1; // -1 means no order is selected yet
-$ptrow = array('name' => '');
-if (!empty($row['procedure_type_id'])) {
-  $ptid = $row['procedure_type_id'];
-  $ptrow = sqlQuery("SELECT name FROM procedure_type WHERE " .
-    "procedure_type_id = '$ptid'");
-}
+generate_form_field(array('data_type'=>10,'field_id'=>'provider_id'),
+  $row['provider_id']);
 ?>
- <tr>
-  <td width='1%' nowrap><b><?php xl('Order Type','e'); ?>:</b></td>
-  <td>
-   <input type='text' size='50' name='form_proc_type_desc'
-    value='<?php echo addslashes($ptrow['name']) ?>'
-    onclick='sel_proc_type()' onfocus='this.blur()'
-    title='<?php xl('Click to select the desired procedure','e'); ?>'
-    style='width:100%;cursor:pointer;cursor:hand' readonly />
-   <input type='hidden' name='form_proc_type' value='<?php echo $ptid ?>' />
   </td>
  </tr>
 
  <tr>
-  <td width='1%' nowrap><b><?php xl('Ordering Provider','e'); ?>:</b></td>
-  <td>
-<?php
-generate_form_field(array('data_type'=>10,'field_id'=>'provider_id'),
-  $row['provider_id']);
+  <td width='1%' valign='top' nowrap><b><?php xl('Sending To','e'); ?>:</b></td>
+  <td valign='top'>
+   <select name='form_lab_id' onchange='lab_id_changed()'>
+ <?php
+  $ppres = sqlStatement("SELECT ppid, name FROM procedure_providers " .
+    "ORDER BY name, ppid");
+  while ($pprow = sqlFetchArray($ppres)) {
+    echo "<option value='" . attr($pprow['ppid']) . "'";
+    if ($pprow['ppid'] == $row['lab_id']) echo " selected";
+    echo ">" . text($pprow['name']) . "</option>";
+  }
 ?>
+   </select>
   </td>
  </tr>
 
  <tr>
-  <td width='1%' nowrap><b><?php xl('Date Ordered','e'); ?>:</b></td>
-  <td>
+  <td width='1%' valign='top' nowrap><b><?php xl('Order Date','e'); ?>:</b></td>
+  <td valign='top'>
 <?php
     echo "<input type='text' size='10' name='form_date_ordered' id='form_date_ordered'" .
       " value='" . $row['date_ordered'] . "'" .
@@ -209,11 +378,11 @@ generate_form_field(array('data_type'=>10,'field_id'=>'provider_id'),
  </tr>
 
  <tr>
-  <td width='1%' nowrap><b><?php xl('Internal Time Collected','e'); ?>:</b></td>
-  <td>
+  <td width='1%' valign='top' nowrap><b><?php xl('Internal Time Collected','e'); ?>:</b></td>
+  <td valign='top'>
 <?php
     echo "<input type='text' size='16' name='form_date_collected' id='form_date_collected'" .
-      " value='" . $row['date_collected'] . "'" .
+      " value='" . substr($row['date_collected'], 0, 16) . "'" .
       " title='" . xl('Date and time that the sample was collected') . "'" .
       // " onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)'" .
       " />" .
@@ -225,8 +394,8 @@ generate_form_field(array('data_type'=>10,'field_id'=>'provider_id'),
  </tr>
 
  <tr>
-  <td width='1%' nowrap><b><?php xl('Priority','e'); ?>:</b></td>
-  <td>
+  <td width='1%' valign='top' nowrap><b><?php xl('Priority','e'); ?>:</b></td>
+  <td valign='top'>
 <?php
 generate_form_field(array('data_type'=>1,'field_id'=>'order_priority',
   'list_id'=>'ord_priority'), $row['order_priority']);
@@ -235,8 +404,8 @@ generate_form_field(array('data_type'=>1,'field_id'=>'order_priority',
  </tr>
 
  <tr>
-  <td width='1%' nowrap><b><?php xl('Status','e'); ?>:</b></td>
-  <td>
+  <td width='1%' valign='top' nowrap><b><?php xl('Status','e'); ?>:</b></td>
+  <td valign='top'>
 <?php
 generate_form_field(array('data_type'=>1,'field_id'=>'order_status',
   'list_id'=>'ord_status'), $row['order_status']);
@@ -245,19 +414,103 @@ generate_form_field(array('data_type'=>1,'field_id'=>'order_status',
  </tr>
 
  <tr>
-  <td width='1%' nowrap><b><?php xl('Patient Instructions','e'); ?>:</b></td>
-  <td>
+  <td width='1%' valign='top' nowrap><b><?php xl('Diagnoses','e'); ?>:</b></td>
+  <td valign='top'>
+   <input type='text' size='50' name='form_diagnoses'
+    value='<?php echo $row['diagnoses'] ?>' onclick='sel_related(this.name)'
+    title='<?php echo xla('Click to add a diagnosis'); ?>'
+    onfocus='this.blur()'
+    style='width:100%;cursor:pointer;cursor:hand' readonly />
+  </td>
+ </tr>
+
+ <tr>
+  <td width='1%' valign='top' nowrap><b><?php xl('Patient Instructions','e'); ?>:</b></td>
+  <td valign='top'>
    <textarea rows='3' cols='40' name='form_patient_instructions' style='width:100%'
     wrap='virtual' class='inputtext' /><?php echo $row['patient_instructions'] ?></textarea>
   </td>
  </tr>
 
+<?php
+
+  // This section merits some explanation. :)
+  //
+  // If any procedures have already been saved for this form, then a top-level table row is
+  // created for each of them, and includes the relevant questions and any existing answers.
+  // Otherwise a single empty table row is created for entering the first or only procedure.
+  //
+  // If a new procedure is selected or changed, the questions for it are (re)generated from
+  // the dialog window from which the procedure is selected, via JavaScript.  The sel_proc_type
+  // function and the types.php script that it invokes collaborate to support this feature.
+  //
+  // The generate_qoe_html function in qoe.inc.php contains logic to generate the HTML for
+  // the questions, and can be invoked either from this script or from types.php.
+  //
+  // The $i counter that you see below is to resolve the need for unique names for form fields
+  // that may occur for each of the multiple procedure requests within the same order.
+  // procedure_order_seq serves a similar need for uniqueness at the database level.
+
+  $oparr = array();
+  if ($formid) {
+    $opres = sqlStatement("SELECT " .
+      "pc.procedure_order_seq, pc.procedure_code, pc.procedure_name, " .
+      "pt.procedure_type_id " .
+      "FROM procedure_order_code AS pc " .
+      "LEFT JOIN procedure_type AS pt ON pt.lab_id = ? AND " .
+      "pt.procedure_code = pc.procedure_code " .
+      "WHERE pc.procedure_order_id = ? " .
+      "ORDER BY pc.procedure_order_seq",
+      array($row['lab_id'], $formid));
+    while ($oprow = sqlFetchArray($opres)) {
+      $oparr[] = $oprow;
+    }
+  }
+  if (empty($oparr)) $oparr[] = array('procedure_name' => '');
+
+  $i = 0;
+  foreach ($oparr as $oprow) {
+    $ptid = -1; // -1 means no procedure is selected yet
+    if (!empty($oprow['procedure_type_id'])) {
+      $ptid = $oprow['procedure_type_id'];
+    }
+?>
+ <tr>
+  <td width='1%' valign='top'><b><?php echo xl('Procedure') . ' ' . ($i + 1); ?>:</b></td>
+  <td valign='top'>
+   <input type='text' size='50' name='form_proc_type_desc[<?php echo $i; ?>]'
+    value='<?php echo addslashes($oprow['procedure_name']) ?>'
+    onclick="sel_proc_type(<?php echo $i; ?>)"
+    onfocus='this.blur()'
+    title='<?php xl('Click to select the desired procedure','e'); ?>'
+    style='width:100%;cursor:pointer;cursor:hand' readonly />
+   <input type='hidden' name='form_proc_type[<?php echo $i; ?>]' value='<?php echo $ptid ?>' />
+   <!-- MSIE innerHTML property for a TABLE element is read-only, so using a DIV here. -->
+   <div style='width:95%;' id='qoetable[<?php echo $i; ?>]'>
+<?php
+$qoe_init_javascript = '';
+echo generate_qoe_html($ptid, $formid, $oprow['procedure_order_seq'], $i);
+if ($qoe_init_javascript)
+  echo "<script language='JavaScript'>$qoe_init_javascript</script>";
+?>
+   </div>
+  </td>
+ </tr>
+<?php
+    ++$i;
+  }
+?>
+
 </table>
 
 <p>
-<input type='submit' name='bn_save' value='<?php xl('Save','e'); ?>' />
+<input type='button' value='<?php echo xla('Add Procedure'); ?>' onclick="addProcLine()" />
+&nbsp;
+<input type='submit' name='bn_save' value='<?php echo xla('Save'); ?>' />
+&nbsp;
+<input type='submit' name='bn_xmit' value='<?php echo xla('Save and Transmit'); ?>' />
 &nbsp;
-<input type='button' value='<?php xl('Cancel','e'); ?>' onclick="top.restoreSession();location='<?php echo $GLOBALS['form_exit_url']; ?>'" />
+<input type='button' value='<?php echo xla('Cancel'); ?>' onclick="top.restoreSession();location='<?php echo $GLOBALS['form_exit_url']; ?>'" />
 </p>
 
 </center>
dissimilarity index 75%
index c4c625f..9808d7e 100644 (file)
@@ -1,51 +1,58 @@
-<?php
-// Copyright (C) 2010 Rod Roark <rod@sunsetsystems.com>
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-
-include_once("../../globals.php");
-include_once($GLOBALS["srcdir"] . "/api.inc");
-include_once($GLOBALS["srcdir"] . "/options.inc.php");
-
-function procedure_order_report($pid, $encounter, $cols, $id) {
- $cols = 1; // force always 1 column
- $count = 0;
- $data = sqlQuery("SELECT pt.name as Procedure_Order,po.* " .
-  "FROM procedure_order as po, procedure_type as pt WHERE " .
-  "po.procedure_type_id = pt.procedure_type_id AND po.procedure_order_id = '$id' AND po.activity = '1'");
- if ($data) {
-  print "<table cellpadding='0' cellspacing='0'>\n<tr>\n";
-  foreach($data as $key => $value) {
-   if ($key == "procedure_order_id" || $key == "pid" || $key == "user" || $key == "groupname" ||
-       $key == "authorized" || $key == "activity" || $key == "date" ||
-       $value == "" || $value == "0" || $value == "0.00") {
-    continue;
-   }
-
-   $key=ucwords(str_replace("_"," ",$key));
-   if ($key == "Order Priority") {
-    print "<td valign='top'><span class='bold'>" . xl($key). ": </span><span class='text'>" .
-     generate_display_field(array('data_type'=>'1','list_id'=>'ord_priority'),$value) .
-     " &nbsp;</span></td>\n";
-   }
-   else if ($key == "Order Status") {
-    print "<td valign='top'><span class='bold'>" . xl($key). ": </span><span class='text'>" .
-     generate_display_field(array('data_type'=>'1','list_id'=>'ord_status'),$value) .
-     " &nbsp;</span></td>\n";
-   }
-   else {
-    print "<td valign='top'><span class='bold'>" . xl($key). ": </span><span class='text'>$value &nbsp;</span></td>\n";   
-   }
-   $count++;
-   if ($count == $cols) {
-    $count = 0;
-    print "</tr>\n<tr>\n";
-   }
-  }
-  print "</tr>\n</table>\n";
- }
-}
-?>
+<?php
+// Copyright (C) 2010-2013 Rod Roark <rod@sunsetsystems.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+
+include_once("../../globals.php");
+include_once($GLOBALS["srcdir"] . "/api.inc");
+include_once($GLOBALS["srcdir"] . "/options.inc.php");
+
+function procedure_order_report($pid, $encounter, $cols, $id) {
+  $data = sqlQuery("SELECT " .
+    "po.procedure_order_id, po.date_ordered, po.diagnoses, " .
+    "po.order_status, po.specimen_type, " .
+    "pp.name AS labname, pr.procedure_report_id, " .
+    "u.lname AS ulname, u.fname AS ufname, u.mname AS umname " .
+    "FROM procedure_order AS po " .
+    "LEFT JOIN procedure_providers AS pp ON pp.ppid = po.lab_id " .
+    "LEFT JOIN users AS u ON u.id = po.provider_id " .
+    "LEFT JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id " .
+    "WHERE po.procedure_order_id = ? " .
+    "ORDER BY pr.procedure_report_id LIMIT 1",
+    array($id));
+
+  if ($data) {
+    echo "<table cellpadding='2' cellspacing='0'>\n";
+    echo " <tr>\n";
+    echo "  <td class='bold'>" . xlt('Order ID') . ": </td>\n";
+    echo "  <td class='text'>" . xlt($data['procedure_order_id']) . "</td>\n";
+    echo " </tr>\n";
+    echo " <tr>\n";
+    echo "  <td class='bold'>" . xlt('Order Date') . ": </td>\n";
+    echo "  <td class='text'>" . xlt(oeFormatShortDate($data['date_ordered'])) . "</td>\n";
+    echo " </tr>\n";
+    echo " <tr>\n";
+    echo "  <td class='bold'>" . xlt('Ordered By') . ": </td>\n";
+    echo "  <td class='text'>" . xlt($data['ulname'] . ', ' . $data['ufname'] . ' ' . $data['umname']) . "</td>\n";
+    echo " </tr>\n";
+    echo " <tr>\n";
+    echo "  <td class='bold'>" . xlt('Lab') . ": </td>\n";
+    echo "  <td class='text'>" . xlt($data['labname']) . "</td>\n";
+    echo " </tr>\n";
+    if (!empty($data['procedure_report_id'])) {
+      echo " <tr>\n";
+      echo "  <td>&nbsp;</td>\n";
+      echo "  <td class='bold'><a href='#' onclick=\"" .
+        "top.restoreSession();" .
+        "window.open('" . $GLOBALS['web_root'] . "/interface/orders/single_order_results.php?orderid=" . $id . "');" .
+        "return false;" .
+        "\">[" . xlt('View Results') . "]</a></td>\n";
+      echo " </tr>\n";
+    }
+    echo "</table>\n";
+  }
+}
+?>
index 8703d08..897ea80 100644 (file)
   'ono' => array(xl('Ofc Notes') , 0, 'main/onotes/office_comments.php'),
   'fax' => array(xl('Fax/Scan')  , 0, 'fax/faxq.php'),
   'adb' => array(xl('Addr Bk')   , 0, 'usergroup/addrbook_list.php'),
+  'orl' => array(xl('Proc Prov') , 0, 'orders/procedure_provider_list.php'),
   'ort' => array(xl('Proc Cat')  , 0, 'orders/types.php'),
+  'orc' => array(xl('Proc Load') , 0, 'orders/load_compendium.php'),
   'orb' => array(xl('Proc Bat')  , 0, 'orders/orders_results.php?batch=1'),
+  'ore' => array(xl('E-Reports') , 0, 'orders/list_reports.php'),
   'cht' => array(xl('Chart Trk') , 0, '../custom/chart_tracker.php'),
   'imp' => array(xl('Import')    , 0, '../custom/import.php'),
   'bil' => array(xl('Billing')   , 0, 'billing/billing_report.php'),
@@ -1210,10 +1213,13 @@ if (!empty($reg)) {
 <?php } ?>
   <li><a class="collapsed" id="proimg" ><span><?php xl('Procedures','e') ?></span></a>
     <ul>
+      <?php genTreeLink('RTop','orl',xl('Providers')); ?>
       <?php genTreeLink('RTop','ort',xl('Configuration')); ?>
+      <?php genTreeLink('RTop','orc',xl('Load Compendium')); ?>
       <?php genTreeLink('RTop','orp',xl('Pending Review')); ?>
       <?php genTreeLink('RTop','orr',xl('Patient Results')); ?>
       <?php genTreeLink('RTop','orb',xl('Batch Results')); ?>
+      <?php genTreeLink('RTop','ore',xl('Electronic Reports')); ?>
     </ul>
   </li>
   <?php
diff --git a/interface/orders/gen_hl7_order.inc.php b/interface/orders/gen_hl7_order.inc.php
new file mode 100644 (file)
index 0000000..56e1243
--- /dev/null
@@ -0,0 +1,456 @@
+<?php
+/**
+* Functions to support HL7 order generation.
+*
+* Copyright (C) 2012 Rod Roark <rod@sunsetsystems.com>
+*
+* LICENSE: This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://opensource.org/licenses/gpl-license.php>.
+*
+* @package   OpenEMR
+* @author    Rod Roark <rod@sunsetsystems.com>
+*/
+
+/*
+* A bit of documentation that will need to go into the manual:
+*
+* The lab may want a list of your insurances for mapping into their system.
+* To produce it, go into phpmyadmin and run this query:
+*
+* SELECT i.id, i.name, a.line1, a.line2, a.city, a.state, a.zip, p.area_code,
+* p.prefix, p.number FROM insurance_companies AS i
+* LEFT JOIN addresses AS a ON a.foreign_id = i.id
+* LEFT JOIN phone_numbers AS p ON p.type = 2 AND p.foreign_id = i.id
+* ORDER BY i.name, i.id;
+*
+* Then export as a CSV file and read it into your favorite spreadsheet app.
+*/
+
+require_once("$srcdir/classes/Address.class.php");
+require_once("$srcdir/classes/InsuranceCompany.class.php");
+
+function hl7Text($s) {
+  // See http://www.interfaceware.com/hl7_escape_protocol.html:
+  $s = str_replace('\\', '\\E\\'  , $s);
+  $s = str_replace('^' , '\\S\\'  , $s);
+  $s = str_replace('|' , '\\F\\'  , $s);
+  $s = str_replace('~' , '\\R\\'  , $s);
+  $s = str_replace('&' , '\\T\\'  , $s);
+  $s = str_replace("\r", '\\X0d\\', $s);
+  return $s;
+}
+
+function hl7Zip($s) {
+  return hl7Text(preg_replace('/[-\s]*/','',$s));
+}
+
+function hl7Date($s) {
+  return preg_replace('/[^\d]/','',$s);
+}
+
+function hl7Time($s) {
+  if (empty($s)) return '';
+  return date('YmdHis', strtotime($s));
+}
+
+function hl7Sex($s) {
+  $s = strtoupper(substr($s, 0, 1));
+  if ($s !== 'M' && $s !== 'F') $s = 'U';
+  return $s;
+}
+
+function hl7Phone($s) {
+  if (preg_match("/([2-9]\d\d)\D*(\d\d\d)\D*(\d\d\d\d)\D*$/", $s, $tmp)) {
+    return '(' . $tmp[1] . ')' . $tmp[2] . '-' . $tmp[3];
+  }
+  if (preg_match("/(\d\d\d)\D*(\d\d\d\d)\D*$/", $s, $tmp)) {
+    return $tmp[1] . '-' . $tmp[2];
+  }
+  return '';
+}
+
+function hl7SSN($s) {
+  if (preg_match("/(\d\d\d)\D*(\d\d)\D*(\d\d\d\d)\D*$/", $s, $tmp)) {
+    return $tmp[1] . '-' . $tmp[2] . '-' . $tmp[3];
+  }
+  return '';
+}
+
+function hl7Priority($s) {
+  return strtoupper(substr($s, 0, 1)) == 'H' ? 'S' : 'R';
+}
+
+function hl7Relation($s) {
+  $tmp = strtolower($s);
+  if ($tmp == 'self' || $tmp == '') return 'self';
+  else if ($tmp == 'spouse') return 'spouse';
+  else if ($tmp == 'child' ) return 'child';
+  else if ($tmp == 'other' ) return 'other';
+  // Should not get here so this will probably get noticed if we do.
+  return $s;
+}
+
+/**
+ * Get array of insurance payers for the specified patient as of the specified
+ * date. If no date is passed then the current date is used.
+ *
+ * @param  integer $pid             Patient ID.
+ * @param  date    $encounter_date  YYYY-MM-DD date.
+ * @return array   Array containing an array of data for each payer.
+ */
+function loadPayerInfo($pid, $date='') {
+  if (empty($date)) $date = date('Y-m-d');
+  $payers = array();
+  $dres = sqlStatement("SELECT * FROM insurance_data WHERE " .
+    "pid = ? AND date <= ? ORDER BY type ASC, date DESC",
+    array($pid, $date));
+  $prevtype = ''; // type is primary, secondary or tertiary
+  while ($drow = sqlFetchArray($dres)) {
+    if (strcmp($prevtype, $drow['type']) == 0) continue;
+    $prevtype = $drow['type'];
+    // Very important to check for a missing provider because
+    // that indicates no insurance as of the given date.
+    if (empty($drow['provider'])) continue;
+    $ins = count($payers);
+    $crow = sqlQuery("SELECT * FROM insurance_companies WHERE id = ?",
+      array($drow['provider']));
+    $orow = new InsuranceCompany($drow['provider']);
+    $payers[$ins] = array();
+    $payers[$ins]['data']    = $drow;
+    $payers[$ins]['company'] = $crow;
+    $payers[$ins]['object']  = $orow;
+  }
+  return $payers;
+}
+
+/**
+ * Generate HL7 for the specified procedure order.
+ *
+ * @param  integer $orderid  Procedure order ID.
+ * @param  string  &$out     Container for target HL7 text.
+ * @return string            Error text, or empty if no errors.
+ */
+function gen_hl7_order($orderid, &$out) {
+
+  // Delimiters
+  $d0 = "\r";
+  $d1 = '|';
+  $d2 = '^';
+
+  $today = time();
+  $out = '';
+
+  $porow = sqlQuery("SELECT " .
+    "po.date_collected, po.date_ordered, po.order_priority, po.diagnoses, " .
+    "pp.*, " .
+    "pd.pid, pd.pubpid, pd.fname, pd.lname, pd.mname, pd.DOB, pd.ss, " .
+    "pd.phone_home, pd.phone_biz, pd.sex, pd.street, pd.city, pd.state, pd.postal_code, " .
+    "f.encounter, u.fname AS docfname, u.lname AS doclname, u.npi AS docnpi " .
+    "FROM procedure_order AS po, procedure_providers AS pp, " .
+    "forms AS f, patient_data AS pd, users AS u " .
+    "WHERE " .
+    "po.procedure_order_id = ? AND " .
+    "pp.ppid = po.lab_id AND " .
+    "f.formdir = 'procedure_order' AND " .
+    "f.form_id = po.procedure_order_id AND " .
+    "pd.pid = f.pid AND " .
+    "u.id = po.provider_id",
+    array($orderid));
+  if (empty($porow)) return "Procedure order or lab is missing for order ID '$orderid'";
+
+  $pcres = sqlStatement("SELECT " .
+    "pc.procedure_code, pc.procedure_name, pc.procedure_order_seq " .
+    "FROM procedure_order_code AS pc " .
+    "WHERE " .
+    "pc.procedure_order_id = ? " .
+    "ORDER BY pc.procedure_order_seq",
+    array($orderid));
+
+  // Message Header
+  $out .= "MSH" .
+    $d1 . "$d2~\\&" .               // Encoding Characters (delimiters)
+    $d1 . $porow['send_app_id'] .   // Sending Application ID
+    $d1 . $porow['send_fac_id'] .   // Sending Facility ID
+    $d1 . $porow['recv_app_id'] .   // Receiving Application ID
+    $d1 . $porow['recv_fac_id'] .   // Receiving Facility ID
+    $d1 . date('YmdHis', $today) .  // Date and time of this message
+    $d1 .
+    $d1 . 'ORM' . $d2 . 'O01' .     // Message Type
+    $d1 . $orderid .                // Unique Message Number
+    $d1 . $porow['DorP'] .          // D=Debugging, P=Production
+    $d1 . '2.3' .                   // HL7 Version ID
+    $d0;
+
+  // Patient Identification
+  $out .= "PID" .
+    $d1 . "1" .                      // Set ID (always just 1 of these)
+    $d1 . $porow['pid'] .            // Patient ID (not required)
+    $d1 . $porow['pid'] .            // Patient ID (required)
+    $d1 .                            // Alternate Patient ID (not required)
+    $d1 . hl7Text($porow['lname']) .
+      $d2 . hl7Text($porow['fname']);
+  if ($porow['mname']) $out .= $d2 . hl7Text($porow['mname']);
+  $out .=
+    $d1 .
+    $d1 . hl7Date($porow['DOB']) .   // DOB
+    $d1 . hl7Sex($porow['sex'])  .   // Sex: M, F or U
+    $d1 . $d1 .
+    $d1 . hl7Text($porow['street']) .
+      $d2 .
+      $d2 . hl7Text($porow['city']) .
+      $d2 . hl7Text($porow['state']) .
+      $d2 . hl7Zip($porow['zip']) .
+    $d1 .
+    $d1 . hl7Phone($porow['phone_home']) .
+    $d1 . hl7Phone($porow['phone_biz']) .
+    $d1 . $d1 . $d1 .
+    $d1 . $porow['encounter'] .
+    $d1 . hl7SSN($porow['ss']) .
+    $d1 . $d1 . $d1 .
+    $d0;
+
+  // NTE segment(s) omitted.
+
+  // Patient Visit.
+  $out .= "PV1" .
+    $d1 . "1" .                           // Set ID (always just 1 of these)
+    $d1 .                                 // Patient Class (if required, O for Outpatient)
+    $d1 .                                 // Patient Location (for inpatient only?)
+    $d1 . $d1 . $d1 .
+    $d1 . hl7Text($porow['docnpi']) .     // Attending Doctor ID
+      $d2 . hl7Text($porow['doclname']) . // Last Name
+      $d2 . hl7Text($porow['docfname']) . // First Name
+    str_repeat($d1, 11) .                 // PV1 8 to 18 all empty
+    $d1 . $porow['encounter'] .           // Encounter Number
+    str_repeat($d1, 13) .                 // PV1 20 to 32 all empty
+    $d0;
+
+  // Insurance stuff.
+  $payers = loadPayerInfo($porow['pid'], $porow['date_ordered']);
+  $setid = 0;
+  foreach ($payers as $payer) {
+    $payer_object = $payer['object'];
+    $payer_address = $payer_object->get_address();
+    $out .= "IN1" .
+      $d1 . ++$setid .                                // Set ID
+      $d1 .                                           // Insurance Plan Identifier ??
+      $d1 . hl7Text($payer['company']['id']) .        // Insurance Company ID
+      $d1 . hl7Text($payer['company']['name'])   .    // Insurance Company Name
+      $d1 . hl7Text($payer_address->get_line1()) .    // Street Address
+        $d2 .
+        $d2 . hl7Text($payer_address->get_city()) .   // City
+        $d2 . hl7Text($payer_address->get_state()) .  // State
+        $d2 . hl7Zip($payer_address->get_zip()) .     // Zip Code
+      $d1 .
+      $d1 . hl7Phone($payer_object->get_phone()) .    // Phone Number
+      $d1 . hl7Text($payer['data']['group_number']) . // Insurance Company Group Number
+      str_repeat($d1, 7) .                            // IN1 9-15 all empty
+      $d1 . hl7Text($payer['data']['subscriber_lname']) .   // Insured last name
+        $d2 . hl7Text($payer['data']['subscriber_fname']) . // Insured first name
+        $d2 . hl7Text($payer['data']['subscriber_mname']) . // Insured middle name
+      $d1 . hl7Relation($payer['data']['subscriber_relationship']) .
+      $d1 . hl7Date($payer['data']['subscriber_DOB']) .     // Insured DOB
+      $d1 . hl7Date($payer['data']['subscriber_street']) .  // Insured Street Address
+        $d2 .
+        $d2 . hl7Text($payer['data']['subscriber_city']) .  // City
+        $d2 . hl7Text($payer['data']['subscriber_state']) . // State
+        $d2 . hl7Zip($payer['data']['subscriber_postal_code']) . // Zip
+      $d1 .
+      $d1 .
+      $d1 . $setid .                                  // 1=Primary, 2=Secondary, 3=Tertiary
+      str_repeat($d1, 13) .                           // IN1-23 to 35 all empty
+      $d1 . hl7Text($payer['data']['policy_number']) . // Policy Number
+      str_repeat($d1, 12) .                           // IN1-37 to 48 all empty
+      $d0;
+
+    // IN2 segment omitted.
+  }
+
+  // Guarantor. OpenEMR doesn't have these so use the patient.
+  $out .= "GT1" .
+    $d1 . "1" .                      // Set ID (always just 1 of these)
+    $d1 .
+    $d1 . hl7Text($porow['lname']) .
+      $d2 . hl7Text($porow['fname']);
+  if ($porow['mname']) $out .= $d2 . hl7Text($porow['mname']);
+  $out .=
+    $d1 .
+    $d1 . hl7Text($porow['street']) .
+      $d2 .
+      $d2 . hl7Text($porow['city']) .
+      $d2 . hl7Text($porow['state']) .
+      $d2 . hl7Zip($porow['zip']) .
+    $d1 . hl7Phone($porow['phone_home']) .
+    $d1 . hl7Phone($porow['phone_biz']) .
+    $d1 . hl7Date($porow['DOB']) .   // DOB
+    $d1 . hl7Sex($porow['sex'])  .   // Sex: M, F or U
+    $d1 .
+    $d1 . 'self' .                   // Relationship
+    $d1 . hl7SSN($porow['ss']) .
+    $d0;
+
+  // Common Order.
+  $out .= "ORC" .
+    $d1 . "NW" .                     // New Order
+    $d1 . $orderid .                 // Placer Order Number
+    str_repeat($d1, 6) .             // ORC 3-8 not used
+    $d1 . date('YmdHis') .           // Transaction date/time
+    $d1 . $d1 .
+    $d1 . hl7Text($porow['docnpi']) .     // Ordering Provider
+      $d2 . hl7Text($porow['doclname']) . // Last Name
+      $d2 . hl7Text($porow['docfname']) . // First Name
+    str_repeat($d1, 7) .             // ORC 13-19 not used
+    $d1 . "2" .                      // ABN Status: 2 = Notified & Signed, 4 = Unsigned
+    $d0;
+
+  $setid = 0;
+  while ($pcrow = sqlFetchArray($pcres)) {
+    // Observation Request.
+    $out .= "OBR" .
+      $d1 . ++$setid .                              // Set ID
+      $d1 . $orderid .                              // Placer Order Number
+      $d1 .
+      $d1 . hl7Text($pcrow['procedure_code']) .
+        $d2 . hl7Text($pcrow['procedure_name']) .
+      $d1 . hl7Priority($porow['order_priority']) . // S=Stat, R=Routine
+      $d1 .
+      $d1 . hl7Time($porow['date_collected']) .     // Observation Date/Time
+      str_repeat($d1, 8) .                          // OBR 8-15 not used
+      $d1 . hl7Text($porow['docnpi']) .             // Physician ID
+        $d2 . hl7Text($porow['doclname']) .         // Last Name
+        $d2 . hl7Text($porow['docfname']) .         // First Name
+      $d1 .
+      $d1 . (count($payers) ? 'I' : 'P') .          // I=Insurance, C=Client, P=Self Pay
+      str_repeat($d1, 8) .                          // OBR 19-26 not used
+      $d1 . '0' .                                   // ?
+      $d0;
+
+    // Diagnoses.  Currently hard-coded for ICD9 and we'll surely want to make
+    // this more flexible.
+    $setid2 = 0;
+    if (!empty($porow['diagnoses'])) {
+      $relcodes = explode(';', $porow['diagnoses']);
+      foreach ($relcodes as $codestring) {
+        if ($codestring === '') continue;
+        list($codetype, $code) = explode(':', $codestring);
+        if ($codetype !== 'ICD9') continue;
+        $dcrow = sqlQuery("SELECT c.code_text FROM " .
+          "code_types AS ct, codes AS c WHERE " .
+          "ct.ct_key = ? AND c.code_type = ct.ct_id AND " .
+          "c.code = ? " .
+          "ORDER BY c.id LIMIT 1",
+          array($codetype, $code));
+        $desc = empty($dcrow['code_text']) ? '' : $dcrow['code_text'];
+        $out .= "DG1" .
+          $d1 . ++$setid2 .                         // Set ID
+          $d1 .                                     // Diagnosis Coding Method
+          $d1 . $code .                             // Diagnosis Code
+          $d1 . hl7Text($desc) .                    // Diagnosis Description
+          $d0;
+      }
+    }
+
+    // Order entry questions and answers.
+    $qres = sqlStatement("SELECT " .
+      "a.question_code, a.answer, q.fldtype " .
+      "FROM procedure_answers AS a " .
+      "LEFT JOIN procedure_questions AS q ON " .
+      "q.lab_id = ? " .
+      "AND q.procedure_code = ? AND " .
+      "q.question_code = a.question_code " .
+      "WHERE " .
+      "a.procedure_order_id = ? AND " .
+      "a.procedure_order_seq = ? " .
+      "ORDER BY q.seq, a.answer_seq",
+      array($porow['ppid'], $pcrow['procedure_code'], $orderid, $pcrow['procedure_order_seq']));
+    $setid2 = 0;
+    while ($qrow = sqlFetchArray($qres)) {
+      // Formatting of these answer values may be lab-specific and we'll figure
+      // out how to deal with that as more labs are supported.
+      $answer = trim($qrow['answer']);
+      $fldtype = $qrow['fldtype'];
+      $datatype = 'ST';
+      if ($fldtype == 'N') {
+        $datatype = "NM";
+      } else if ($fldtype == 'D') {
+        $answer = hl7Date($answer);
+      } else if ($fldtype == 'G') {
+        $weeks = intval($answer / 7);
+        $days = $answer % 7;
+        $answer = $weeks . 'wks ' . $days . 'days';
+      }
+      $out .= "OBX" .
+        $d1 . ++$setid2 .                           // Set ID
+        $d1 . $datatype .                           // Structure of observation value
+        $d1 . hl7Text($qrow['question_code']) .     // Clinical question code
+        $d1 .
+        $d1 . hl7Text($answer) .                    // Clinical question answer
+        $d0;
+    }
+  }
+
+  return '';
+}
+
+/**
+ * Transmit HL7 for the specified lab.
+ *
+ * @param  integer $ppid  Procedure provider ID.
+ * @param  string  $out   The HL7 text to be sent.
+ * @return string         Error text, or empty if no errors.
+ */
+function send_hl7_order($ppid, $out) {
+  global $srcdir;
+
+  $d0 = "\r";
+
+  $pprow = sqlQuery("SELECT * FROM procedure_providers " .
+    "WHERE ppid = ?", array($ppid));
+  if (empty($pprow)) return xl('Procedure provider') . " $ppid " . xl('not found');
+
+  $protocol = $pprow['protocol'];
+  $remote_host = $pprow['remote_host'];
+
+  // Extract MSH-10 which is the message control ID.
+  $segmsh = explode(substr($out, 3, 1), substr($out, 0, strpos($out, $d0)));
+  $msgid = $segmsh[9];
+  if (empty($msgid)) return xl('Internal error: Cannot find MSH-10');
+
+  if ($protocol == 'SFTP') {
+    ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . "$srcdir/phpseclib");
+    require_once("$srcdir/phpseclib/Net/SFTP.php");
+
+    // Compute the target path/file name.
+    $filename = $msgid . '.txt';
+    if ($pprow['orders_path']) $filename = $pprow['orders_path'] . '/' . $filename;
+
+    // Connect to the server and write the file.
+    $sftp = new Net_SFTP($remote_host);
+    if (!$sftp->login($pprow['login'], $pprow['password'])) {
+      return xl('Login to remote host') . " '$remote_host' " . xl('failed');
+    }
+    if (!$sftp->put($filename, $out)) {
+      return xl('Creating file') . " '$filename' " . xl('on remote host failed');
+    }
+  }
+
+  // TBD: Insert "else if ($protocol == '???') {...}" to support other protocols.
+
+  else {
+    return xl('Protocol') . " '$protocol' " . xl('not implemented');
+  }
+
+  // Falling through to here indicates success.
+  newEvent("proc_order_xmit", $_SESSION['authUser'], $_SESSION['authProvider'], 1,
+    "ID: $msgid Protocol: $protocol Host: $remote_host");
+  return '';
+}
+?>
diff --git a/interface/orders/list_reports.php b/interface/orders/list_reports.php
new file mode 100644 (file)
index 0000000..1b60d44
--- /dev/null
@@ -0,0 +1,320 @@
+<?php
+/**
+* List procedure orders and reports, and fetch new reports and their results.
+*
+* Copyright (C) 2013 Rod Roark <rod@sunsetsystems.com>
+*
+* LICENSE: This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://opensource.org/licenses/gpl-license.php>.
+*
+* @package   OpenEMR
+* @author    Rod Roark <rod@sunsetsystems.com>
+*/
+
+require_once("../globals.php");
+require_once("$srcdir/acl.inc");
+require_once("$srcdir/formdata.inc.php");
+require_once("$srcdir/options.inc.php");
+require_once("$srcdir/formatting.inc.php");
+require_once("./receive_hl7_results.inc.php");
+
+function getListItem($listid, $value) {
+  $lrow = sqlQuery("SELECT title FROM list_options " .
+    "WHERE list_id = ? AND option_id = ?",
+    array($listid, $value));
+  $tmp = xl_list_label($lrow['title']);
+  if (empty($tmp)) $tmp = "($report_status)";
+  return $tmp;
+}
+
+function myCellText($s) {
+  if ($s === '') return '&nbsp;';
+  return text($s);
+}
+
+// Check authorization.
+$thisauth = acl_check('patients', 'med');
+if (!$thisauth) die(xl('Not authorized'));
+?>
+<html>
+<head>
+<?php html_header_show();?>
+
+<link rel="stylesheet" href='<?php  echo $css_header ?>' type='text/css'>
+<title><?php  xl('Procedure Orders and Reports','e'); ?></title>
+
+<style>
+
+tr.head   { font-size:10pt; background-color:#cccccc; text-align:center; }
+tr.detail { font-size:10pt; }
+a, a:visited, a:hover { color:#0000cc; }
+
+</style>
+
+<style type="text/css">@import url(<?php echo $GLOBALS['webroot'] ?>/library/dynarch_calendar.css);</style>
+<script type="text/javascript" src="<?php echo $GLOBALS['webroot'] ?>/library/dynarch_calendar.js"></script>
+<?php include_once("{$GLOBALS['srcdir']}/dynarch_calendar_en.inc.php"); ?>
+<script type="text/javascript" src="<?php echo $GLOBALS['webroot'] ?>/library/dynarch_calendar_setup.js"></script>
+
+<script type="text/javascript" src="../../library/dialog.js"></script>
+<script type="text/javascript" src="../../library/textformat.js"></script>
+
+<script language="JavaScript">
+
+var mypcc = '<?php echo $GLOBALS['phone_country_code'] ?>';
+
+function openResults(orderid) {
+ top.restoreSession();
+ window.open('single_order_results.php?orderid=' + orderid);
+}
+
+</script>
+
+</head>
+
+<body class="body_top">
+<form method='post' action='list_reports.php'
+ onsubmit='return validate(this)'>
+
+<?php
+$messages = array();
+$errmsg = poll_hl7_results($messages);
+foreach ($messages as $message) {
+  echo text($message) . "<br />\n";
+}
+if ($errmsg) {
+  echo "<font color='red'>" . text($errmsg) . "</font><br />\n";
+}
+
+$form_from_date = formData('form_from_date','P',true);
+$form_to_date   = formData('form_to_date','P',true);
+if (empty($form_to_date)) $form_to_date = $form_from_date;
+
+$form_reviewed = 0 + formData('form_reviewed');
+if (!$form_reviewed) $form_reviewed = 3;
+
+$form_patient = !empty($_POST['form_patient']);
+?>
+
+<table>
+ <tr>
+  <td class='text'>
+   &nbsp;<?php xl('From','e'); ?>:
+   <input type='text' size='10' name='form_from_date' id='form_from_date'
+    value='<?php echo $form_from_date ?>'
+    title='<?php xl('yyyy-mm-dd','e'); ?>'
+    onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)' />
+   <img src='../pic/show_calendar.gif' align='absbottom' width='24' height='22'
+    id='img_from_date' border='0' alt='[?]' style='cursor:pointer'
+    title='<?php xl('Click here to choose a date','e'); ?>' />
+
+   &nbsp;<?php xl('To','e'); ?>:
+   <input type='text' size='10' name='form_to_date' id='form_to_date'
+    value='<?php echo $form_to_date ?>'
+    title='<?php xl('yyyy-mm-dd','e'); ?>'
+    onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)' />
+   <img src='../pic/show_calendar.gif' align='absbottom' width='24' height='22'
+    id='img_to_date' border='0' alt='[?]' style='cursor:pointer'
+    title='<?php xl('Click here to choose a date','e'); ?>' />
+
+   &nbsp;
+   <select name='form_reviewed'>
+<?php
+foreach (array('1' => xlt('All'), '2' => xlt('Reviewed'), '3' => xlt('Unreviewed'),
+  '4' => xlt('Unreceived')) as $key => $value) {
+  echo "<option value='$key'";
+  if ($key == $form_reviewed) echo " selected";
+  echo ">$value</option>\n";
+}
+?>
+   </select>
+
+   &nbsp;
+   <input type='checkbox' name='form_patient' value='1'
+    <?php if ($form_patient) echo 'checked '; ?>/>Current Patient Only
+
+   &nbsp;
+   <input type='submit' name='form_refresh' value=<?php xl('Refresh','e'); ?>>
+  </td>
+ </tr>
+</table>
+
+<table width='100%' cellpadding='1' cellspacing='2'>
+
+ <tr class='head'>
+  <td colspan='2'><?php echo xlt('Patient'  ); ?></td>
+  <td colspan='2'><?php echo xlt('Order'    ); ?></td>
+  <td colspan='2'><?php echo xlt('Procedure'); ?></td>
+  <td colspan='2'><?php echo xlt('Report'   ); ?></td>
+ </tr>
+
+ <tr class='head'>
+  <td><?php echo xlt('Name'       ); ?></td>
+  <td><?php echo xlt('ID'         ); ?></td>
+  <td><?php echo xlt('Date'       ); ?></td>
+  <td><?php echo xlt('ID'         ); ?></td>
+  <td><?php echo xlt('Code'       ); ?></td>
+  <td><?php echo xlt('Description'); ?></td>
+  <td><?php echo xlt('Date'       ); ?></td>
+  <td><?php echo xlt('Status'     ); ?></td>
+  <!-- <td><?php echo xlt('Reviewed'   ); ?></td> -->
+ </tr>
+
+<?php 
+$selects =
+  "po.procedure_order_id, po.date_ordered, pc.procedure_order_seq, pc.procedure_code, " .
+  "pc.procedure_name, " .
+  "pr.procedure_report_id, pr.date_report, pr.report_status, pr.review_status";
+
+$joins =
+  "LEFT JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id AND " .
+  "pr.procedure_order_seq = pc.procedure_order_seq";
+
+$orderby =
+  "po.date_ordered, po.procedure_order_id, " .
+  "pc.procedure_order_seq, pr.procedure_report_id";
+
+$where = "1 = 1";
+if (!empty($form_from_date)) {
+  $where .= " AND po.date_ordered >= '$form_from_date'";
+}
+if (!empty($form_to_date)) {
+  $where .= " AND po.date_ordered <= '$form_to_date'";
+}
+
+if ($form_patient) {
+  $where .= " AND po.patient_id = '$pid'";
+}
+
+if ($form_reviewed == 2) {
+  $where .= " AND pr.procedure_report_id IS NOT NULL AND pr.review_status = 'reviewed'";
+}
+else if ($form_reviewed == 3) {
+  $where .= " AND pr.procedure_report_id IS NOT NULL AND pr.review_status != 'reviewed'";
+}
+else if ($form_reviewed == 4) {
+  $where .= " AND pr.procedure_report_id IS NULL";
+}
+
+$query = "SELECT po.patient_id, " .
+  "pd.fname, pd.mname, pd.lname, pd.pubpid, $selects " .
+  "FROM procedure_order AS po " .
+  "LEFT JOIN procedure_order_code AS pc ON pc.procedure_order_id = po.procedure_order_id " .
+  "LEFT JOIN patient_data AS pd ON pd.pid = po.patient_id $joins " .
+  "WHERE $where " .
+  "ORDER BY pd.lname, pd.fname, pd.mname, po.patient_id, $orderby";
+
+$res = sqlStatement($query);
+
+$lastptid = -1;
+$lastpoid = -1;
+$lastpcid = -1;
+$encount = 0;
+$lino = 0;
+$extra_html = '';
+
+while ($row = sqlFetchArray($res)) {
+  $patient_id       = empty($row['patient_id'         ]) ? 0 : ($row['patient_id'         ] + 0);
+  $order_id         = empty($row['procedure_order_id' ]) ? 0 : ($row['procedure_order_id' ] + 0);
+  $order_seq        = empty($row['procedure_order_seq']) ? 0 : ($row['procedure_order_seq'] + 0);
+  $date_ordered     = empty($row['date_ordered']) ? '' : $row['date_ordered'];
+  $procedure_code   = empty($row['procedure_code']) ? '' : $row['procedure_code'];
+  $procedure_name   = empty($row['procedure_name']) ? '' : $row['procedure_name'];
+  $report_id        = empty($row['procedure_report_id']) ? 0 : ($row['procedure_report_id'] + 0);
+  $date_report      = empty($row['date_report']) ? '' : $row['date_report'];
+  $report_status    = empty($row['report_status']) ? '' : $row['report_status']; 
+  $review_status    = empty($row['review_status']) ? '' : $row['review_status'];
+
+  $ptname = $row['lname'];
+  if ($row['fname'] || $row['mname'])
+    $ptname .= ', ' . $row['fname'] . ' ' . $row['mname'];
+
+  if ($lastpoid != $order_id || $lastpcid != $order_seq) {
+    ++$encount;
+  }
+  $bgcolor = "#" . (($encount & 1) ? "ddddff" : "ffdddd");
+
+  echo " <tr class='detail' bgcolor='$bgcolor'>\n";
+
+  // Generate patient columns.
+  if ($lastptid != $patient_id) {
+    $lastpoid = -1;
+    echo "  <td>" . text($ptname) . "</td>\n";
+    echo "  <td>" . text($row['pubpid']) . "</td>\n";
+  }
+  else {
+    echo "  <td colspan='2' style='background-color:transparent'>&nbsp;</td>";
+  }
+
+  // Generate order columns.
+  if ($lastpoid != $order_id) {
+    $lastpcid = -1;
+    echo "  <td><a href='javascript:openResults($order_id)'>";    
+    echo text($date_ordered);
+    echo "</a></td>\n";
+    echo "  <td>" . text($order_id) . "</td>\n";
+  }
+  else {
+    echo "  <td colspan='2' style='background-color:transparent'>&nbsp;</td>";
+  }
+
+  // Generate procedure columns.
+  if ($order_seq && $lastpcid != $order_seq) {
+    echo "  <td>" . text($procedure_code) . "</td>\n";
+    echo "  <td>" . text($procedure_name) . "</td>\n";
+  }
+  else {
+    echo "  <td colspan='2' style='background-color:transparent'>&nbsp;</td>";
+  }
+
+  // Generate report columns.
+  if ($report_id) {
+    echo "  <td>" . text($date_report) . "</td>\n";
+
+    // echo "  <td>" . text($report_status) . "</td>\n";
+    // echo "  <td>" . text($review_status) . "</td>\n";
+
+    echo "  <td title='" . xla('Check mark indicates reviewed') . "'>";
+    echo myCellText(getListItem('proc_rep_status', $report_status));
+    if ($review_status == 'reviewed') {
+      echo " &#x2713;"; // unicode check mark character
+    }
+    echo "</td>\n";
+
+  }
+  else {
+    echo "  <td colspan='2' style='background-color:transparent'>&nbsp;</td>";
+  }
+
+  echo " </tr>\n";
+
+  $lastptid = $patient_id;
+  $lastpoid = $order_id;
+  $lastpcid = $order_seq;
+  ++$lino;
+}
+?>
+
+</table>
+
+<script language='JavaScript'>
+
+// Initialize calendar widgets for "from" and "to" dates.
+Calendar.setup({inputField:'form_from_date', ifFormat:'%Y-%m-%d',
+ button:'img_from_date'});
+Calendar.setup({inputField:'form_to_date', ifFormat:'%Y-%m-%d',
+ button:'img_to_date'});
+
+</script>
+
+</form>
+</body>
+</html>
diff --git a/interface/orders/load_compendium.php b/interface/orders/load_compendium.php
new file mode 100644 (file)
index 0000000..a0d3dde
--- /dev/null
@@ -0,0 +1,462 @@
+<?php
+/**
+* Administrative loader for lab compendium data.
+*
+* Supports loading of lab order codes and related order entry questions from CSV
+* format into the procedure_order and procedure_questions tables, respectively.
+*
+* Copyright (C) 2012 Rod Roark <rod@sunsetsystems.com>
+*
+* LICENSE: This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://opensource.org/licenses/gpl-license.php>.
+*
+* @package   OpenEMR
+* @author    Rod Roark <rod@sunsetsystems.com>
+*/
+
+set_time_limit(0);
+
+$sanitize_all_escapes  = true;
+$fake_register_globals = false;
+
+require_once("../globals.php");
+require_once("$srcdir/acl.inc");
+
+// This array is an important reference for the supported labs and their NPI
+// numbers as known to this program.  The clinic must define at least one
+// address book entry for a lab that has a supported NPI number.
+//
+$lab_npi = array(
+  '1235186800' => 'Pathgroup Labs LLC',
+  '1598760985' => 'Yosemite Pathology Medical Group',
+);
+
+/**
+ * Get lab's ID from the users table given its NPI.  If none return 0.
+ *
+ * @param  string  $npi           The lab's NPI number as known to the system
+ * @return integer                The numeric value of the lab's address book entry
+ */
+function getLabID($npi) {
+  $lrow = sqlQuery("SELECT ppid FROM procedure_providers WHERE " .
+    "npi = ? ORDER BY ppid LIMIT 1",
+    array($npi));
+  if (empty($lrow['ppid'])) return 0;
+  return intval($lrow['ppid']);
+}
+
+if (!acl_check('admin', 'super')) die(xlt('Not authorized','','','!'));
+
+$form_step   = isset($_POST['form_step']) ? trim($_POST['form_step']) : '0';
+$form_status = isset($_POST['form_status' ]) ? trim($_POST['form_status' ]) : '';
+
+if (!empty($_POST['form_import'])) $form_step = 1;
+
+// When true the current form will submit itself after a brief pause.
+$auto_continue = false;
+
+// Set up main paths.
+$EXPORT_FILE = $GLOBALS['temporary_files_dir'] . "/openemr_config.sql";
+?>
+<html>
+
+<head>
+<link rel="stylesheet" href='<?php echo $css_header ?>' type='text/css'>
+<title><?php echo xlt('Load Lab Configuration'); ?></title>
+</head>
+
+<body class="body_top">
+<center>
+&nbsp;<br />
+<form method='post' action='load_compendium.php' enctype='multipart/form-data'>
+
+<table>
+
+<?php
+if ($form_step == 0) {
+  echo " <tr>\n";
+  echo "  <td width='1%' nowrap>" . xlt('Vendor') . "</td>\n";
+  echo "  <td><select name='vendor'>";
+  foreach ($lab_npi as $key => $value) {
+    echo "<option value='" . attr($key) . "'";
+    if (!getLabID($key)) {
+      // Entries with no matching address book entry will be disabled.
+      echo " disabled";
+    }
+    echo ">" . text($key) . ": " . text($value) . "</option>";
+  }
+  echo "</td>\n";
+  echo " </tr>\n";
+
+  echo " <tr>\n";
+  echo "  <td nowrap>" . xlt('Action') . "</td>\n";
+  echo "  <td><select name='action'>";
+  echo "<option value='1'>" . xlt('Load Order Definitions'    ) . "</option>";
+  echo "<option value='2'>" . xlt('Load Order Entry Questions') . "</option>";
+  echo "<option value='3'>" . xlt('Load OE Question Options'  ) . "</option>";
+  echo "</td>\n";
+  echo " </tr>\n";
+
+  echo " <tr>\n";
+  echo "  <td nowrap>" . xlt('Container Group Name') . "</td>\n";
+  echo "  <td><select name='group'>";
+  $gres = sqlStatement("SELECT procedure_type_id, name FROM procedure_type " .
+    "WHERE procedure_type = 'grp' ORDER BY name, procedure_type_id");
+  while ($grow = sqlFetchArray($gres)) {
+    echo "<option value='" . attr($grow['procedure_type_id']) . "'>" .
+      text($grow['name']) . "</option>";
+  }
+  echo "</td>\n";
+  echo " </tr>\n";
+
+  echo " <tr>\n";
+  echo "  <td nowrap>" . xlt('File to Upload') . "</td>\n";
+  echo "<td><input type='hidden' name='MAX_FILE_SIZE' value='4000000' />";
+  echo "<input type='file' name='userfile' /></td>\n";
+  echo " </tr>\n";
+
+  echo " <tr>\n";
+  echo "  <td nowrap>&nbsp;</td>\n";
+  echo "  <td><input type='submit' value='" . xlt('Submit') . "' /></td>\n";
+  echo " </tr>\n";
+}
+
+echo " <tr>\n";
+echo "  <td colspan='2'>\n";
+
+if ($form_step == 1) {
+  // Process uploaded config file.
+  if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {
+    $form_vendor = $_POST['vendor'];
+    $form_action = intval($_POST['action']);
+    $form_group  = intval($_POST['group']);
+    $lab_id = getLabID($form_vendor);
+
+    $form_status .= xlt('Applying') . "...<br />";
+    echo nl2br($form_status);
+
+    $fhcsv = fopen($_FILES['userfile']['tmp_name'], "r");
+
+    if ($fhcsv) {
+
+      // Vendor = Pathgroup
+      //
+      if ($form_vendor == '1235186800') {
+
+        if ($form_action == 1) { // load compendium
+          // Mark all "ord" rows having the indicated parent as inactive.
+          sqlStatement("UPDATE procedure_type SET activity = 0 WHERE " .
+            "parent = ? AND procedure_type = 'ord'",
+            array($form_group));
+
+          // What should be uploaded is the "Compendium" spreadsheet provided by
+          // PathGroup, saved in "Text CSV" format from OpenOffice, using its
+          // default settings.  Values for each row are:
+          //   0: Order Code  : mapped to procedure_code of order type
+          //   1: Order Name  : mapped to name of order type
+          //   2: Result Code : mapped to procedure_code of result type
+          //   3: Result Name : mapped to name of result type
+          //
+          while (!feof($fhcsv)) {
+            $acsv = fgetcsv($fhcsv, 4096);
+            if (count($acsv) < 4 || $acsv[0] == "Order Code") continue;
+            $standard_code = empty($acsv[2]) ? '' : ('CPT4:' . $acsv[2]);
+
+            // Update or insert the order row, if not already done.
+            $trow = sqlQuery("SELECT * FROM procedure_type WHERE " .
+              "parent = ? AND procedure_code = ? AND procedure_type = 'ord' " .
+              "ORDER BY procedure_type_id DESC LIMIT 1",
+              array($form_group, $acsv[0]));
+            if (empty($trow['procedure_type_id']) || $trow['activity'] == 0) {
+              if (empty($trow['procedure_type_id'])) {
+                $ptid = sqlInsert("INSERT INTO procedure_type SET " .
+                  "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?",
+                  array($form_group, $acsv[1], $lab_id, $acsv[0], 'ord'));
+              }
+              else {
+                $ptid = $trow['procedure_type_id'];
+                sqlStatement("UPDATE procedure_type SET " .
+                  "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?, " .
+                  "activity = 1 WHERE procedure_type_id = ?",
+                  array($form_group, $acsv[1], $lab_id, $acsv[0], 'ord', $ptid));
+              }
+              sqlStatement("UPDATE procedure_type SET activity = 0 WHERE " .
+                "parent = ? AND procedure_type = 'res'",
+                array($ptid));
+            }
+
+            // Update or insert the result row.
+            // Not sure we need this, but what the hell.
+            $trow = sqlQuery("SELECT * FROM procedure_type WHERE " .
+              "parent = ? AND procedure_code = ? AND procedure_type = 'res' " .
+              "ORDER BY procedure_type_id DESC LIMIT 1",
+              array($ptid, $acsv[2]));
+            // The following should always be true, otherwise duplicate input row.
+            if (empty($trow['procedure_type_id']) || $trow['activity'] == 0) {
+              if (empty($trow['procedure_type_id'])) {
+                sqlInsert("INSERT INTO procedure_type SET " .
+                  "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?",
+                  array($ptid, $acsv[3], $lab_id, $acsv[2], 'res'));
+              }
+              else {
+                $resid = $trow['procedure_type_id'];
+                sqlStatement("UPDATE procedure_type SET " .
+                  "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?, " .
+                  "activity = 1 WHERE procedure_type_id = ?",
+                  array($ptid, $acsv[3], $lab_id, $acsv[2], 'res', $resid));
+              }
+            } // end if
+          } // end while
+        } // end load compendium
+
+        else if ($form_action == 2) { // load questions
+          // Mark the vendor's current questions inactive.
+          sqlStatement("UPDATE procedure_questions SET activity = 0 WHERE lab_id = ?",
+            array($lab_id));
+
+          // What should be uploaded is the "AOE Questions" spreadsheet provided by
+          // PathGroup, saved in "Text CSV" format from OpenOffice, using its
+          // default settings.  Values for each row are:
+          //   0: OBRCode (order code)
+          //   1: Question Code
+          //   2: Question
+          //   3: "Tips"
+          //   4: Required (0 = No, 1 = Yes)
+          //   5: Maxchar (integer length)
+          //   6: FieldType (FT = free text, DD = dropdown, ST = string)
+          //
+          $seq = 0;
+          $last_code = '';
+          while (!feof($fhcsv)) {
+            $acsv = fgetcsv($fhcsv, 4096);
+            if (count($acsv) < 7 || $acsv[4] == "Required") continue;
+            $code = trim($acsv[0]);
+            if (empty($code)) continue;
+
+            if ($code != $last_code) {
+              $seq = 0;
+              $last_code = $code;
+            }
+            ++$seq;
+
+            $required = 0 + $acsv[4];
+            $maxsize = 0 + $acsv[5];
+            $fldtype = 'T';
+
+            // Figure out field type.
+            if ($acsv[6] == 'DD') $fldtype = 'S';
+            else if (stristr($acsv[3], 'mm/dd/yy') !== FALSE) $fldtype = 'D';
+            else if (stristr($acsv[3], 'wks_days') !== FALSE) $fldtype = 'G';
+            else if ($acsv[6] == 'FT') $fldtype = 'T';
+            else $fldtype = 'N';
+
+            $qrow = sqlQuery("SELECT * FROM procedure_questions WHERE " .
+              "lab_id = ? AND procedure_code = ? AND question_code = ?",
+              array($lab_id, $code, $acsv[1]));
+
+            if (empty($qrow['question_code'])) {
+              sqlStatement("INSERT INTO procedure_questions SET " .
+                "lab_id = ?, procedure_code = ?, question_code = ?, question_text = ?, " .
+                "required = ?, maxsize = ?, fldtype = ?, options = '', tips = ?,
+                activity = 1, seq = ?",
+                array($lab_id, $code, $acsv[1], $acsv[2], $required, $maxsize, $fldtype, $acsv[3], $seq));
+            }
+            else {
+              sqlStatement("UPDATE procedure_questions SET " .
+                "question_text = ?, required = ?, maxsize = ?, fldtype = ?, " .
+                "options = '', tips = ?, activity = 1, seq = ? WHERE " .
+                "lab_id = ? AND procedure_code = ? AND question_code = ?",
+                array($acsv[2], $required, $maxsize, $fldtype, $acsv[3], $seq,
+                  $lab_id, $code, $acsv[1]));
+            }
+
+          } // end while
+        } // end load questions
+
+        else if ($form_action == 3) { // load question options
+          // What should be uploaded is the "AOE Options" spreadsheet provided
+          // by YPMG, saved in "Text CSV" format from OpenOffice, using its
+          // default settings.  Values for each row are:
+          //   0: OBXCode (question code)
+          //   1: OBRCode (procedure code)
+          //   2: Option1 (option text)
+          //   3: Optioncode (the row is duplicated for each possible value)
+          //
+          while (!feof($fhcsv)) {
+            $acsv = fgetcsv($fhcsv, 4096);
+            if (count($acsv) < 4 || ($acsv[0] == "OBXCode")) continue;
+            $pcode   = trim($acsv[1]);
+            $qcode   = trim($acsv[0]);
+            $options = trim($acsv[2]) . ':' . trim($acsv[3]);
+            if (empty($pcode) || empty($qcode)) continue;
+            $qrow = sqlQuery("SELECT * FROM procedure_questions WHERE " .
+              "lab_id = ? AND procedure_code = ? AND question_code = ?",
+              array($lab_id, $pcode, $qcode));
+            if (empty($qrow['procedure_code'])) {
+              continue; // should not happen
+            }
+            else {
+              if ($qrow['activity'] == '1' && $qrow['options'] !== '') {
+                $options = $qrow['options'] . ';' . $options;
+              }
+              sqlStatement("UPDATE procedure_questions SET " .
+                "options = ? WHERE " .
+                "lab_id = ? AND procedure_code = ? AND question_code = ?",
+                array($options, $lab_id, $pcode, $qcode));
+            }
+          } // end while
+        } // end load questions
+      } // End Pathgroup
+
+      // Vendor = Yosemite Pathology Medical Group
+      //
+      if ($form_vendor == '1598760985') {
+        if ($form_action == 1) { // load compendium
+          // Mark all "ord" rows having the indicated parent as inactive.
+          sqlStatement("UPDATE procedure_type SET activity = 0 WHERE " .
+            "parent = ? AND procedure_type = 'ord'",
+            array($form_group));
+          // What should be uploaded is the Order Compendium spreadsheet provided
+          // by YPMG, saved in "Text CSV" format from OpenOffice, using its
+          // default settings.  Values for each row are:
+          //   0: Order code    : mapped to procedure_code
+          //   1: Order Name    : mapped to name
+          //   2: Result Code   : ignored (will cause multiple occurrences of the same order code)
+          //   3: Result Name   : ignored
+          //
+          while (!feof($fhcsv)) {
+            $acsv = fgetcsv($fhcsv, 4096);
+            $ordercode = trim($acsv[0]);
+            if (count($acsv) < 2 || $ordercode == "Order Code") continue;
+            $trow = sqlQuery("SELECT * FROM procedure_type WHERE " .
+              "parent = ? AND procedure_code = ? AND procedure_type = 'ord' " .
+              "ORDER BY procedure_type_id DESC LIMIT 1",
+              array($form_group, $ordercode));
+
+            if (empty($trow['procedure_type_id'])) {
+              sqlStatement("INSERT INTO procedure_type SET " .
+                "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?, " .
+                "activity = 1",
+                array($form_group, trim($acsv[1]), $lab_id, $ordercode, 'ord'));
+            }
+            else {
+              sqlStatement("UPDATE procedure_type SET " .
+                "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?, " .
+                "activity = 1 " .
+                "WHERE procedure_type_id = ?",
+                array($form_group, trim($acsv[1]), $lab_id, $ordercode, 'ord',
+                  $trow['procedure_type_id']));
+            }
+          }
+        }
+        else if ($form_action == 2) { // load questions
+          // Mark the vendor's current questions inactive.
+          sqlStatement("UPDATE procedure_questions SET activity = 0 WHERE lab_id = ?",
+            array($lab_id));
+
+          // What should be uploaded is the "AOE Questions" spreadsheet provided
+          // by YPMG, saved in "Text CSV" format from OpenOffice, using its
+          // default settings.  Values for each row are:
+          //   0: Order Code
+          //   1: Question Code
+          //   2: Question
+          //   3: Is Required (always "false")
+          //   4: Field Type ("Free Text", "Pre-Defined Text" or "Multiselect Pre-Defined Text")
+          //   5: Response (just one; the row is duplicated for each possible value)
+          //
+          while (!feof($fhcsv)) {
+            $acsv = fgetcsv($fhcsv, 4096);
+            if (count($acsv) < 5 || ($acsv[3] !== "false" && $acsv[3] !== "true")) continue;
+
+            $pcode   = trim($acsv[0]);
+            $qcode   = trim($acsv[1]);
+            $options = trim($acsv[5]);
+            if (empty($pcode) || empty($qcode)) continue;
+
+            $qrow = sqlQuery("SELECT * FROM procedure_questions WHERE " .
+              "lab_id = ? AND procedure_code = ? AND question_code = ?",
+              array($lab_id, $pcode, $qcode));
+
+            // If this is the first option value and it's Multiselect, then
+            // prepend '+;' to indicate start of a multi-select list.
+            if (!empty($options) &&
+                (empty($qrow['options']) || empty($qrow['activity'])) &&
+                strpos($acsv[4], 'Multiselect') !== FALSE)
+            {
+              $options = '+;' . $options;
+            }
+
+            if (empty($qrow['procedure_code'])) {
+              sqlStatement("INSERT INTO procedure_questions SET " .
+                "lab_id = ?, procedure_code = ?, question_code = ?, question_text = ?, " .
+                "options = ?, activity = 1",
+                array($lab_id, $pcode, $qcode, trim($acsv[2]), $options));
+            }
+            else {
+              if ($qrow['activity'] == '1' && $qrow['options'] !== '' && $options !== '') {
+                $options = $qrow['options'] . ';' . $options;
+              }
+              sqlStatement("UPDATE procedure_questions SET " .
+                "question_text = ?, options = ?, activity = 1 WHERE " .
+                "lab_id = ? AND procedure_code = ? AND question_code = ?",
+                array(trim($acsv[2]), $options, $lab_id, $pcode, $qcode));
+            }
+          } // end while
+        } // end load questions
+      } // End YPMG
+
+      fclose($fhcsv);
+    }
+    else {
+      echo xlt('Internal error accessing uploaded file!');
+      $form_step = -1;
+    }
+  }
+  else {
+    echo xlt('Upload failed!');
+    $form_step = -1;
+  }
+  $auto_continue = true;
+}
+
+if ($form_step == 2) {
+  $form_status .= xlt('Done') . ".";
+  echo nl2br($form_status);
+}
+
+++$form_step;
+?>
+
+  </td>
+ </tr>
+</table>
+
+<input type='hidden' name='form_step' value='<?php echo attr($form_step); ?>' />
+<input type='hidden' name='form_status' value='<?php echo $form_status; ?>' />
+
+</form>
+
+<?php
+ob_flush();
+flush();
+?>
+
+</center>
+
+<?php if ($auto_continue) { ?>
+<script language="JavaScript">
+ setTimeout("document.forms[0].submit();", 500);
+</script>
+<?php } ?>
+
+</body>
+</html>
+
index c58915b..d0836a0 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010 Rod Roark <rod@sunsetsystems.com>
+// Copyright (C) 2010-2013 Rod Roark <rod@sunsetsystems.com>
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -23,11 +23,11 @@ $form_review = empty($_GET['review']) ? 0 : 1;
 $thisauth = acl_check('patients', 'med');
 if (!$thisauth) die(xl('Not authorized'));
 
-// Check authorization for panding review.
+// Check authorization for pending review.
 $reviewauth = acl_check('patients', 'sign');
 if ($form_review and !$reviewauth and !$thisauth) die(xl('Not authorized'));
 
-// Set pid for panding review.
+// Set pid for pending review.
 if ($_GET['set_pid'] && $form_review) {
   require_once("$srcdir/pid.inc");
   require_once("$srcdir/patient.inc");
@@ -44,6 +44,11 @@ if ($_GET['set_pid'] && $form_review) {
 
 if (!$form_batch && !$pid && !$form_review) die(xl('There is no current patient'));
 
+function oresRawData($name, $index) {
+  $s = isset($_POST[$name][$index]) ? $_POST[$name][$index] : '';
+  return trim(strip_escape_custom($s));
+}
+
 function oresData($name, $index) {
   $s = isset($_POST[$name][$index]) ? $_POST[$name][$index] : '';
   return formDataCore($s, true);
@@ -56,14 +61,12 @@ function QuotedOrNull($fld) {
 
 $current_report_id = 0;
 
-if ($_POST['form_submit']) {
- if($_POST['form_line']!='') { 
- foreach ($_POST['form_line'] as $lino => $line_value) {
-    list($order_id, $restyp_id, $report_id, $result_id) = explode(':', $line_value);
+if ($_POST['form_submit'] && !empty($_POST['form_line'])) { 
+  foreach ($_POST['form_line'] as $lino => $line_value) {
+    list($order_id, $order_seq, $report_id, $result_id) = explode(':', $line_value);
 
-    // Not using xl() here because these errors are for debugging only.
+    // Not using xl() here because this is for debugging only.
     if (empty($order_id)) die("Order ID is missing from line $lino.");
-    if (empty($restyp_id)) die("Result type ID is missing from line $lino.");
 
     // If report data exists for this line, save it.
     $date_report = oresData("form_date_report", $lino);
@@ -71,6 +74,7 @@ if ($_POST['form_submit']) {
     if (!empty($date_report)) {
       $sets =
         "procedure_order_id = '$order_id', " .
+        "procedure_order_seq = '$order_seq', " .
         "date_report = '$date_report', " .
         "date_collected = " . QuotedOrNull(oresData("form_date_collected", $lino)) . ", " .
         "specimen_num = '" . oresData("form_specimen_num", $lino) . "', " .
@@ -95,14 +99,24 @@ if ($_POST['form_submit']) {
 
     // If there's a report, save corresponding results.
     if ($current_report_id) {
+      // Comments and notes will be combined into one comments field.
+      $form_comments = oresRawData("form_comments", $lino);
+      $form_comments = str_replace("\n"  ,'~' , $form_comments);
+      $form_comments = str_replace("\r"  ,''  , $form_comments);
+      $form_notes = oresRawData("form_notes", $lino);
+      if ($form_notes !== '') {
+        $form_comments .= "\n" . $form_notes;
+      }
       $sets =
         "procedure_report_id = '$current_report_id', " .
-        "procedure_type_id = '$restyp_id', " .
+        "result_code = '" . oresData("form_result_code", $lino) . "', " .
+        "result_text = '" . oresData("form_result_text", $lino) . "', " .
         "abnormal = '" . oresData("form_result_abnormal", $lino) . "', " .
         "result = '" . oresData("form_result_result", $lino) . "', " .
         "`range` = '" . oresData("form_result_range", $lino) . "', " .
+        "units = '" . oresData("form_result_units", $lino) . "', " .
         "facility = '" . oresData("form_facility", $lino) . "', " .
-        "comments = '" . oresData("form_comments", $lino) . "', " .
+        "comments = '" . add_escape_custom($form_comments) . "', " .
         "result_status = '" . oresData("form_result_status", $lino) . "'";
       if ($result_id) { // result already exists
         sqlStatement("UPDATE procedure_result SET $sets "  .
@@ -112,9 +126,7 @@ if ($_POST['form_submit']) {
         $result_id = sqlInsert("INSERT INTO procedure_result SET $sets");
       }
     }
-
   } // end foreach
- }
 }
 ?>
 <html>
@@ -265,15 +277,6 @@ function validate(f) {
   }
   var abnstat = f['form_result_abnormal['+lino+']'].selectedIndex > 0;
   if (abnstat && !prDateRequired(rlino)) return false;
-  /*******************************************************************
-  var resstat = f['form_result_status['+lino+']'].selectedIndex > 0;
-  if (resstat != abnstat) {
-   alert('<?php xl('Result status or abnormality is missing','e') ?>');
-   if (f['form_result_abnormal['+lino+']'].focus)
-    f['form_result_abnormal['+lino+']'].focus();
-   return false;
-  }
-  *******************************************************************/
  }
  top.restoreSession();
  return true;
@@ -349,90 +352,72 @@ if ($form_batch) {
  <tr class='head'>
   <td colspan='2'><?php echo $form_batch ? xl('Patient') : xl('Order'); ?></td>
   <td colspan='4'><?php xl('Report','e'); ?></td>
-  <td colspan='6'><?php xl('Results and','e'); ?> <span class='reccolor''>
+  <td colspan='7'><?php xl('Results and','e'); ?> <span class='reccolor''>
    <?php  xl('Recommendations','e'); ?></span></td>
  </tr>
 
  <tr class='head'>
   <td><?php echo $form_batch ? xl('Name') : xl('Date'); ?></td>
-  <td><?php echo $form_batch ? xl('ID') : xl('Name'); ?></td>
+  <td><?php echo $form_batch ? xl('ID') : xl('Procedure Name'); ?></td>
   <td><?php xl('Reported','e'); ?></td>
   <td><?php xl('Ext Time Collected','e'); ?></td>
   <td><?php xl('Specimen','e'); ?></td>
   <td><?php xl('Status','e'); ?></td>
-  <td><?php xl('Group','e'); ?></td>
-  <td><?php xl('Name (click for more)','e'); ?></td>
+  <td><?php xl('Code','e'); ?></td>
+  <td><?php xl('Name','e'); ?></td>
   <td><?php xl('Abn','e'); ?></td>
   <td><?php xl('Value','e'); ?></td>
   <td><?php xl('Units', 'e'); ?></td>
   <td><?php xl('Range','e'); ?></td>
+  <td><?php xl('?','e'); ?></td>
  </tr>
 
 <?php 
 $selects =
-  "po.procedure_order_id, po.date_ordered, " .
-  "po.procedure_type_id AS order_type_id, pt1.name AS procedure_name, " .
-  "ptrc.name AS result_category_name, " .
-  "pt2.procedure_type AS result_type, " .
-  "pt2.procedure_type_id AS result_type_id, pt2.name AS result_name, " .
-  "pt2.units AS result_def_units, pt2.range AS result_def_range, " .
-  "pt2.description AS result_description, lo.title AS units_name, " .
-  "pr.procedure_report_id, pr.date_report, pr.date_collected, pr.specimen_num, pr.report_status, pr.review_status, " .
-  "ps.procedure_result_id, ps.abnormal, ps.result, ps.range, ps.result_status, " .
-  "ps.facility, ps.comments, ps.units ";
-
-// This join syntax means that results must all be at the same "level".
-// Either there is one result the same as the order, or all results are
-// direct children of the order, or all results are grandchildren of the
-// order.  No other arrangements are allowed.
-//
+  "po.procedure_order_id, po.date_ordered, pc.procedure_order_seq, " .
+  "pt1.procedure_type_id AS order_type_id, pc.procedure_name, " .
+  "pr.procedure_report_id, pr.date_report, pr.date_collected, pr.specimen_num, " .
+  "pr.report_status, pr.review_status";
+
 $joins =
-  "LEFT JOIN procedure_type AS pt1 ON pt1.procedure_type_id = po.procedure_type_id " .
-  // ptrc is an optional result category just under the order type
-  "LEFT JOIN procedure_type AS ptrc ON ptrc.parent = po.procedure_type_id " .
-  "AND ptrc.procedure_type LIKE 'grp%' " .
-  // pt2 is a result or recommendation type the same as or just under the order type
-  "LEFT JOIN procedure_type AS pt2 ON " .
-  "( ( ptrc.procedure_type_id IS NULL AND ( pt2.parent = po.procedure_type_id " .
-  "OR pt2.procedure_type_id = po.procedure_type_id ) ) OR " .
-  "( ptrc.procedure_type_id IS NOT NULL AND pt2.parent = ptrc.procedure_type_id ) " .
-  ") AND ( pt2.procedure_type LIKE 'res%' OR pt2.procedure_type LIKE 'rec%' ) " .
-  //
-  "LEFT JOIN list_options AS lo ON list_id = 'proc_unit' AND option_id = pt2.units " .
-  "LEFT JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id " .
-  "LEFT JOIN procedure_result AS ps ON ps.procedure_report_id = pr.procedure_report_id " .
-  "AND ps.procedure_type_id = pt2.procedure_type_id";
+  "JOIN procedure_order_code AS pc ON pc.procedure_order_id = po.procedure_order_id " .
+  "LEFT JOIN procedure_type AS pt1 ON pt1.lab_id = po.lab_id AND pt1.procedure_code = pc.procedure_code " .
+  "LEFT JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id AND " .
+  "pr.procedure_order_seq = pc.procedure_order_seq";
 
 $orderby =
-  "po.date_ordered, po.procedure_order_id, pr.procedure_report_id, " .
-  "ptrc.seq, ptrc.name, ptrc.procedure_type_id, " .
-  "pt2.seq, pt2.name, pt2.procedure_type_id";
+  "po.date_ordered, po.procedure_order_id, " .
+  "pc.procedure_order_seq, pr.procedure_report_id";
 
 // removed by jcw -- check/submit sequece too tedious.  This is a quick fix
 //$where = empty($_POST['form_all']) ?
 //  "( pr.report_status IS NULL OR pr.report_status = '' OR pr.report_status = 'prelim' )" :
 //  "1 = 1";
 
- $where = "1 = 1";
+$where = "1 = 1";
 
 if ($form_batch) {
-  $res = sqlStatement("SELECT po.patient_id, " .
+  $query = "SELECT po.patient_id, " .
   "pd.fname, pd.mname, pd.lname, pd.pubpid, $selects " .
   "FROM procedure_order AS po " .
   "LEFT JOIN patient_data AS pd ON pd.pid = po.patient_id $joins " .
-  "WHERE po.procedure_type_id = '$form_proc_type' AND " .
+  "WHERE pt.procedure_type_id = '$form_proc_type' AND " .
   "po.date_ordered >= '$form_from_date' AND po.date_ordered <= '$form_to_date' " .
   "AND $where " .
-  "ORDER BY pd.lname, pd.fname, pd.mname, po.patient_id, $orderby");
+  "ORDER BY pd.lname, pd.fname, pd.mname, po.patient_id, $orderby";
 }
 else {
-  $res = sqlStatement("SELECT $selects " .
-  "FROM procedure_order AS po $joins " .
+  $query = "SELECT $selects " .
+  "FROM procedure_order AS po " .
+  "$joins " .
   "WHERE po.patient_id = '$pid' AND $where " .
-  "ORDER BY $orderby");
+  "ORDER BY $orderby";
 }
 
+$res = sqlStatement($query);
+
 $lastpoid = -1;
+$lastpcid = -1;
 $lastprid = -1;
 $encount = 0;
 $lino = 0;
@@ -441,40 +426,15 @@ $lastrcn = '';
 $facilities = array();
 
 while ($row = sqlFetchArray($res)) {
-  $order_id  = empty($row['procedure_order_id' ]) ? 0 : ($row['procedure_order_id' ] + 0);
-  $restyp_id = empty($row['result_type_id'])      ? 0 : ($row['result_type_id'     ] + 0);
-  $report_id = empty($row['procedure_report_id']) ? 0 : ($row['procedure_report_id'] + 0);
-  $result_id = empty($row['procedure_result_id']) ? 0 : ($row['procedure_result_id'] + 0);
-
-  /*******************************************************************
-  $result_name = '';
-  if (!empty($row['result_category_name'])) $result_name = $row['result_category_name'] . ' / ';
-  if (!empty($row['result_name'])) $result_name .= $row['result_name'];
-  *******************************************************************/
-  $result_category_name = empty($row['result_category_name']) ? '--' : $row['result_category_name'];
-  $result_name      = empty($row['result_name'     ]) ? '' : $row['result_name'];
-
-  $date_report      = empty($row['date_report'     ]) ? '' : $row['date_report'];
-  $date_collected   = empty($row['date_collected'  ]) ? '' : substr($row['date_collected'], 0, 16);
-  $specimen_num     = empty($row['specimen_num'    ]) ? '' : $row['specimen_num'];
-  $report_status    = empty($row['report_status'   ]) ? '' : $row['report_status']; 
-  $result_abnormal  = empty($row['abnormal'        ]) ? '' : $row['abnormal'];
-  $result_result    = empty($row['result'          ]) ? '' : $row['result'];
-  $result_unit      = empty($row['units'           ]) ? '' : $row['units'];
-  $facility         = empty($row['facility'        ]) ? '' : $row['facility'];
-  $comments         = empty($row['comments'        ]) ? '' : $row['comments'];
-  $result_range     = empty($row['range'           ]) ? $row['result_def_range'] : $row['range'];
-  $result_status    = empty($row['result_status'   ]) ? '' : $row['result_status'];
-  $result_def_units = empty($row['result_def_units']) ? '' : $row['result_def_units'];
-  $units_name       = empty($row['units_name'      ]) ? xl('Units not defined') : $row['units_name'];
-
-  $review_status    = empty($row['review_status'   ]) ? 'received' : $row['review_status'];
-
-  //echo $facility. "<br>";
-  if($facility <> "" && !in_array($facility, $facilities))
-  {
-      $facilities[] = $facility;
-  }
+  $order_type_id  = empty($row['order_type_id'      ]) ? 0 : ($row['order_type_id' ] + 0);
+  $order_id       = empty($row['procedure_order_id' ]) ? 0 : ($row['procedure_order_id' ] + 0);
+  $order_seq      = empty($row['procedure_order_seq']) ? 0 : ($row['procedure_order_seq'] + 0);
+  $report_id      = empty($row['procedure_report_id']) ? 0 : ($row['procedure_report_id'] + 0);
+  $date_report    = empty($row['date_report'     ]) ? '' : $row['date_report'];
+  $date_collected = empty($row['date_collected'  ]) ? '' : substr($row['date_collected'], 0, 16);
+  $specimen_num   = empty($row['specimen_num'    ]) ? '' : $row['specimen_num'];
+  $report_status  = empty($row['report_status'   ]) ? '' : $row['report_status']; 
+  $review_status  = empty($row['review_status'   ]) ? 'received' : $row['review_status'];
 
   // skip report_status = receive to make sure do not show the report before it reviewed and sign off by Physicians
   if ($form_review) {
@@ -484,192 +444,264 @@ while ($row = sqlFetchArray($res)) {
     if ($review_status == "received") continue;
   }
 
-  if ($lastpoid != $order_id) {
-    ++$encount;
-    $lastrcn = '';
-  }
-  $bgcolor = "#" . (($encount & 1) ? "ddddff" : "ffdddd");
-
-  echo " <tr class='detail' bgcolor='$bgcolor'>\n";
-
-  // If this starts a new order, display its date and procedure name,
-  // otherwise empty space.
-  //
-  if ($lastpoid != $order_id) {
-    if ($form_batch) {
-      $tmp = $row['lname'];
-      if ($row['fname'] || $row['mname'])
-        $tmp .= ', ' . $row['fname'] . ' ' . $row['mname'];
-      echo "  <td>" . htmlentities($tmp) . "</td>\n";
-      echo "  <td>" . htmlentities($row['pubpid']) . "</td>\n";
+  $selects = "pt2.procedure_type, pt2.procedure_code, pt2.units AS pt2_units, " .
+    "pt2.range AS pt2_range, pt2.procedure_type_id AS procedure_type_id, " .
+    "pt2.name AS name, pt2.description, pt2.seq AS seq, " .
+    "ps.procedure_result_id, ps.result_code AS result_code, ps.result_text, ps.abnormal, ps.result, " .
+    "ps.range, ps.result_status, ps.facility, ps.comments, ps.units, ps.comments";
+
+  // procedure_type_id for order:
+  $pt2cond = "pt2.parent = $order_type_id AND " .
+    "(pt2.procedure_type LIKE 'res%' OR pt2.procedure_type LIKE 'rec%')";
+
+  // pr.procedure_report_id or 0 if none:
+  $pscond = "ps.procedure_report_id = $report_id";
+
+  $joincond = "ps.result_code = pt2.procedure_code";
+
+  // This union emulates a full outer join. The idea is to pick up all
+  // result types defined for this order type, as well as any actual
+  // results that do not have a matching result type.
+  $query = "(SELECT $selects FROM procedure_type AS pt2 " .
+    "LEFT JOIN procedure_result AS ps ON $pscond AND $joincond " .
+    "WHERE $pt2cond" .
+    ") UNION (" .
+    "SELECT $selects FROM procedure_result AS ps " .
+    "LEFT JOIN procedure_type AS pt2 ON $pt2cond AND $joincond " .
+    "WHERE $pscond) " .
+    "ORDER BY seq, name, procedure_type_id, result_code";
+
+  $rres = sqlStatement($query);
+  while ($rrow = sqlFetchArray($rres)) {
+    $restyp_code      = empty($rrow['procedure_code'  ]) ? '' : $rrow['procedure_code'];
+    $restyp_type      = empty($rrow['procedure_type'  ]) ? '' : $rrow['procedure_type'];
+    $restyp_name      = empty($rrow['name'            ]) ? '' : $rrow['name'];
+    $restyp_units     = empty($rrow['pt2_units'       ]) ? '' : $rrow['pt2_units'];
+    $restyp_range     = empty($rrow['pt2_range'       ]) ? '' : $rrow['pt2_range'];
+
+    $result_id        = empty($rrow['procedure_result_id']) ? 0 : ($rrow['procedure_result_id'] + 0);
+    $result_code      = empty($rrow['result_code'     ]) ? $restyp_code : $rrow['result_code'];
+    $result_text      = empty($rrow['result_text'     ]) ? $restyp_name : $rrow['result_text'];
+    $result_abnormal  = empty($rrow['abnormal'        ]) ? '' : $rrow['abnormal'];
+    $result_result    = empty($rrow['result'          ]) ? '' : $rrow['result'];
+    $result_units     = empty($rrow['units'           ]) ? $restyp_units : $rrow['units'];
+    $result_facility  = empty($rrow['facility'        ]) ? '' : $rrow['facility'];
+    $result_comments  = empty($rrow['comments'        ]) ? '' : $rrow['comments'];
+    $result_range     = empty($rrow['range'           ]) ? $restyp_range : $rrow['range'];
+    $result_status    = empty($rrow['result_status'   ]) ? '' : $rrow['result_status'];
+
+    // If there is more than one line of comments, everything after that is "notes".
+    $result_notes = '';
+    $i = strpos($result_comments, "\n");
+    if ($i !== FALSE) {
+      $result_notes = trim(substr($result_comments, $i + 1));
+      $result_comments = substr($result_comments, 0, $i);
+    }
+    $result_comments = trim($result_comments);
+
+    if($result_facility <> "" && !in_array($result_facility, $facilities)) {
+      $facilities[] = $result_facility;
+    }
+
+    if ($lastpoid != $order_id || $lastpcid != $order_seq) {
+      ++$encount;
+      $lastrcn = '';
+    }
+    $bgcolor = "#" . (($encount & 1) ? "ddddff" : "ffdddd");
+
+    echo " <tr class='detail' bgcolor='$bgcolor'>\n";
+
+    // Generate first 2 columns.
+    if ($lastpoid != $order_id || $lastpcid != $order_seq) {
+      $lastprid = -1; // force report fields on first line of each procedure
+      if ($form_batch) {
+        if ($lastpoid != $order_id) {
+          $tmp = $row['lname'];
+          if ($row['fname'] || $row['mname'])
+            $tmp .= ', ' . $row['fname'] . ' ' . $row['mname'];
+          echo "  <td>" . htmlentities($tmp) . "</td>\n";
+          echo "  <td>" . htmlentities($row['pubpid']) . "</td>\n";
+        }
+        else {
+          echo "  <td colspan='2' style='background-color:transparent'>&nbsp;</td>";
+        }
+      }
+      else {
+        if ($lastpoid != $order_id) {
+          echo "  <td>" . $row['date_ordered'] . "</td>\n";
+        }
+        else {
+          echo "  <td style='background-color:transparent'>&nbsp;</td>";
+        }
+        echo "  <td>" . htmlentities($row['procedure_name']) . "</td>\n";
+      }
     }
     else {
-      echo "  <td>" . $row['date_ordered'] . "</td>\n";
-      echo "  <td>" . htmlentities($row['procedure_name']) . "</td>\n";
+      echo "  <td colspan='2' style='background-color:transparent'>&nbsp;</td>";
     }
-    $lastprid = -1; // force report fields on first line of each order
-  } else {
-    echo "  <td colspan='2' style='background-color:#94d6e7'>&nbsp;";
-  }
-  // Include a hidden form field containing all IDs for this line.
-  echo "<input type='hidden' name='form_line[$lino]' value='$order_id:$restyp_id:$report_id:$result_id' />";
-  echo "</td>\n";
-
-  // If this starts a new report or a new order, generate the report form
-  // fields.  In the case of a new order with no report yet, the fields will
-  // have their blank/default values, and form_line (above) will indicate a
-  // report ID of 0.
-  //
-  // TBD: Also generate default report fields and another set of results if
-  // the previous report is marked "Preliminary".
-  //
-  if ($report_id != $lastprid) {
+
+    // If this starts a new report or a new order, generate the report form
+    // fields.  In the case of a new order with no report yet, the fields will
+    // have their blank/default values, and form_line (above) will indicate a
+    // report ID of 0.
+    //
+    // TBD: Also generate default report fields and another set of results if
+    // the previous report is marked "Preliminary".
+    //
+    if ($report_id != $lastprid) {
+      echo "  <td nowrap>";
+      echo "<input type='text' size='8' name='form_date_report[$lino]'" .
+        " id='form_date_report[$lino]' class='celltextfw' value='" . attr($date_report) . "' " .
+        " title='" . xl('Date of this report') . "'" .
+        " onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)'" .
+        " />";
+      echo "<span class='bold' id='q_date_report[$lino]' style='cursor:pointer' " .
+        "title='" . xl('Click here to choose a date') . "' />?</span>";
+      echo "</td>\n";
+
+      echo "  <td nowrap>";
+      echo "<input type='text' size='13' name='form_date_collected[$lino]'" .
+        " id='form_date_collected[$lino]'" .
+        " class='celltextfw' value='" . attr($date_collected) . "' " .
+        " title='" . xl('Date and time of sample collection') . "'" .
+        " onkeyup='datekeyup(this,mypcc,true)' onblur='dateblur(this,mypcc,true)'" .
+        " />";
+      echo "<span class='bold' id='q_date_collected[$lino]' style='cursor:pointer' " .
+        "title='" . xl('Click here to choose a date and time') . "' />?</span>";
+      echo "</td>\n";
+
+      echo "  <td>";
+      echo "<input type='text' size='8' name='form_specimen_num[$lino]'" .
+        " class='celltext' value='" . attr($specimen_num) . "' " .
+        " title='" . xl('Specimen number/identifier') . "'" .
+        " />";
+      echo "</td>\n";
+
+      echo "  <td>";
+      echo generate_select_list("form_report_status[$lino]", 'proc_rep_status',
+        $report_status, xl('Report Status'), ' ', 'cellselect');
+      echo "</td>\n";
+    }
+    else {
+      echo "  <td colspan='4' style='background-color:transparent'>&nbsp;</td>\n";
+    }
+
     echo "  <td nowrap>";
-    echo "<input type='text' size='8' name='form_date_report[$lino]'" .
-      " id='form_date_report[$lino]' class='celltextfw' value='$date_report' " .
-      " title='" . xl('Date of this report') . "'" .
-      " onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)'" .
-      " />";
-    echo "<span class='bold' id='q_date_report[$lino]' style='cursor:pointer' " .
-      "title='" . xl('Click here to choose a date') . "' />?</span>";
+    echo "<input type='text' size='6' name='form_result_code[$lino]'" .
+      " class='celltext' value='" . attr($result_code) . "' />" .
+      "</td>\n";
+
+    echo "  <td>" .
+      "<input type='text' size='16' name='form_result_text[$lino]'" .
+      " class='celltext' value='" . attr($result_text) . "' />";
+      "</td>\n";
+
+    echo "  <td>";
+    echo generate_select_list("form_result_abnormal[$lino]", 'proc_res_abnormal',
+      $result_abnormal, xl('Indicates abnormality'), ' ', 'cellselect');
     echo "</td>\n";
 
-    echo "  <td nowrap>";
-    echo "<input type='text' size='13' name='form_date_collected[$lino]'" .
-      " id='form_date_collected[$lino]'" .
-      " class='celltextfw' value='$date_collected' " .
-      " title='" . xl('Date and time of sample collection') . "'" .
-      " onkeyup='datekeyup(this,mypcc,true)' onblur='dateblur(this,mypcc,true)'" .
-      " />";
-    echo "<span class='bold' id='q_date_collected[$lino]' style='cursor:pointer' " .
-      "title='" . xl('Click here to choose a date and time') . "' />?</span>";
+    echo "  <td>";
+    if ($result_units == 'bool') {
+      echo "&nbsp;--";
+    }
+    else {
+      echo "<input type='text' size='7' name='form_result_result[$lino]'" .
+        " class='celltext' value='" . attr($result_result) . "' " .
+        " />";
+    }
     echo "</td>\n";
 
     echo "  <td>";
-    echo "<input type='text' size='8' name='form_specimen_num[$lino]'" .
-      " class='celltext' value='$specimen_num' " .
-      " title='" . xl('Specimen number/identifier') . "'" .
+    echo "<input type='text' size='4' name='form_result_units[$lino]'" .
+      " class='celltext' value='" . attr($result_units) . "' " .
+      " title='" . xl('Units applicable to the result value') . "'" .
       " />";
     echo "</td>\n";
 
     echo "  <td>";
-    echo generate_select_list("form_report_status[$lino]", 'proc_rep_status',
-      $report_status, xl('Report Status'), ' ', 'cellselect');
+    echo "<input type='text' size='8' name='form_result_range[$lino]'" .
+      " class='celltext' value='" . attr($result_range) . "' " .
+      " title='" . xl('Reference range of results') . "'" .
+      " />";
+    // Include a hidden form field containing all IDs for this line.
+    echo "<input type='hidden' name='form_line[$lino]' " .
+      "value='$order_id:$order_seq:$report_id:$result_id' />";
     echo "</td>\n";
-  }
-  else {
-    echo "  <td colspan='4' style='background-color:#94d6e7'>&nbsp;</td>\n";
-  }
 
-  if ($result_category_name != $lastrcn) {
-    echo "  <td>";
-    echo htmlentities($result_category_name);
+    echo "  <td class='bold' style='cursor:pointer' " .
+      "onclick='extShow($lino, this)' align='center' " .
+      "title='" . xl('Click here to view/edit more details') . "'>";
+    echo "&nbsp;?&nbsp;";
     echo "</td>\n";
-    $lastrcn = $result_category_name;
-  }
-  else {
-    echo "  <td style='background-color:#94d6e7'>&nbsp;</td>\n";
-  }
 
-  echo "  <td title='" . addslashes($row['result_description']) . "'";
-  if ($row['result_type'] == 'rec') echo " class='reccolor'";
-  echo " style='cursor:pointer' onclick='extShow($lino, this)'>" .
-    htmlentities($result_name) . "</td>\n";
-
-  echo "  <td>";
-  echo generate_select_list("form_result_abnormal[$lino]", 'proc_res_abnormal',
-    $result_abnormal, xl('Indicates abnormality'), ' ', 'cellselect');
-  echo "</td>\n";
-
-  echo "  <td>";
-  if ($result_def_units == 'bool') {
-    // echo generate_select_list("form_result_result[$lino]", 'proc_res_bool',
-    //   $result_result, $units_name, ' ', 'cellselect');
-    echo "&nbsp;--";
-  }
-  else {
-    echo "<input type='text' size='4' name='form_result_result[$lino]'" .
-      " class='celltext' value='$result_result' " .
-      " title='" . addslashes($units_name) . "'" .
-      " />";
+    echo " </tr>\n";
+
+    // Create a floating div for additional attributes of this result.
+    $extra_html .= "<div id='ext_$lino' " .
+      "style='position:absolute;width:750px;border:1px solid black;" .
+      "padding:2px;background-color:#cccccc;visibility:hidden;" .
+      "z-index:1000;left:-1000px;top:0px;font-size:9pt;'>\n" .
+      "<table width='100%'>\n" .
+      "<tr><td class='bold' align='center' colspan='2' style='padding:4pt 0 4pt 0'>" .
+      htmlspecialchars($result_text) .
+      "</td></tr>\n" .
+      "<tr><td class='bold' width='1%' nowrap>" . xlt('Status') . ": </td>" .
+      "<td>" . generate_select_list("form_result_status[$lino]", 'proc_res_status',
+        $result_status, xl('Result Status'), '') . "</td></tr>\n" .
+      "<tr><td class='bold' nowrap>" . xlt('Facility') . ": </td>" .
+      "<td><input type='text' size='15' name='form_facility[$lino]'" .
+      " value='$result_facility' " .
+      " title='" . xla('Supplier facility name') . "'" .
+      " style='width:100%' /></td></tr>\n" .
+      "<tr><td class='bold' nowrap>" . xlt('Comments') . ": </td>" .
+      "<td><textarea rows='3' cols='15' name='form_comments[$lino]'" .
+      " title='" . xla('Comments for this result or recommendation') . "'" .
+      " style='width:100%' />" . htmlspecialchars($result_comments) .
+      "</textarea></td></tr>\n" .
+      "<tr><td class='bold' nowrap>" . xlt('Notes') . ": </td>" .
+      "<td><textarea rows='4' cols='15' name='form_notes[$lino]'" .
+      " title='" . xla('Additional notes for this result or recommendation') . "'" .
+      " style='width:100%' />" . htmlspecialchars($result_notes) .
+      "</textarea></td></tr>\n" .
+      "</table>\n" .
+      "<p><center><input type='button' value='" . xla('Close') . "' " .
+      "onclick='extShow($lino, false)' /></center></p>\n".
+      "</div>";
+
+    $lastpoid = $order_id;
+    $lastpcid = $order_seq;
+    $lastprid = $report_id;
+    ++$lino;
   }
-  echo "</td>\n";
-
-  echo "<td>";
-  echo $result_unit;
-  echo "</td>";
-
-
-  echo "  <td>";
-  echo "<input type='text' size='8' name='form_result_range[$lino]'" .
-    " class='celltext' value='$result_range' " .
-    " title='" . xl('Reference range of results') . "'" .
-    " />";
-  echo "</td>\n";
-
-  echo " </tr>\n";
-
-  // Create a floating div for additional attributes of this result.
-  $extra_html .= "<div id='ext_$lino' " .
-    "style='position:absolute;width:500px;border:1px solid black;" .
-    "padding:2px;background-color:#cccccc;visibility:hidden;" .
-    "z-index:1000;left:-1000px;top:0px;font-size:9pt;'>\n" .
-    "<table width='100%'>\n" .
-    "<tr><td class='bold' align='center' colspan='2' style='padding:4pt 0 4pt 0'>" .
-    // xl('Additional Attributes') .
-    htmlspecialchars($result_name) .
-    "</td></tr>\n" .
-    "<tr><td class='bold' width='1%' nowrap>" . xl('Status') . ": </td>" .
-    "<td>" . generate_select_list("form_result_status[$lino]", 'proc_res_status',
-      $result_status, xl('Result Status'), '') . "</td></tr>\n" .
-    "<tr><td class='bold' nowrap>" . xl('Facility') . ": </td>" .
-    "<td><input type='text' size='15' name='form_facility[$lino]'" .
-    " value='$facility' " .
-    " title='" . xl('Supplier facility name') . "'" .
-    " style='width:100%' /></td></tr>\n" .
-    "<tr><td class='bold' nowrap>" . xl('Comments') . ": </td>" .
-    "<td><textarea rows='3' cols='15' name='form_comments[$lino]'" .
-    " title='" . xl('Comments for this result or recommendation') . "'" .
-    " style='width:100%' />" . htmlspecialchars($comments) .
-    "</textarea></td></tr>\n" .
-    "</table>\n" .
-    "<p><center><input type='button' value='" . xl('Close') . "' " .
-    "onclick='extShow($lino, false)' /></center></p>\n".
-    "</div>";
-
-  $lastpoid = $order_id;
-  $lastprid = $report_id;
-  ++$lino;
-
 }
-// display facility information
-$extra_html .= "<table>";
-$extra_html .= "<th>". xl('Performing Laboratory Facility') . "</th>";
-foreach($facilities as $facilityID)
-{
-    foreach(explode(":", $facilityID) as $lab_facility)
-    {
-
-        $facility_array = getFacilityInfo($lab_facility);
-        if($facility_array)
-        {
-            $extra_html .=
-                "<tr><td><hr></td></tr>" .
-                "<tr><td>". htmlspecialchars($facility_array['fname']) . " " . htmlspecialchars($facility_array['lname']) . ", " . htmlspecialchars($facility_array['title']). "</td></tr>" .
-                "<tr><td>". htmlspecialchars($facility_array['organization']) . "</td></tr>" .
-                "<tr><td>". htmlspecialchars($facility_array['street']) . " " .htmlspecialchars($facility_array['city']) . " " . htmlspecialchars($facility_array['state']) . "</td></tr>" .
-                "<tr><td>". htmlspecialchars(formatPhone($facility_array['phone'])) . "</td></tr>";
-        }
+
+if (!empty($facilities)) {
+  // display facility information
+  $extra_html .= "<table>";
+  $extra_html .= "<tr><th>". xl('Performing Laboratory Facility') . "</th></tr>";
+  foreach($facilities as $facilityID) {
+    foreach(explode(":", $facilityID) as $lab_facility) {
+      $facility_array = getFacilityInfo($lab_facility);
+      if($facility_array) {
+        $extra_html .=
+          "<tr><td><hr></td></tr>" .
+          "<tr><td>". htmlspecialchars($facility_array['fname']) . " " . htmlspecialchars($facility_array['lname']) . ", " . htmlspecialchars($facility_array['title']). "</td></tr>" .
+          "<tr><td>". htmlspecialchars($facility_array['organization']) . "</td></tr>" .
+          "<tr><td>". htmlspecialchars($facility_array['street']) . " " .htmlspecialchars($facility_array['city']) . " " . htmlspecialchars($facility_array['state']) . "</td></tr>" .
+          "<tr><td>". htmlspecialchars(formatPhone($facility_array['phone'])) . "</td></tr>";
+      }
     }
+  }
+  $extra_html .= "</table>\n";
 }
-
-$extra_html .= "</table>\n";
 ?>
+
 </table>
 
 <?php
 if ($form_review) {
- // if user authorised for panding review.
+ // if user authorized for pending review.
  if ($reviewauth) {
  ?>
   <center><p>
index a355532..4fa6856 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010 Rod Roark <rod@sunsetsystems.com>
+// Copyright (C) 2010-2013 Rod Roark <rod@sunsetsystems.com>
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -27,7 +27,6 @@ function thisLineItem($row) {
     echo '"' . addslashes($row['pubpid'        ]) . '",';
     echo '"' . addslashes(oeFormatShortDate($row['date_ordered'  ])) . '",';
     echo '"' . addslashes($row['organization'  ]) . '",';
-    echo '"' . addslashes($row['procedure_name']) . '",';
     echo '"' . addslashes($provname             ) . '",';
     echo '"' . addslashes($row['priority_name' ]) . '",';
     echo '"' . addslashes($row['status_name'   ]) . '"' . "\n";
@@ -39,7 +38,6 @@ function thisLineItem($row) {
   <td class="detail"><?php echo $row['pubpid'        ]; ?></td>
   <td class="detail"><?php echo oeFormatShortDate($row['date_ordered'  ]); ?></td>
   <td class="detail"><?php echo $row['organization'  ]; ?></td>
-  <td class="detail"><?php echo $row['procedure_name']; ?></td>
   <td class="detail"><?php echo $provname; ?></td>
   <td class="detail"><?php echo $row['priority_name' ]; ?></td>
   <td class="detail"><?php echo $row['status_name'   ]; ?></td>
@@ -125,7 +123,6 @@ else { // not export
   <td class="dehead"><?php xl('ID','e'       ) ?></td>
   <td class="dehead"><?php xl('Ordered','e'  ) ?></td>
   <td class="dehead"><?php xl('From','e'     ) ?></td>
-  <td class="dehead"><?php xl('Procedure','e') ?></td>
   <td class="dehead"><?php xl('Provider','e' ) ?></td>
   <td class="dehead"><?php xl('Priority','e' ) ?></td>
   <td class="dehead"><?php xl('Status','e'   ) ?></td>
@@ -142,9 +139,8 @@ if ($_POST['form_refresh'] || $_POST['form_csvexport']) {
   $query = "SELECT po.patient_id, po.date_ordered, " .
     "pd.pubpid, " .
     "CONCAT(pd.lname, ', ', pd.fname, ' ', pd.mname) AS patient_name, " .
-    "pt1.name AS procedure_name, " .
     "u1.lname AS provider_lname, u1.fname AS provider_fname, u1.mname AS provider_mname, " .
-    "u2.organization, " .
+    "pp.name AS organization, " .
     "lop.title AS priority_name, " .
     "los.title AS status_name, " .
     "pr.procedure_report_id, pr.date_report, pr.report_status " .
@@ -152,8 +148,7 @@ if ($_POST['form_refresh'] || $_POST['form_csvexport']) {
     "JOIN form_encounter AS fe ON fe.pid = po.patient_id AND fe.encounter = po.encounter_id " .
     "JOIN patient_data AS pd ON pd.pid = po.patient_id " .
     "LEFT JOIN users AS u1 ON u1.id = po.provider_id " .
-    "LEFT JOIN procedure_type AS pt1 ON pt1.procedure_type_id = po.procedure_type_id " .
-    "LEFT JOIN users AS u2 ON u2.id = pt1.lab_id " .
+    "LEFT JOIN procedure_providers AS pp ON pp.ppid = po.lab_id " .
     "LEFT JOIN list_options AS lop ON lop.list_id = 'ord_priority' AND lop.option_id = po.order_priority " .
     "LEFT JOIN list_options AS los ON los.list_id = 'ord_status' AND los.option_id = po.order_status " .
     "LEFT JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id " .
diff --git a/interface/orders/procedure_provider_edit.php b/interface/orders/procedure_provider_edit.php
new file mode 100644 (file)
index 0000000..4b9d513
--- /dev/null
@@ -0,0 +1,277 @@
+<?php
+/**
+* Maintenance for the list of procedure providers.
+*
+* Copyright (C) 2012 Rod Roark <rod@sunsetsystems.com>
+*
+* LICENSE: This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://opensource.org/licenses/gpl-license.php>.
+*
+* @package   OpenEMR
+* @author    Rod Roark <rod@sunsetsystems.com>
+*/
+
+$sanitize_all_escapes = true;
+$fake_register_globals =false;
+
+require_once("../globals.php");
+require_once("$srcdir/acl.inc");
+require_once("$srcdir/options.inc.php");
+require_once("$srcdir/formdata.inc.php");
+require_once("$srcdir/htmlspecialchars.inc.php");
+
+// Collect user id if editing entry
+$ppid = $_REQUEST['ppid'];
+
+$info_msg = "";
+
+function invalue($name) {
+  $fld = add_escape_custom(trim($_POST[$name]));
+  return "'$fld'";
+}
+
+?>
+<html>
+<head>
+<title><?php echo $ppid ? xlt('Edit') : xlt('Add New') ?> <?php echo xlt('Procedure Provider'); ?></title>
+<link rel="stylesheet" href='<?php echo $css_header ?>' type='text/css'>
+<script type="text/javascript" src="../../library/js/jquery.1.3.2.js"></script>
+
+<style>
+td { font-size:10pt; }
+
+.inputtext {
+ padding-left:2px;
+ padding-right:2px;
+}
+
+.button {
+ font-family:sans-serif;
+ font-size:9pt;
+ font-weight:bold;
+}
+</style>
+
+<script language="JavaScript">
+</script>
+
+</head>
+
+<body class="body_top">
+<?php
+// If we are saving, then save and close the window.
+//
+if ($_POST['form_save']) {
+  $sets =
+    "name = "         . invalue('form_name')         . ", " .
+    "npi = "          . invalue('form_npi')          . ", " .
+    "send_app_id = "  . invalue('form_send_app_id')  . ", " .
+    "send_fac_id = "  . invalue('form_send_fac_id')  . ", " .
+    "recv_app_id = "  . invalue('form_recv_app_id')  . ", " .
+    "recv_fac_id = "  . invalue('form_recv_fac_id')  . ", " .
+    "DorP = "         . invalue('form_DorP')         . ", " .
+    "protocol = "     . invalue('form_protocol')     . ", " .
+    "remote_host = "  . invalue('form_remote_host')  . ", " .
+    "login = "        . invalue('form_login')        . ", " .
+    "password = "     . invalue('form_password')     . ", " .
+    "orders_path = "  . invalue('form_orders_path')  . ", " .
+    "results_path = " . invalue('form_results_path') . ", " .
+    "notes = "        . invalue('form_notes');
+  if ($ppid) {
+    $query = "UPDATE procedure_providers SET $sets " .
+      "WHERE ppid = '"  . add_escape_custom($ppid) . "'";
+    sqlStatement($query);
+  }
+  else {
+    $ppid = sqlInsert("INSERT INTO procedure_providers SET $sets");
+  }
+}
+else if ($_POST['form_delete']) {
+  if ($ppid) {
+    sqlStatement("DELETE FROM procedure_providers WHERE ppid = ?", array($ppid));
+  }
+}
+
+if ($_POST['form_save'] || $_POST['form_delete']) {
+  // Close this window and redisplay the updated list.
+  echo "<script language='JavaScript'>\n";
+  if ($info_msg) echo " alert('" . addslashes($info_msg) . "');\n";
+  echo " window.close();\n";
+  echo " if (opener.refreshme) opener.refreshme();\n";
+  echo "</script></body></html>\n";
+  exit();
+}
+
+if ($ppid) {
+  $row = sqlQuery("SELECT * FROM procedure_providers WHERE ppid = ?", array($ppid));
+}
+?>
+
+<form method='post' name='theform' action='procedure_provider_edit.php?ppid=<?php echo attr($ppid) ?>'>
+<center>
+
+<table border='0' width='100%'>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Name'); ?>:</b></td>
+  <td>
+   <input type='text' size='40' name='form_name' maxlength='255'
+    value='<?php echo attr($row['name']); ?>'
+    style='width:100%' class='inputtext' />
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('NPI'); ?>:</b></td>
+  <td>
+   <input type='text' size='10' name='form_npi' maxlength='10'
+    value='<?php echo attr($row['npi']); ?>' class='inputtext' />
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Sender IDs'); ?>:</b></td>
+  <td>
+   <?php echo xlt('Application'); ?>:
+   <input type='text' size='10' name='form_send_app_id' maxlength='100'
+    value='<?php echo attr($row['send_app_id']); ?>'
+    title='<?php echo xla('MSH-3.1'); ?>'
+    class='inputtext' />
+   &nbsp;<?php echo xlt('Facility'); ?>:
+   <input type='text' size='10' name='form_send_fac_id' maxlength='100'
+    value='<?php echo attr($row['send_fac_id']); ?>'
+    title='<?php echo xla('MSH-4.1'); ?>'
+    class='inputtext' />
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Receiver IDs'); ?>:</b></td>
+  <td>
+   <?php echo xlt('Application'); ?>:
+   <input type='text' size='10' name='form_recv_app_id' maxlength='100'
+    value='<?php echo attr($row['recv_app_id']); ?>'
+    title='<?php echo xla('MSH-5.1'); ?>'
+    class='inputtext' />
+   &nbsp;<?php echo xlt('Facility'); ?>:
+   <input type='text' size='10' name='form_recv_fac_id' maxlength='100'
+    value='<?php echo attr($row['recv_fac_id']); ?>'
+    title='<?php echo xla('MSH-6.1'); ?>'
+    class='inputtext' />
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Usage'); ?>:</b></td>
+  <td>
+   <select name='form_DorP' title='<?php echo xla('MSH-11'); ?>'>
+<?php
+foreach(array(
+  'D' => 'Debugging',
+  'P' => 'Production',
+  ) as $key => $value)
+{
+  echo "    <option value='" . attr($key) . "'";
+  if ($key == $row['DorP']) echo " selected";
+  echo ">" . xlt($value) . "</option>\n";
+}
+?>
+   </select>
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Protocol'); ?>:</b></td>
+  <td>
+   <select name='form_protocol'>
+<?php
+foreach(array(
+  // Add to this list as more protocols are supported.
+  'DL' => 'Download',
+  'SFTP' => 'SFTP',
+  ) as $key => $value)
+{
+  echo "    <option value='" . attr($key) . "'";
+  if ($key == $row['protocol']) echo " selected";
+  echo ">" . xlt($value) . "</option>\n";
+}
+?>
+   </select>
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Remote Host'); ?>:</b></td>
+  <td>
+   <input type='text' size='40' name='form_remote_host' maxlength='255'
+    value='<?php echo attr($row['remote_host']); ?>' class='inputtext' />
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Login'); ?>:</b></td>
+  <td>
+   <input type='text' size='20' name='form_login' maxlength='255'
+    value='<?php echo attr($row['login']); ?>' class='inputtext' />
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Password'); ?>:</b></td>
+  <td>
+   <input type='text' size='20' name='form_password' maxlength='255'
+    value='<?php echo attr($row['password']); ?>' class='inputtext' />
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Orders Path'); ?>:</b></td>
+  <td>
+   <input type='text' size='40' name='form_orders_path' maxlength='255'
+    value='<?php echo attr($row['orders_path']); ?>' class='inputtext' />
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Results Path'); ?>:</b></td>
+  <td>
+   <input type='text' size='40' name='form_results_path' maxlength='255'
+    value='<?php echo attr($row['results_path']); ?>' class='inputtext' />
+  </td>
+ </tr>
+
+ <tr>
+  <td nowrap><b><?php echo xlt('Notes'); ?>:</b></td>
+  <td>
+   <textarea rows='3' cols='40' name='form_notes' style='width:100%'
+    wrap='virtual' class='inputtext' /><?php echo text($row['notes']) ?></textarea>
+  </td>
+ </tr>
+
+</table>
+
+<br />
+
+<input type='submit' name='form_save' value='<?php echo xla('Save'); ?>' />
+
+<?php if ($ppid) { ?>
+&nbsp;
+<input type='submit' name='form_delete' value='<?php echo xla('Delete'); ?>' style='color:red' />
+<?php } ?>
+
+&nbsp;
+<input type='button' value='<?php echo xla('Cancel'); ?>' onclick='window.close()' />
+</p>
+
+</center>
+</form>
+</body>
+</html>
diff --git a/interface/orders/procedure_provider_list.php b/interface/orders/procedure_provider_list.php
new file mode 100644 (file)
index 0000000..a806d7f
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+* Maintenance for the list of procedure providers.
+*
+* Copyright (C) 2012 Rod Roark <rod@sunsetsystems.com>
+*
+* LICENSE: This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://opensource.org/licenses/gpl-license.php>.
+*
+* @package   OpenEMR
+* @author    Rod Roark <rod@sunsetsystems.com>
+*/
+
+$sanitize_all_escapes = true;
+$fake_register_globals = false;
+
+require_once("../globals.php");
+require_once("$srcdir/acl.inc");
+require_once("$srcdir/formdata.inc.php");
+require_once("$srcdir/options.inc.php");
+require_once("$srcdir/htmlspecialchars.inc.php");
+
+$popup = empty($_GET['popup']) ? 0 : 1;
+
+$form_name = trim($_POST['form_name']);
+
+$query = "SELECT pp.* FROM procedure_providers AS pp";
+$query .= " ORDER BY pp.name";
+$res = sqlStatement($query);
+?>
+<html>
+
+<head>
+
+<link rel="stylesheet" href='<?php echo $css_header ?>' type='text/css'>
+<title><?php echo xlt('Procedure Providers'); ?></title>
+
+<?php if ($popup) { ?>
+<script type="text/javascript" src="../../library/topdialog.js"></script>
+<?php } ?>
+<script type="text/javascript" src="../../library/dialog.js"></script>
+
+<script language="JavaScript">
+
+<?php if ($popup) require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
+
+// Callback from popups to refresh this display.
+function refreshme() {
+ // location.reload();
+ document.forms[0].submit();
+}
+
+// Process click to pop up the add window.
+function doedclick_add() {
+ top.restoreSession(); 
+ dlgopen('procedure_provider_edit.php?ppid=0', '_blank', 700, 550);
+}
+
+// Process click to pop up the edit window.
+function doedclick_edit(ppid) {
+ top.restoreSession();
+ dlgopen('procedure_provider_edit.php?ppid=' + ppid, '_blank', 700, 550);
+}
+
+</script>
+
+</head>
+
+<body class="body_top">
+
+<div id="addressbook_list">
+<form method='post' action='procedure_provider_list.php'>
+
+<table>
+ <tr class='search'> <!-- bgcolor='#ddddff' -->
+  <td>
+   <input type='submit' class='button' name='form_search' value='<?php echo xla("Refresh")?>' />
+   <input type='button' class='button' value='<?php echo xla("Add New"); ?>' onclick='doedclick_add()' />
+  </td>
+ </tr>
+</table>
+
+<table>
+ <tr class='head'>
+  <td title='<?php echo xla('Click to view or edit'); ?>'><?php echo xlt('Name'); ?></td>
+  <td><?php echo xlt('NPI'); ?></td>
+  <td><?php echo xlt('Protocol'); ?></td>
+ </tr>
+
+<?php
+ $encount = 0;
+ while ($row = sqlFetchArray($res)) {
+  ++$encount;
+  $bgclass = (($encount & 1) ? "evenrow" : "oddrow");
+
+  if (acl_check('admin', 'practice' )) {
+   $trTitle = xl('Edit') . ' ' . $row['name'];
+   echo " <tr class='detail $bgclass' style='cursor:pointer' " .
+        "onclick='doedclick_edit(" . $row['ppid'] . ")' title='" . attr($trTitle) . "'>\n"; 
+  }
+  else {
+   $trTitle = $displayName . " (" . xl("Not Allowed to Edit") . ")";
+   echo " <tr class='detail $bgclass' title='" . attr($trTitle) . "'>\n";
+  }
+  echo "  <td>" . text($row['name']    ) . "</td>\n";
+  echo "  <td>" . text($row['npi']     ) . "</td>\n";
+  echo "  <td>" . text($row['protocol']) . "</td>\n";
+  echo " </tr>\n";
+ }
+?>
+</table>
+
+</body>
+</html>
index 5bddebc..2776054 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010 Rod Roark <rod@sunsetsystems.com>
+// Copyright (C) 2010-2013 Rod Roark <rod@sunsetsystems.com>
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -395,24 +395,27 @@ foreach (array(1 => 'Screen', 2 => 'Printer', 3 => 'Export File') as $key => $va
       "po.provider_id, pd.regdate, " .
       "pd.sex, pd.DOB, pd.lname, pd.fname, pd.mname, " .
       "pd.contrastart, pd.referral_source$pd_fields, " .
-      "ps.abnormal, ps.procedure_type_id AS result_type, " .
-      "pto.name AS order_name, ptr.name AS result_name, ptr.related_code " .
+      "ps.abnormal, " .
+      // "pto.name AS order_name, ptr.name AS result_name, ptr.related_code " .
+      "pc.procedure_name AS order_name, ptr.name AS result_name, ptr.related_code " .
       "FROM procedure_order AS po " .
       "JOIN form_encounter AS fe ON fe.pid = po.patient_id AND fe.encounter = po.encounter_id " .
       "JOIN patient_data AS pd ON pd.pid = po.patient_id $sexcond" .
+      "JOIN procedure_order_code AS pc ON pc.procedure_order_id = po.procedure_order_id " .
       "JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id " .
+      "AND pr.procedure_order_seq = pc.procedure_order_seq " .
       "AND pr.date_report IS NOT NULL " .
       "JOIN procedure_result AS ps ON ps.procedure_report_id = pr.procedure_report_id " .
       "AND ps.result_status = 'final' " .
-      "JOIN procedure_type AS pto ON pto.procedure_type_id = po.procedure_type_id " .
-      "JOIN procedure_type AS ptr ON ptr.procedure_type_id = ps.procedure_type_id " .
-      "AND ptr.procedure_type NOT LIKE 'rec' " .
+      // "JOIN procedure_type AS pto ON pto.procedure_type_id = pc.procedure_type_id " .
+      "JOIN procedure_type AS ptr ON ptr.lab_id = po.lab_id AND ptr.procedure_code = ps.result_code " .
+      "AND ptr.procedure_type LIKE 'res%' " .
       "WHERE po.date_ordered IS NOT NULL AND po.date_ordered >= '$from_date' " .
       "AND po.date_ordered <= '$to_date' ";
     if ($form_facility) {
       $query .= "AND fe.facility_id = '$form_facility' ";
     }
-    $query .= "ORDER BY fe.pid, fe.encounter, ps.procedure_type_id"; // needed?
+    $query .= "ORDER BY fe.pid, fe.encounter, ps.result_code"; // needed?
 
     $res = sqlStatement($query);
 
diff --git a/interface/orders/qoe.inc.php b/interface/orders/qoe.inc.php
new file mode 100644 (file)
index 0000000..6fb629c
--- /dev/null
@@ -0,0 +1,195 @@
+<?php
+/**
+* Functions to support questions at order entry that are specific to order type.
+*
+* Copyright (C) 2012 Rod Roark <rod@sunsetsystems.com>
+*
+* LICENSE: This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://opensource.org/licenses/gpl-license.php>.
+*
+* @package   OpenEMR
+* @author    Rod Roark <rod@sunsetsystems.com>
+*/
+
+/**
+ * Generate HTML for the QOE form suitable for insertion into a <div>.
+ * This HTML may contain single quotes but not unescaped double quotes.
+ *
+ * @param  integer $ptid     Value matching a procedure_type_id in the procedure_types table.
+ * @param  integer $orderid  Procedure order ID, if there is an existing order.
+ * @param  integer $dbseq    Procedure order item sequence number, if there is an existing procedure.
+ * @param  string  $formseq  Zero-relative occurrence number in the form.
+ * @return string            The generated HTML.
+ */
+function generate_qoe_html($ptid=0, $orderid=0, $dbseq=0, $formseq=0) {
+  global $rootdir, $qoe_init_javascript;
+
+  $s = "";
+  $qoe_init_javascript = '';
+  $prefix = 'ans' . $formseq . '_';
+
+  if (empty($ptid)) return $s;
+
+  $s .= "<table>";
+
+  // Get all the questions for the given procedure order type.
+  $qres = sqlStatement("SELECT " .
+    "q.question_code, q.question_text, q.options, q.required, q.maxsize, " .
+    "q.fldtype, q.tips " .
+    "FROM procedure_type AS t " .
+    "JOIN procedure_questions AS q ON q.lab_id = t.lab_id " .
+    "AND q.procedure_code = t.procedure_code AND q.activity = 1 " .
+    "WHERE t.procedure_type_id = ? " .
+    "ORDER BY q.seq, q.question_text", array($ptid));
+
+  while ($qrow = sqlFetchArray($qres)) {
+    $options = trim($qrow['options']);
+    $qfieldid = $prefix . attr(trim($qrow['question_code']));
+    $fldtype = $qrow['fldtype'];
+    $maxsize = 0 + $qrow['maxsize'];
+
+    // Get answer value(s) to this question, if any.
+    $answers = array();
+    if ($orderid && $dbseq > 0) {
+      $ares = sqlStatement("SELECT answer FROM procedure_answers WHERE " .
+        "procedure_order_id = ? AND procedure_order_seq = ? AND question_code = ? " .
+        "ORDER BY answer_seq", array($orderid, $dbseq, $qrow['question_code']));
+      while ($arow = sqlFetchArray($ares)) {
+        $answers[] = $arow['answer'];
+      }
+    }
+
+    $s .= "<tr>";
+    $s .= "<td width='1%' valign='top' nowrap";
+    if ($qrow['required']) $s .= " style='color:#880000'"; // TBD: move to stylesheet
+    $s .= ">" . attr($qrow['question_text']) . "</td>";
+    $s .= "<td valign='top'>";
+
+    if ($fldtype == 'T') {
+      // Text Field.
+      $s .= "<input type='text' name='$qfieldid'";
+      if ($maxsize) $s .= " maxlength='$maxsize'";
+      if (!empty($answers)) $s .= " value='" . attr($answers[0]) . "'";
+      $s .= " />";
+      $s .= "&nbsp;" . text($qrow['tips']);
+    }
+
+    else if ($fldtype == 'N') {
+      // Numeric text Field.
+      // TBD: Add some JavaScript validation for this.
+      $s .= "<input type='text' name='$qfieldid' maxlength='8'";
+      if (!empty($answers)) $s .= " value='" . attr($answers[0]) . "'";
+      $s .= " />";
+      $s .= "&nbsp;" . text($qrow['tips']);
+    }
+
+    else if ($fldtype == 'D') {
+      // Date Field.
+      $s .= "<input type='text' size='10' name='$qfieldid' id='$qfieldid'";
+      if (!empty($answers)) $s .= " value='" . attr($answers[0]) . "'";
+      $s .= " onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)' />";
+      $s .= "<img src='$rootdir/pic/show_calendar.gif' align='absbottom' width='24' height='22'" .
+        " id='img_$qfieldid' border='0' alt='[?]' style='cursor:pointer'" .
+        " title='" . htmlspecialchars( xl('Click here to choose a date'), ENT_QUOTES) . "' />";
+      $qoe_init_javascript .= " Calendar.setup({inputField:'$qfieldid', ifFormat:'%Y-%m-%d', button:'img_$qfieldid'});";
+    }
+
+    else if ($fldtype == 'G') {
+      // Gestational age in weeks and days.
+      $currweeks = -1;
+      $currdays  = -1;
+      if (!empty($answers)) {
+        $currweeks = intval($answers[0] / 7);
+        $currdays  = $answers[0] % 7;
+      }
+      $s .= "<select name='G1_$qfieldid'>";
+      $s .= "<option value=''></option>";
+      for ($i = 5; $i <= 21; ++$i) {
+        $s .= "<option value='$i'";
+        if ($i == $currweeks) $s .= " selected";
+        $s .= ">$i</option>";
+      }
+      $s .= "</select>";
+      $s .= " " . xl('weeks') . " &nbsp;";
+      $s .= "<select name='G2_$qfieldid'>";
+      $s .= "<option value=''></option>";
+      for ($i = 0; $i <= 6; ++$i) {
+        $s .= "<option value='$i'";
+        if ($i == $currdays) $s .= " selected";
+        $s .= ">$i</option>";
+      }
+      $s .= "</select>";
+      $s .= " " . xl('days');
+    }
+
+    // Possible alternative code instead of radio buttons and checkboxes.
+    // Might use this for cases where the list of choices is large.
+    /*****************************************************************
+    else {
+      // Single- or multi-select list.
+      $multiple = false;
+      if (substr($options, 0, 2) == '+;') {
+        $multiple = true;
+        $options = substr($options, 2);
+      }
+      $s .= "<select name='$qfieldid'";
+      if ($multiple) $s .= " multiple";
+      $s .= ">";
+      $a = explode(';', $qrow['options']);
+      foreach ($a as $aval) {
+        list($desc, $code) = explode(':', $aval);
+        if (empty($code)) $code = $desc;
+        $s .= "<option value='" . attr($code) . "'";
+        if (in_array($code, $answers)) $s .= " selected";
+        $s .= ">" . text($desc) . "</option>";
+      }
+      $s .= "</select>";
+    }
+    *****************************************************************/
+
+    else if ($fldtype == 'M') {
+      // List of checkboxes.
+      $a = explode(';', $qrow['options']);
+      $i = 0;
+      foreach ($a as $aval) {
+        list($desc, $code) = explode(':', $aval);
+        if (empty($code)) $code = $desc;
+        if ($i) $s .= "<br />";
+        $s .= "<input type='checkbox' name='$qfieldid[$i]' value='" . attr($code) . "'";
+        if (in_array($code, $answers)) $s .= " checked";
+        $s .= " />" . text($desc);
+        ++$i;
+      }
+    }
+
+    else {
+      // Radio buttons.
+      $a = explode(';', $qrow['options']);
+      $i = 0;
+      foreach ($a as $aval) {
+        list($desc, $code) = explode(':', $aval);
+        if (empty($code)) $code = $desc;
+        if ($i) $s .= "<br />";
+        $s .= "<input type='radio' name='$qfieldid' value='" . attr($code) . "'";
+        if (in_array($code, $answers)) $s .= " checked";
+        $s .= " />" . text($desc);
+        ++$i;
+      }
+    }
+
+    $s .= '</td>';
+    $s .= '</tr>';
+  }
+
+  $s .= '</table>';
+  return $s;
+}
+?>
diff --git a/interface/orders/receive_hl7_results.inc.php b/interface/orders/receive_hl7_results.inc.php
new file mode 100644 (file)
index 0000000..b45a80b
--- /dev/null
@@ -0,0 +1,369 @@
+<?php
+/**
+* Functions to support parsing and saving hl7 results.
+*
+* Copyright (C) 2013 Rod Roark <rod@sunsetsystems.com>
+*
+* LICENSE: This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://opensource.org/licenses/gpl-license.php>.
+*
+* @package   OpenEMR
+* @author    Rod Roark <rod@sunsetsystems.com>
+*/
+
+function rhl7InsertRow(&$arr, $tablename) {
+  if (empty($arr)) return;
+
+  // echo "<!-- ";   // debugging
+  // print_r($arr);
+  // echo " -->\n";
+
+  $query = "INSERT INTO $tablename SET";
+  $binds = array();
+  $sep = '';
+  foreach ($arr as $key => $value) {
+    $query .= "$sep `$key` = ?";
+    $sep = ',';
+    $binds[] = $value;
+  }
+  $arr = array();
+  return sqlInsert($query, $binds);
+}
+
+function rhl7FlushResult(&$ares) {
+  return rhl7InsertRow($ares, 'procedure_result');
+}
+
+function rhl7FlushReport(&$arep) {
+  return rhl7InsertRow($arep, 'procedure_report');
+}
+
+function rhl7Text($s) {
+  $s = str_replace('\\S\\'  ,'^' , $s);
+  $s = str_replace('\\F\\'  ,'|' , $s);
+  $s = str_replace('\\R\\'  ,'~' , $s);
+  $s = str_replace('\\T\\'  ,'&' , $s);
+  $s = str_replace('\\X0d\\',"\r", $s);
+  $s = str_replace('\\E\\'  ,'\\', $s);
+  return $s;
+}
+
+function rhl7DateTime($s) {
+  if (empty($s)) return '0000-00-00 00:00:00';
+  $ret = substr($s, 0, 4) . '-' . substr($s, 4, 2) . '-' . substr($s, 6, 2);
+  if (strlen($s) > 8) $ret .= ' ' . substr($s, 8, 2) . ':' . substr($s, 10, 2) . ':';
+  if (strlen($s) > 12) {
+    $ret .= substr($s, 12, 2);
+  } else {
+    $ret .= '00';
+  }
+  return $ret;
+}
+
+function rhl7Abnormal($s) {
+  if ($s == ''  ) return 'no';
+  if ($s == 'A' ) return 'yes';
+  if ($s == 'H' ) return 'high';
+  if ($s == 'L' ) return 'low';
+  if ($s == 'HH') return 'vhigh';
+  if ($s == 'LL') return 'vlow';
+  return rhl7Text($s);
+}
+
+function rhl7ReportStatus($s) {
+  if ($s == 'F') return 'final';
+  if ($s == 'P') return 'prelim';
+  if ($s == 'C') return 'correct';
+  return rhl7Text($s);
+}
+
+/**
+ * Parse and save.
+ *
+ * @param  string  &$pprow   A row from the procedure_providers table.
+ * @param  string  &$hl7     The input HL7 text.
+ * @return string            Error text, or empty if no errors.
+ */
+function receive_hl7_results(&$pprow, &$hl7) {
+  if (substr($hl7, 0, 3) != 'MSH') {
+    return xl('Input does not begin with a MSH segment');
+  }
+
+  // End-of-line delimiter for text in procedure_result.comments
+  $commentdelim = "\n";
+
+  $today = time();
+
+  $in_message_id = '';
+  $in_ssn = '';
+  $in_dob = '';
+  $in_lname = '';
+  $in_fname = '';
+  $in_orderid = 0;
+  $in_procedure_code = '';
+  $in_report_status = '';
+  $in_encounter = 0;
+
+  $porow = false;
+  $pcrow = false;
+  $procedure_report_id = 0;
+  $arep = array(); // holding area for OBR and its NTE data
+  $ares = array(); // holding area for OBX and its NTE data
+
+  // This is so we know where we are if a segment like NTE that can appear in
+  // different places is encountered.
+  $context = '';
+
+  // Delimiters
+  $d0 = "\r";
+  $d1 = substr($hl7, 3, 1); // typically |
+  $d2 = substr($hl7, 4, 1); // typically ^
+  $d3 = substr($hl7, 5, 1); // typically ~
+
+  $segs = explode($d0, $hl7);
+
+  foreach ($segs as $seg) {
+    if (empty($seg)) continue;
+
+    $a = explode($d1, $seg);
+
+    if ($a[0] == 'MSH') {
+      $context = $a[0];
+      if ($a[8] != 'ORU^R01') {
+        return xl('Message type') . " '${a[8]}' " . xl('does not seem valid');
+      }
+      $in_message_id = $a[9];
+    }
+
+    else if ($a[0] == 'PID') {
+      $context = $a[0];
+      rhl7FlushResult($ares);
+      // Next line will do something only if there was a report with no results.
+      rhl7FlushReport($arep);
+      $in_ssn = $a[4];
+      $in_dob = $a[7]; // yyyymmdd format
+      $tmp = explode($d2, $a[5]);
+      $in_lname = $tmp[0];
+      $in_fname = $tmp[1];
+    }
+
+    else if ($a[0] == 'PV1') {
+      // Save placer encounter number if present.
+      if (!empty($a[19])) {
+        $tmp = explode($d2, $a[19]);
+        $in_encounter = intval($tmp[0]);
+      }
+    }
+
+    else if ($a[0] == 'ORC') {
+      $context = $a[0];
+      rhl7FlushResult($ares);
+      // Next line will do something only if there was a report with no results.
+      rhl7FlushReport($arep);
+      $porow = false;
+      $pcrow = false;
+      if ($a[2]) $in_orderid = intval($a[2]);
+    }
+
+    else if ($a[0] == 'NTE' && $context == 'ORC') {
+      // TBD? Is this ever used?
+    }
+
+    else if ($a[0] == 'OBR') {
+      $context = $a[0];
+      rhl7FlushResult($ares);
+      // Next line will do something only if there was a report with no results.
+      rhl7FlushReport($arep);
+      $procedure_report_id = 0;
+      if ($a[2]) $in_orderid = intval($a[2]);
+      $tmp = explode($d2, $a[4]);
+      $in_procedure_code = $tmp[0];
+      $in_procedure_name = $tmp[1];
+      $in_report_status = rhl7ReportStatus($a[25]);
+      if (empty($porow)) {
+        $porow = sqlQuery("SELECT * FROM procedure_order WHERE " .
+          "procedure_order_id = ?", array($in_orderid));
+        // The order must already exist. Currently we do not handle electronic
+        // results returned for manual orders.
+        if (empty($porow)) {
+          return xl('Procedure order') . " '$in_orderid' " . xl('was not found');
+        }
+        if ($in_encounter) {
+          if ($porow['encounter_id'] != $in_encounter) {
+            return xl('Encounter ID') .
+              " '" . $porow['encounter_id'] . "' " .
+              xl('for OBR placer order number') .
+              " '$in_orderid' " .
+              xl('does not match the PV1 encounter number') .
+              " '$in_encounter'";
+          }
+        }
+        else {
+          // They did not return an encounter number to verify, so more checking
+          // might be done here to make sure the patient seems to match.
+        }
+      }
+      // Find the order line item (procedure code) that matches this result.
+      $pcquery = "SELECT pc.* FROM procedure_order_code AS pc " .
+        "WHERE pc.procedure_order_id = ? AND pc.procedure_code = ? " .
+        "ORDER BY procedure_order_seq LIMIT 1";
+      $pcrow = sqlQuery($pcquery, array($in_orderid, $in_procedure_code));
+      if (empty($pcrow)) {
+        // There is no matching procedure in the order, so it must have been
+        // added after the original order was sent, either as a manual request
+        // from the physician or as a "reflex" from the lab.
+        // procedure_source = '2' indicates this.
+        sqlInsert("INSERT INTO procedure_order_code SET " .
+          "procedure_order_id = ?, " .
+          "procedure_code = ?, " .
+          "procedure_name = ?, " .
+          "procedure_source = '2'",
+          array($in_orderid, $in_procedure_code, $in_procedure_name));
+        $pcrow = sqlQuery($pcquery, array($in_orderid, $in_procedure_code));
+      }
+      $arep = array();
+      $arep['procedure_order_id'] = $in_orderid;
+      $arep['procedure_order_seq'] = $pcrow['procedure_order_seq'];
+      $arep['date_collected'] = rhl7DateTime($a[7]);
+      $arep['date_report'] = substr(rhl7DateTime($a[22]), 0, 10);
+      $arep['report_status'] = $in_report_status;
+      $arep['report_notes'] = '';
+    }
+
+    else if ($a[0] == 'NTE' && $context == 'OBR') {
+      $arep['report_notes'] .= rhl7Text($a[3]) . "\n";
+    }
+
+    else if ($a[0] == 'OBX') {
+      $context = $a[0];
+      rhl7FlushResult($ares);
+      if (!$procedure_report_id) {
+        $procedure_report_id = rhl7FlushReport($arep);
+      }
+      $ares = array();
+      $ares['procedure_report_id'] = $procedure_report_id;
+      // OBX-5 can be a very long string of text with "~" as line separators.
+      // The first line of comments is reserved for such things.
+      if (strlen($a[5]) > 200) {
+        $ares['result_data_type'] = 'L';
+        $ares['result'] = '';
+        $ares['comments'] = rhl7Text($a[5]) . $commentdelim;
+      }
+      else {
+        $ares['result_data_type'] = substr($a[2], 0, 1); // N, S or F
+        $ares['result'] = rhl7Text($a[5]);
+        $ares['comments'] = $commentdelim;
+      }
+      $tmp = explode($d2, $a[3]);
+      $ares['result_code'] = rhl7Text($tmp[0]);
+      $ares['result_text'] = rhl7Text($tmp[1]);
+      $ares['date'] = rhl7DateTime($a[14]);
+      $ares['facility'] = rhl7Text($a[15]);
+      $ares['units'] = rhl7Text($a[6]);
+      $ares['range'] = rhl7Text($a[7]);
+      $ares['abnormal'] = rhl7Abnormal($a[8]); // values are lab dependent
+      $ares['result_status'] = rhl7ReportStatus($a[11]);
+    }
+
+    else if ($a[0] == 'NTE' && $context == 'OBX') {
+      $ares['comments'] .= rhl7Text($a[3]) . $commentdelim;
+    }
+
+    // Add code here for any other segment types that may be present.
+
+    else {
+      return xl('Segment name') . " '${a[0]}' " . xl('is misplaced or unknown');
+    }
+  }
+
+  rhl7FlushResult($ares);
+  // Next line will do something only if there was a report with no results.
+  rhl7FlushReport($arep);
+  return '';
+}
+
+/**
+ * Poll all eligible labs for new results and store them in the database.
+ *
+ * @param  array   &$messages  Receives messages of interest.
+ * @return string  Error text, or empty if no errors.
+ */
+function poll_hl7_results(&$messages) {
+  global $srcdir;
+
+  $messages = array();
+  $filecount = 0;
+  $badcount = 0;
+
+  $ppres = sqlStatement("SELECT * FROM procedure_providers ORDER BY name");
+
+  while ($pprow = sqlFetchArray($ppres)) {
+    $protocol = $pprow['protocol'];
+    $remote_host = $pprow['remote_host'];
+    $hl7 = '';
+
+    if ($protocol == 'SFTP') {
+      ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . "$srcdir/phpseclib");
+      require_once("$srcdir/phpseclib/Net/SFTP.php");
+      // Compute the target path name.
+      $pathname = '.';
+      if ($pprow['results_path']) $pathname = $pprow['results_path'] . '/' . $pathname;
+      // Connect to the server and enumerate files to process.
+      $sftp = new Net_SFTP($remote_host);
+      if (!$sftp->login($pprow['login'], $pprow['password'])) {
+        return xl('Login to remote host') . " '$remote_host' " . xl('failed');
+      }
+      $files = $sftp->nlist($pathname);
+      foreach ($files as $file) {
+        if (substr($file, 0, 1) == '.') continue;
+        ++$filecount;
+        $hl7 = $sftp->get("$pathname/$file");
+        // Archive the results file.
+        $prpath = $GLOBALS['OE_SITE_DIR'] . "/procedure_results";
+        if (!file_exists($prpath)) mkdir($prpath);
+        $prpath .= '/' . $pprow['ppid'];
+        if (!file_exists($prpath)) mkdir($prpath);
+        $fh = fopen("$prpath/$file", 'w');
+        if ($fh) {
+          fwrite($fh, $hl7);
+          fclose($fh);
+        }
+        else {
+          $messages[] = xl('File') . " '$file' " . xl('cannot be archived, ignored');
+          ++$badcount;
+          continue;
+        }
+        // Now delete it from its ftp directory.
+        if (!$sftp->delete("$pathname/$file")) {
+          $messages[] = xl('File') . " '$file' " . xl('cannot be deleted, ignored');
+          ++$badcount;
+          continue;
+        }
+        // Parse and process its contents.
+        $msg = receive_hl7_results($pprow, $hl7);
+        if ($msg) {
+          $messages[] = xl('Error processing file') . " '$file':" . $msg;
+          ++$badcount;
+          continue;
+        }
+        $messages[] = xl('New file') . " '$file' " . xl('processed successfully');
+      }
+    }
+
+    // TBD: Insert "else if ($protocol == '???') {...}" to support other protocols.
+
+  }
+
+  if ($badcount) return "$badcount " . xl('error(s) encountered from new results');
+
+  return '';
+}
+?>
diff --git a/interface/orders/single_order_results.php b/interface/orders/single_order_results.php
new file mode 100644 (file)
index 0000000..ed0360b
--- /dev/null
@@ -0,0 +1,385 @@
+<?php
+/**
+* Script to display results for a given procedure order.
+*
+* Copyright (C) 2013 Rod Roark <rod@sunsetsystems.com>
+*
+* LICENSE: This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://opensource.org/licenses/gpl-license.php>.
+*
+* @package   OpenEMR
+* @author    Rod Roark <rod@sunsetsystems.com>
+*/
+
+require_once("../globals.php");
+require_once("$srcdir/acl.inc");
+require_once("$srcdir/formdata.inc.php");
+require_once("$srcdir/options.inc.php");
+require_once("$srcdir/formatting.inc.php");
+require_once("../orders/lab_exchange_tools.php");
+
+// Check authorization.
+$thisauth = acl_check('patients', 'med');
+if (!$thisauth) die(xl('Not authorized'));
+
+$orderid = intval($_GET['orderid']);
+
+function getListItem($listid, $value) {
+  $lrow = sqlQuery("SELECT title FROM list_options " .
+    "WHERE list_id = ? AND option_id = ?",
+    array($listid, $value));
+  $tmp = xl_list_label($lrow['title']);
+  if (empty($tmp)) $tmp = "($report_status)";
+  return $tmp;
+}
+
+function myCellText($s) {
+  if ($s === '') return '&nbsp;';
+  return text($s);
+}
+
+// Check if the given string already exists in the $aNotes array.
+// If not, stores it as a new entry.
+// Either way, returns the corresponding key which is a small integer.
+function storeNote($s) {
+  global $aNotes;
+  $key = array_search($s, $aNotes);
+  if ($key !== FALSE) return $key;
+  $key = count($aNotes);
+  $aNotes[$key] = $s;
+  return $key;
+}
+
+if (!empty($_POST['form_sign_list'])) {
+  if (!acl_check('patients', 'sign')) {
+    die(xl('Not authorized to sign results'));
+  }
+  // When signing results we are careful to sign only those reports that were
+  // in the sending form. While this will usually be all the reports linked to
+  // the order it's possible for a new report to come in while viewing these,
+  // and it would be very bad to sign results that nobody has seen!
+  $arrSign = explode(',', $_POST['form_sign_list']);
+  foreach ($arrSign as $id) {
+  sqlStatement("UPDATE procedure_report SET " .
+    "review_status = 'reviewed' WHERE " .
+    "procedure_report_id = ?", array($id));
+  }
+}
+
+$orow = sqlQuery("SELECT " .
+  "po.procedure_order_id, po.date_ordered, po.diagnoses, " .
+  "po.order_status, po.specimen_type, " .
+  "pd.pubpid, pd.lname, pd.fname, pd.mname, " .
+  "pp.name AS labname, " .
+  "u.lname AS ulname, u.fname AS ufname, u.mname AS umname " .
+  "FROM procedure_order AS po " .
+  "LEFT JOIN patient_data AS pd ON pd.pid = po.patient_id " .
+  "LEFT JOIN procedure_providers AS pp ON pp.ppid = po.lab_id " .
+  "LEFT JOIN users AS u ON u.id = po.provider_id " .
+  "WHERE po.procedure_order_id = ?",
+  array($orderid));
+?>
+<html>
+
+<head>
+<?php html_header_show(); ?>
+
+<link rel="stylesheet" href='<?php  echo $css_header ?>' type='text/css'>
+<title><?php  xl('Order Results','e'); ?></title>
+
+<style>
+
+body {
+ margin: 9pt;
+ font-family: sans-serif; 
+ font-size: 1em;
+}
+
+tr.head   { font-size:10pt; background-color:#cccccc; text-align:center; }
+tr.detail { font-size:10pt; }
+a, a:visited, a:hover { color:#0000cc; }
+
+table {
+ border-style: solid;
+ border-width: 1px 0px 0px 1px;
+ border-color: black;
+}
+
+td, th {
+ border-style: solid;
+ border-width: 0px 1px 1px 0px;
+ border-color: black;
+}
+
+</style>
+
+<script type="text/javascript" src="../../library/dialog.js"></script>
+<script type="text/javascript" src="../../library/textformat.js"></script>
+
+<script language="JavaScript">
+
+var mypcc = '<?php echo $GLOBALS['phone_country_code'] ?>';
+
+</script>
+
+</head>
+
+<body>
+<form method='post' action='single_order_results.php?orderid=<?php echo $orderid; ?>'>
+
+<table width='100%' cellpadding='2' cellspacing='0'>
+ <tr bgcolor='#cccccc'>
+  <td width='5%' nowrap><?php echo xlt('Patient ID'); ?></td>
+  <td width='45%'><?php echo myCellText($orow['pubpid']); ?></td>
+  <td width='5%' nowrap><?php echo xlt('Order ID'); ?></td>
+  <td width='45%'><?php echo myCellText($orow['procedure_order_id']); ?></td>
+ </tr>
+ <tr bgcolor='#cccccc'>
+  <td nowrap><?php echo xlt('Patient Name'); ?></td>
+  <td><?php echo myCellText($orow['lname'] . ', ' . $orow['fname'] . ' ' . $orow['mname']); ?></td>
+  <td nowrap><?php echo xlt('Ordered By'); ?></td>
+  <td><?php echo myCellText($orow['ulname'] . ', ' . $orow['ufname'] . ' ' . $orow['umname']); ?></td>
+ </tr>
+ <tr bgcolor='#cccccc'>
+  <td nowrap><?php echo xlt('Order Date'); ?></td>
+  <td><?php echo myCellText(oeFormatShortDate($orow['date_ordered'])); ?></td>
+  <td nowrap><?php echo xlt('Print Date'); ?></td>
+  <td><?php echo oeFormatShortDate(date('Y-m-d')); ?></td>
+ </tr>
+ <tr bgcolor='#cccccc'>
+  <td nowrap><?php echo xlt('Order Status'); ?></td>
+  <td><?php echo myCellText($orow['order_status']); ?></td>
+  <td nowrap><?php echo xlt('Diagnoses'); ?></td>
+  <td><?php echo myCellText($orow['diagnoses']); ?></td>
+ </tr>
+ <tr bgcolor='#cccccc'>
+  <td nowrap><?php echo xlt('Lab'); ?></td>
+  <td><?php echo myCellText($orow['labname']); ?></td>
+  <td nowrap><?php echo xlt('Specimen Type'); ?></td>
+  <td><?php echo myCellText($orow['specimen_type']); ?></td>
+ </tr>
+</table>
+
+&nbsp;<br />
+
+<table width='100%' cellpadding='2' cellspacing='0'>
+
+ <tr class='head'>
+  <td rowspan='2' valign='middle'><?php echo xlt('Ordered Procedure'); ?></td>
+  <td colspan='4'><?php echo xlt('Report'); ?></td>
+  <td colspan='7'><?php echo xlt('Results'); ?></td>
+ </tr>
+
+ <tr class='head'>
+  <td><?php echo xlt('Reported'); ?></td>
+  <td><?php echo xlt('Specimen'); ?></td>
+  <td><?php echo xlt('Status'); ?></td>
+  <td><?php echo xlt('Note'); ?></td>
+  <td><?php echo xlt('Code'); ?></td>
+  <td><?php echo xlt('Name'); ?></td>
+  <td><?php echo xlt('Abn'); ?></td>
+  <td><?php echo xlt('Value'); ?></td>
+  <td><?php echo xlt('Range'); ?></td>
+  <td><?php echo xlt('Units'); ?></td>
+  <td><?php echo xlt('Note'); ?></td>
+ </tr>
+
+<?php 
+$query = "SELECT " .
+  "po.date_ordered, pc.procedure_order_seq, pc.procedure_code, " .
+  "pc.procedure_name, " .
+  "pr.procedure_report_id, pr.date_report, pr.date_collected, pr.specimen_num, " .
+  "pr.report_status, pr.review_status, pr.report_notes " .
+  "FROM procedure_order AS po " .
+  "JOIN procedure_order_code AS pc ON pc.procedure_order_id = po.procedure_order_id " .
+  "LEFT JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id AND " .
+  "pr.procedure_order_seq = pc.procedure_order_seq " .
+  "WHERE po.procedure_order_id = ? " .
+  "ORDER BY pc.procedure_order_seq, pr.procedure_report_id";
+
+$res = sqlStatement($query, array($orderid));
+
+$lastpoid = -1;
+$lastpcid = -1;
+$lastprid = -1;
+$encount = 0;
+$lino = 0;
+$extra_html = '';
+$aNotes = array();
+$sign_list = '';
+
+while ($row = sqlFetchArray($res)) {
+  $order_type_id  = empty($row['order_type_id'      ]) ? 0 : ($row['order_type_id' ] + 0);
+  $order_seq      = empty($row['procedure_order_seq']) ? 0 : ($row['procedure_order_seq'] + 0);
+  $report_id      = empty($row['procedure_report_id']) ? 0 : ($row['procedure_report_id'] + 0);
+  $procedure_code = empty($row['procedure_code'  ]) ? '' : $row['procedure_code'];
+  $procedure_name = empty($row['procedure_name'  ]) ? '' : $row['procedure_name'];
+  $date_report    = empty($row['date_report'     ]) ? '' : $row['date_report'];
+  $date_collected = empty($row['date_collected'  ]) ? '' : substr($row['date_collected'], 0, 16);
+  $specimen_num   = empty($row['specimen_num'    ]) ? '' : $row['specimen_num'];
+  $report_status  = empty($row['report_status'   ]) ? '' : $row['report_status']; 
+  $review_status  = empty($row['review_status'   ]) ? 'received' : $row['review_status'];
+
+  if ($review_status != 'reviewed') {
+    if ($sign_list) $sign_list .= ',';
+    $sign_list .= $report_id;
+  }
+
+  $report_noteid ='&nbsp;';
+  if (!empty($row['report_notes'])) {
+    $report_noteid = 1 + storeNote($row['report_notes']);
+  }
+
+  $query = "SELECT " .
+    "ps.result_code, ps.result_text, ps.abnormal, ps.result, " .
+    "ps.range, ps.result_status, ps.facility, ps.units, ps.comments " .
+    "FROM procedure_result AS ps " .
+    "WHERE ps.procedure_report_id = ? " .
+    "ORDER BY ps.result_code, ps.procedure_result_id";
+
+  $rres = sqlStatement($query, array($report_id));
+  $rrows = array();
+  while ($rrow = sqlFetchArray($rres)) {
+    $rrows[] = $rrow;
+  }
+  if (empty($rrows)) {
+    $rrows[0] = array('result_code' => '');
+  }
+
+  foreach ($rrows as $rrow) {
+    $result_code      = empty($rrow['result_code'     ]) ? '' : $rrow['result_code'];
+    $result_text      = empty($rrow['result_text'     ]) ? '' : $rrow['result_text'];
+    $result_abnormal  = empty($rrow['abnormal'        ]) ? '' : $rrow['abnormal'];
+    $result_result    = empty($rrow['result'          ]) ? '' : $rrow['result'];
+    $result_units     = empty($rrow['units'           ]) ? '' : $rrow['units'];
+    $result_facility  = empty($rrow['facility'        ]) ? '' : $rrow['facility'];
+    $result_comments  = empty($rrow['comments'        ]) ? '' : $rrow['comments'];
+    $result_range     = empty($rrow['range'           ]) ? '' : $rrow['range'];
+    $result_status    = empty($rrow['result_status'   ]) ? '' : $rrow['result_status'];
+
+    $result_comments = trim($result_comments);
+    $result_noteid = '&nbsp;';
+    if (!empty($result_comments)) {
+      $result_noteid = 1 + storeNote($result_comments);
+    }
+
+    if ($lastpoid != $order_id || $lastpcid != $order_seq) {
+      ++$encount;
+    }
+    $bgcolor = "#" . (($encount & 1) ? "ddddff" : "ffdddd");
+
+    echo " <tr class='detail' bgcolor='$bgcolor'>\n";
+
+    if ($lastpcid != $order_seq) {
+      $lastprid = -1; // force report fields on first line of each procedure
+      echo "  <td>" . htmlentities("$procedure_code: $procedure_name") . "</td>\n";
+    }
+    else {
+      echo "  <td style='background-color:transparent'>&nbsp;</td>";
+    }
+
+    // If this starts a new report or a new order, generate the report fields.
+    if ($report_id != $lastprid) {
+      echo "  <td>";
+      echo myCellText(oeFormatShortDate($date_report));
+      echo "</td>\n";
+
+      echo "  <td>";
+      echo myCellText($specimen_num);
+      echo "</td>\n";
+
+      echo "  <td title='" . xla('Check mark indicates reviewed') . "'>";
+      echo myCellText(getListItem('proc_rep_status', $report_status));
+      if ($row['review_status'] == 'reviewed') {
+        echo " &#x2713;"; // unicode check mark character
+      }
+      echo "</td>\n";
+
+      echo "  <td align='center'>";
+      echo $report_noteid;
+      echo "</td>\n";
+    }
+    else {
+      echo "  <td colspan='4' style='background-color:transparent'>&nbsp;</td>\n";
+    }
+
+    if ($result_code !== '') {
+      echo "  <td>";
+      echo myCellText($result_code);
+      echo "</td>\n";
+      echo "  <td>";
+      echo myCellText($result_text);
+      echo "</td>\n";
+      echo "  <td>";
+      echo myCellText(getListItem('proc_res_abnormal', $result_abnormal));
+      echo "</td>\n";
+      echo "  <td>";
+      echo myCellText($result_result);
+      echo "</td>\n";
+      echo "  <td>";
+      echo myCellText($result_range);
+      echo "</td>\n";
+      echo "  <td>";
+      echo myCellText($result_units);
+      echo "</td>\n";
+      echo "  <td align='center'>";
+      echo $result_noteid;
+      echo "</td>\n";
+    }
+    else {
+      echo "  <td colspan='7' style='background-color:transparent'>&nbsp;</td>\n";
+    }
+
+    echo " </tr>\n";
+
+    $lastpoid = $order_id;
+    $lastpcid = $order_seq;
+    $lastprid = $report_id;
+    ++$lino;
+  }
+}
+?>
+
+</table>
+
+&nbsp;<br />
+<table width='100%' style='border-width:0px;'>
+ <tr>
+  <td style='border-width:0px;'>
+<?php
+if (!empty($aNotes)) {
+  echo "<table cellpadding='3' cellspacing='0'>\n";
+  echo " <tr bgcolor='#cccccc'>\n";
+  echo "  <th align='center' colspan='2'>" . xlt('Notes') . "</th>\n";
+  echo " </tr>\n";
+  foreach ($aNotes as $key => $value) {
+    echo " <tr>\n";
+    echo "  <td valign='top'>" . ($key + 1) . "</td>\n";
+    echo "  <td>" . nl2br(text($value)) . "</td>\n";
+    echo " </tr>\n";
+  }
+  echo "</table>\n";
+}
+?>
+  </td>
+  <td style='border-width:0px;' align='right' valign='top'>
+<?php if ($sign_list) { ?>
+   <input type='hidden' name='form_sign_list' value='<?php echo attr($sign_list); ?>' />
+   <input type='submit' name='form_sign' value='<?php echo xla('Sign Results'); ?>'
+    title='<?php echo xla('Mark these reports as reviewed'); ?>' />
+<?php } ?>
+  </td>
+ </tr>
+</table>
+
+</form>
+</body>
+</html>
index ae536f1..1a4f7f9 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010 Rod Roark <rod@sunsetsystems.com>
+// Copyright (C) 2010-2012 Rod Roark <rod@sunsetsystems.com>
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -17,6 +17,7 @@ require_once("$srcdir/formdata.inc.php");
 
 $popup = empty($_GET['popup']) ? 0 : 1;
 $order = formData('order', 'G') + 0;
+$labid = formData('labid', 'G') + 0;
 
 // If Save was clicked, set the result, close the window and exit.
 //
@@ -27,11 +28,24 @@ if ($popup && $_POST['form_save']) {
   $name = addslashes($ptrow['name']);
 ?>
 <script language="JavaScript">
-if (opener.closed || ! opener.set_proc_type)
+if (opener.closed || ! opener.set_proc_type) {
  alert('<?php xl('The destination form was closed; I cannot act on your selection.','e'); ?>');
-else
+}
+else {
  opener.set_proc_type(<?php echo "$form_order, '$name'" ?>);
-window.close();
+<?php
+// This is to generate the "Questions at Order Entry" for the Procedure Order form.
+// GET parms needed for this are: formid, formseq.
+if (isset($_GET['formid'])) {
+  require_once("qoe.inc.php");
+  $qoe_init_javascript = '';
+  echo ' opener.set_proc_html("';
+  echo generate_qoe_html($form_order, intval($_GET['formid']), 0, intval($_GET['formseq']));
+  echo '", "' . $qoe_init_javascript .  '");' . "\n";
+}
+?>
+}
+window.close(); // comment out for debugging
 </script>
 <?php
   exit();
@@ -128,7 +142,7 @@ function nextOpen() {
    if (thisid > 0)
     toggle(thisid);
    else
-    $.getScript('types_ajax.php?id=' + thisid + '&order=<?php echo $order; ?>');
+    $.getScript('types_ajax.php?id=' + thisid + '&order=<?php echo $order; ?>' + '&labid=<?php echo $labid; ?>');
   }
   else {
    recolor();
@@ -160,7 +174,7 @@ function toggle(id) {
   td1.parent().after('<tr class="outertr"><td colspan="5" id="con' + id + '" style="padding:0">Loading...</td></tr>');
   td1.addClass('isExpanded');
   swapsign(td1, '+', '-');
-  $.getScript('types_ajax.php?id=' + id + '&order=<?php echo $order; ?>');
+  $.getScript('types_ajax.php?id=' + id + '&order=<?php echo $order; ?>' + '&labid=<?php echo $labid; ?>');
  }
 }
 
@@ -191,7 +205,7 @@ function refreshFamily(id, haskids) {
   }
  }
  if (haskids)
-  $.getScript('types_ajax.php?id=' + id + '&order=<?php echo $order; ?>');
+  $.getScript('types_ajax.php?id=' + id + '&order=<?php echo $order; ?>' + '&labid=<?php echo $labid; ?>');
  else
   recolor();
 }
@@ -225,7 +239,11 @@ function recolor() {
 
 <h3 style='margin-top:0'><?php xl('Types of Orders and Results','e') ?></h3>
 
-<form method='post' name='theform' action='types.php?popup=<?php echo $popup ?>&order=<?php echo $order ?>'>
+<form method='post' name='theform' action='types.php?popup=<?php echo $popup ?>&order=<?php
+echo $order;
+if (isset($_GET['formid' ])) echo '&formid='  . $_GET['formid'];
+if (isset($_GET['formseq'])) echo '&formseq=' . $_GET['formseq'];
+?>'>
 
 <table width='100%' cellspacing='0' cellpadding='0' border='0'>
  <tr class='head'>
index c9d3cd9..eee53cd 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010 Rod Roark <rod@sunsetsystems.com>
+// Copyright (C) 2010-2012 Rod Roark <rod@sunsetsystems.com>
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -11,6 +11,7 @@ require_once("$srcdir/formdata.inc.php");
 
 $id = formData('id','G') + 0;
 $order = formData('order','G') + 0;
+$labid = formData('labid','G') + 0;
 
 echo "$('#con$id').html('<table width=\"100%\" cellspacing=\"0\">";
 
@@ -45,11 +46,11 @@ while ($row = sqlFetchArray($res)) {
   echo "<span style=\"margin:0 4 0 " . ($level * 9) . "pt\" class=\"plusminus\">";
   echo $iscontainer ? "+" : '|';
   echo "</span>";
-  echo $row['name'] . "</td>";
+  echo htmlspecialchars($row['name'], ENT_QUOTES) . "</td>";
   //
   echo "<td class=\"col2\">";
   if (substr($row['procedure_type'], 0, 3) == 'ord') {
-    if ($order) {
+    if ($order && ($labid == 0 || $row['lab_id'] == $labid)) {
       echo "<input type=\"radio\" name=\"form_order\" value=\"$chid\"";
       if ($chid == $order) echo " checked";
       echo " />";
@@ -63,8 +64,8 @@ while ($row = sqlFetchArray($res)) {
   }
   echo "</td>";
   //
-  echo "<td class=\"col3\">" . $row['procedure_code'] . "</td>";
-  echo "<td class=\"col4\">" . $row['description'] . "</td>";
+  echo "<td class=\"col3\">" . htmlspecialchars($row['procedure_code'], ENT_QUOTES) . "</td>";
+  echo "<td class=\"col4\">" . htmlspecialchars($row['description'], ENT_QUOTES) . "</td>";
   echo "<td class=\"col5\">";
   echo "<span onclick=\"enode($chid)\" class=\"haskids\">[" . xl('Edit') . "]</span>";
   echo "<span onclick=\"anode($chid)\" class=\"haskids\"> [" . xl('Add') . "]</span>";
@@ -72,7 +73,6 @@ while ($row = sqlFetchArray($res)) {
   echo "</tr>";
 }
 
-echo "</table>');\n"; // end of html argument
-
+echo "</table>');\n";
 echo "nextOpen();\n";
 ?>
index 7d1208b..21b5a33 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010 Rod Roark <rod@sunsetsystems.com>
+// Copyright (C) 2010-2012 Rod Roark <rod@sunsetsystems.com>
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -58,7 +58,7 @@ function recursiveDelete($typeid) {
 ?>
 <html>
 <head>
-<title><?php echo $typeid ? xl('Edit') : xl('Add New') ?> <?php xl('Order/Result Type','e'); ?></title>
+<title><?php echo $typeid ? xlt('Edit') : xlt('Add New'); ?> <?php echo xlt('Order/Result Type'); ?></title>
 <link rel="stylesheet" href='<?php echo $css_header ?>' type='text/css'>
 
 <style>
@@ -204,7 +204,7 @@ if ($typeid) {
 <table border='0' width='100%'>
 
  <tr>
-  <td width='1%' nowrap><b><?php xl('Procedure Type','e'); ?>:</b></td>
+  <td width='1%' nowrap><b><?php echo xlt('Procedure Type'); ?>:</b></td>
   <td>
 <?php
 echo generate_select_list('form_procedure_type', 'proc_type', $row['procedure_type'],
@@ -214,70 +214,74 @@ echo generate_select_list('form_procedure_type', 'proc_type', $row['procedure_ty
  </tr>
 
  <tr>
-  <td nowrap><b><?php xl('Name','e'); ?>:</b></td>
+  <td nowrap><b><?php echo xlt('Name'); ?>:</b></td>
   <td>
    <input type='text' size='40' name='form_name' maxlength='63'
     value='<?php echo htmlspecialchars($row['name'], ENT_QUOTES); ?>'
-    title='<?php xl('Your name for this category, procedure or result','e'); ?>'
+    title='<?php echo xlt('Your name for this category, procedure or result'); ?>'
     style='width:100%' class='inputtext' />
   </td>
  </tr>
 
  <tr>
-  <td nowrap><b><?php xl('Description','e'); ?>:</b></td>
+  <td nowrap><b><?php echo xlt('Description'); ?>:</b></td>
   <td>
    <input type='text' size='40' name='form_description' maxlength='255'
     value='<?php echo htmlspecialchars($row['description'], ENT_QUOTES); ?>'
-    title='<?php xl('Description of this procedure or result code','e'); ?>'
+    title='<?php echo xlt('Description of this procedure or result code'); ?>'
     style='width:100%' class='inputtext' />
   </td>
  </tr>
 
  <tr>
-  <td nowrap><b><?php xl('Sequence','e'); ?>:</b></td>
+  <td nowrap><b><?php echo xlt('Sequence'); ?>:</b></td>
   <td>
    <input type='text' size='4' name='form_seq' maxlength='11'
     value='<?php echo $row['seq'] + 0; ?>'
-    title='<?php xl('Relative ordering of this entity','e'); ?>'
+    title='<?php echo xla('Relative ordering of this entity'); ?>'
     class='inputtext' />
   </td>
  </tr>
 
  <tr class='ordonly'>
-  <td width='1%' nowrap><b><?php xl('Order From','e'); ?>:</b></td>
+  <td width='1%' nowrap><b><?php echo xlt('Order From'); ?>:</b></td>
   <td>
-<?php
-// Address book entries for procedure ordering.
-generate_form_field(array('data_type' => 14, 'field_id' => 'lab_id',
-  'list_id' => '', 'edit_options' => 'O',
-  'description' => xl('Address book entry for the company performing this procedure')),
-  $row['lab_id']);
+   <select name='form_lab_id' title='<?php echo xla('The entity performing this procedure'); ?>'>
+ <?php
+  $ppres = sqlStatement("SELECT ppid, name FROM procedure_providers " .
+    "ORDER BY name, ppid");
+  while ($pprow = sqlFetchArray($ppres)) {
+    echo "<option value='" . attr($pprow['ppid']) . "'";
+    if ($pprow['ppid'] == $row['lab_id']) echo " selected";
+    echo ">" . text($pprow['name']) . "</option>";
+  }
 ?>
+   </select>
   </td>
  </tr>
 
  <tr class='ordonly'>
-  <td nowrap><b><?php xl('Procedure Code','e'); ?>:</b></td>
+  <td nowrap><b><?php echo xlt('Procedure Code'); ?>:</b></td>
   <td>
    <input type='text' size='40' name='form_procedure_code' maxlength='31'
     value='<?php echo htmlspecialchars($row['procedure_code'], ENT_QUOTES); ?>'
-    title='<?php xl('The vendor-specific code identifying this procedure or result','e'); ?>'
+    title='<?php echo xla('The vendor-specific code identifying this procedure or result'); ?>'
     style='width:100%' class='inputtext' />
   </td>
  </tr>
 
  <tr class='ordonly'>
-  <td nowrap><b><?php xl('Standard Code','e'); ?>:</b></td>
+  <td nowrap><b><?php echo xlt('Standard Code'); ?>:</b></td>
   <td>
    <input type='text' size='50' name='form_standard_code'
     value='<?php echo $row['standard_code'] ?>' onclick='sel_related("form_standard_code")'
-    title='<?php xl('Click to select an industry-standard code for this procedure','e'); ?>'
+    title='<?php echo xla('Click to select an industry-standard code for this procedure'); ?>'
     style='width:100%' readonly />
   </td>
  </tr>
 
  <tr class='ordonly'>
-  <td width='1%' nowrap><b><?php xl('Body Site','e'); ?>:</b></td>
+  <td width='1%' nowrap><b><?php echo xlt('Body Site'); ?>:</b></td>
   <td>
 <?php
 generate_form_field(array('data_type' => 1, 'field_id' => 'body_site',
@@ -288,7 +292,7 @@ generate_form_field(array('data_type' => 1, 'field_id' => 'body_site',
  </tr>
 
  <tr class='ordonly'>
-  <td width='1%' nowrap><b><?php xl('Specimen Type','e'); ?>:</b></td>
+  <td width='1%' nowrap><b><?php echo xlt('Specimen Type'); ?>:</b></td>
   <td>
 <?php
 generate_form_field(array('data_type' => 1, 'field_id' => 'specimen',
@@ -300,7 +304,7 @@ generate_form_field(array('data_type' => 1, 'field_id' => 'specimen',
  </tr>
 
  <tr class='ordonly'>
-  <td width='1%' nowrap><b><?php xl('Administer Via','e'); ?>:</b></td>
+  <td width='1%' nowrap><b><?php echo xlt('Administer Via'); ?>:</b></td>
   <td>
 <?php
 generate_form_field(array('data_type' => 1, 'field_id' => 'route_admin',
@@ -312,7 +316,7 @@ generate_form_field(array('data_type' => 1, 'field_id' => 'route_admin',
  </tr>
 
  <tr class='ordonly'>
-  <td width='1%' nowrap><b><?php xl('Laterality','e'); ?>:</b></td>
+  <td width='1%' nowrap><b><?php echo xlt('Laterality'); ?>:</b></td>
   <td>
 <?php
 generate_form_field(array('data_type' => 1, 'field_id' => 'laterality',
@@ -324,7 +328,7 @@ generate_form_field(array('data_type' => 1, 'field_id' => 'laterality',
  </tr>
 
  <tr class='resonly'>
-  <td width='1%' nowrap><b><?php xl('Default Units','e'); ?>:</b></td>
+  <td width='1%' nowrap><b><?php echo xlt('Default Units'); ?>:</b></td>
   <td>
 <?php
 generate_form_field(array('data_type' => 1, 'field_id' => 'units',
@@ -336,21 +340,21 @@ generate_form_field(array('data_type' => 1, 'field_id' => 'units',
  </tr>
 
  <tr class='resonly'>
-  <td nowrap><b><?php xl('Default Range','e'); ?>:</b></td>
+  <td nowrap><b><?php echo xlt('Default Range'); ?>:</b></td>
   <td>
    <input type='text' size='40' name='form_range' maxlength='255'
     value='<?php echo htmlspecialchars($row['range'], ENT_QUOTES); ?>'
-    title='<?php xl('Optional default range for manual entry of results','e'); ?>'
+    title='<?php echo xla('Optional default range for manual entry of results'); ?>'
     style='width:100%' class='inputtext' />
   </td>
  </tr>
 
  <tr class='resonly'>
-  <td nowrap><b><?php xl('Followup Services','e'); ?>:</b></td>
+  <td nowrap><b><?php echo xlt('Followup Services'); ?>:</b></td>
   <td>
    <input type='text' size='50' name='form_related_code'
     value='<?php echo $row['related_code'] ?>' onclick='sel_related("form_related_code")'
-    title='<?php xl('Click to select services to perform if this result is abnormal','e'); ?>'
+    title='<?php echo xla('Click to select services to perform if this result is abnormal'); ?>'
     style='width:100%' readonly />
   </td>
  </tr>
@@ -359,15 +363,15 @@ generate_form_field(array('data_type' => 1, 'field_id' => 'units',
 
 <br />
 
-<input type='submit' name='form_save' value=<?php xl('Save','e','\'','\''); ?> />
+<input type='submit' name='form_save' value='<?php echo xla('Save'); ?>' />
 
 <?php if ($typeid) { ?>
 &nbsp;
-<input type='submit' name='form_delete' value=<?php xl('Delete','e','\'','\''); ?> style='color:red' />
+<input type='submit' name='form_delete' value='<?php echo xla('Delete'); ?>' style='color:red' />
 <?php } ?>
 
 &nbsp;
-<input type='button' value=<?php xl('Cancel','e','\'','\''); ?> onclick='window.close()' />
+<input type='button' value='<?php echo xla('Cancel'); ?>' onclick='window.close()' />
 </p>
 
 </center>
index bd136ba..d0a014f 100644 (file)
@@ -74,7 +74,6 @@ ALTER TABLE `version` ADD COLUMN `v_acl` int(11) NOT NULL DEFAULT 0;
 ALTER TABLE `documents_legal_detail` ADD COLUMN `dld_moved` tinyint(4) NOT NULL DEFAULT '0'; 
 #EndIf
 
-
 #IfMissingColumn documents_legal_detail dld_patient_comments
 ALTER TABLE `documents_legal_detail` ADD COLUMN `dld_patient_comments` text COMMENT 'Patient comments stored here';
 #EndIf
@@ -128,3 +127,146 @@ ALTER TABLE `billing` CHANGE `code` `code` varchar(20) default NULL;
 ALTER TABLE `ar_activity` CHANGE `code` `code` varchar(20) NOT NULL COMMENT 'empty means claim level';
 #EndIf
 
+#IfNotTable procedure_questions
+CREATE TABLE `procedure_questions` (
+  `lab_id`              bigint(20)   NOT NULL DEFAULT 0   COMMENT 'references users.id to identify the lab',
+  `procedure_code`      varchar(31)  NOT NULL DEFAULT ''  COMMENT 'references procedure_type.procedure_code to identify this order type',
+  `question_code`       varchar(31)  NOT NULL DEFAULT ''  COMMENT 'code identifying this question',
+  `seq`                 int(11)      NOT NULL default 0   COMMENT 'sequence number for ordering',
+  `question_text`       varchar(255) NOT NULL DEFAULT ''  COMMENT 'descriptive text for question_code',
+  `required`            tinyint(1)   NOT NULL DEFAULT 0   COMMENT '1 = required, 0 = not',
+  `maxsize`             int          NOT NULL DEFAULT 0   COMMENT 'maximum length if text input field',
+  `fldtype`             char(1)      NOT NULL DEFAULT 'T' COMMENT 'Text, Number, Select, Multiselect, Date, Gestational-age',
+  `options`             text         NOT NULL DEFAULT ''  COMMENT 'choices for fldtype S and T',
+  `activity`            tinyint(1)   NOT NULL DEFAULT 1   COMMENT '1 = active, 0 = inactive',
+  PRIMARY KEY (`lab_id`, `procedure_code`, `question_code`)
+) ENGINE=MyISAM;
+#EndIf
+
+#IfMissingColumn procedure_type activity
+ALTER TABLE `procedure_type` ADD COLUMN `activity` tinyint(1) NOT NULL default 1;
+#EndIf
+
+#IfMissingColumn procedure_type notes
+ALTER TABLE `procedure_type` ADD COLUMN `notes` varchar(255) NOT NULL default '';
+#EndIf
+
+#IfNotTable procedure_answers
+CREATE TABLE `procedure_answers` (
+  `procedure_order_id`  bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references procedure_order.procedure_order_id',
+  `procedure_order_seq` int(11)      NOT NULL DEFAULT 1  COMMENT 'references procedure_order_code.seq',
+  `question_code`       varchar(31)  NOT NULL DEFAULT '' COMMENT 'references procedure_questions.question_code',
+  `answer_seq`          int(11)      NOT NULL AUTO_INCREMENT COMMENT 'supports multiple-choice questions',
+  `answer`              varchar(255) NOT NULL DEFAULT '' COMMENT 'answer data',
+  PRIMARY KEY (`procedure_order_id`, `procedure_order_seq`, `question_code`, `answer_seq`)
+) ENGINE=MyISAM;
+#EndIf
+
+#IfNotTable procedure_providers
+CREATE TABLE `procedure_providers` (
+  `ppid`         bigint(20)   NOT NULL auto_increment,
+  `name`         varchar(255) NOT NULL DEFAULT '',
+  `npi`          varchar(15)  NOT NULL DEFAULT '',
+  `protocol`     varchar(15)  NOT NULL DEFAULT 'DL',
+  `login`        varchar(255) NOT NULL DEFAULT '',
+  `password`     varchar(255) NOT NULL DEFAULT '',
+  `orders_path`  varchar(255) NOT NULL DEFAULT '',
+  `results_path` varchar(255) NOT NULL DEFAULT '',
+  `notes`        text         NOT NULL DEFAULT '',
+  PRIMARY KEY (`ppid`)
+) ENGINE=MyISAM;
+#EndIf
+
+#IfNotTable procedure_order_code
+CREATE TABLE `procedure_order_code` (
+  `procedure_order_id`  bigint(20)  NOT NULL,
+  `procedure_order_seq` int(11)     NOT NULL AUTO_INCREMENT COMMENT 'supports multiple tests per order',
+  `procedure_type_id`   bigint(20)  NOT NULL                COMMENT 'references procedure_type.procedure_type_id',
+  `procedure_code`      varchar(31) NOT NULL DEFAULT ''     COMMENT 'copy of procedure_type.procedure_code',
+  PRIMARY KEY (`procedure_order_id`, `procedure_order_seq`)
+) ENGINE=MyISAM;
+INSERT INTO procedure_order_code
+  SELECT po.procedure_order_id, 1, po.procedure_type_id, pt.procedure_code
+  FROM procedure_order AS po
+  LEFT JOIN procedure_type AS pt ON pt.procedure_type_id = po.procedure_type_id;
+ALTER TABLE `procedure_order`
+  DROP COLUMN `procedure_type_id`;
+#EndIf
+
+#IfMissingColumn procedure_order lab_id
+ALTER TABLE `procedure_order`
+  ADD COLUMN `lab_id`            bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references procedure_providers.ppid',
+  ADD COLUMN `specimen_type`     varchar(31)  NOT NULL DEFAULT '' COMMENT 'from the Specimen_Type list',
+  ADD COLUMN `specimen_location` varchar(31)  NOT NULL DEFAULT '' COMMENT 'from the Specimen_Location list',
+  ADD COLUMN `specimen_volume`   varchar(30)  NOT NULL DEFAULT '' COMMENT 'from a text input field';
+UPDATE procedure_order AS po, procedure_order_code AS pc, procedure_type AS pt
+  SET po.lab_id = pt.lab_id WHERE
+  po.lab_id = 0 AND
+  pc.procedure_order_id = po.procedure_order_id AND
+  pt.procedure_type_id = pc.procedure_type_id AND
+  pt.lab_id != 0;
+#EndIf
+
+#IfMissingColumn procedure_report procedure_order_seq
+ALTER TABLE procedure_report
+  ADD COLUMN `procedure_order_seq` int(11) NOT NULL DEFAULT 1 COMMENT 'references procedure_order_code.procedure_order_seq';
+#EndIf
+
+#IfMissingColumn procedure_order diagnoses
+ALTER TABLE `procedure_order`
+  ADD COLUMN `diagnoses` text NOT NULL DEFAULT '' COMMENT 'diagnoses and maybe other coding (e.g. ICD9:111.11)';
+#EndIf
+
+#IfMissingColumn procedure_providers remote_host
+ALTER TABLE `procedure_providers`
+  ADD COLUMN `remote_host` varchar(255)  NOT NULL DEFAULT ''  COMMENT 'IP or hostname of remote server',
+  ADD COLUMN `send_app_id` varchar(255)  NOT NULL DEFAULT ''  COMMENT 'Sending application ID (MSH-3.1)',
+  ADD COLUMN `send_fac_id` varchar(255)  NOT NULL DEFAULT ''  COMMENT 'Sending facility ID (MSH-4.1)',
+  ADD COLUMN `recv_app_id` varchar(255)  NOT NULL DEFAULT ''  COMMENT 'Receiving application ID (MSH-5.1)',
+  ADD COLUMN `recv_fac_id` varchar(255)  NOT NULL DEFAULT ''  COMMENT 'Receiving facility ID (MSH-6.1)',
+  ADD COLUMN `DorP`        char(1)       NOT NULL DEFAULT 'D' COMMENT 'Debugging or Production (MSH-11)';
+#EndIf
+
+#IfMissingColumn procedure_order_code procedure_source
+ALTER TABLE `procedure_order_code`
+  ADD COLUMN `procedure_source` char(1) NOT NULL DEFAULT '1' COMMENT '1=original order, 2=added after order sent';
+#EndIf
+
+#IfMissingColumn procedure_result result_code
+ALTER TABLE `procedure_result`
+  ADD COLUMN `result_data_type` char(1) NOT NULL DEFAULT 'S' COMMENT
+  'N=Numeric, S=String, F=Formatted, E=External, L=Long text as first line of comments',
+  ADD COLUMN `result_code` varchar(31) NOT NULL DEFAULT '' COMMENT
+  'LOINC code, might match a procedure_type.procedure_code',
+  ADD COLUMN `result_text` varchar(255) NOT NULL DEFAULT '' COMMENT
+  'Description of result_code';
+# This severs the link between procedure_result and procedure_type:
+UPDATE procedure_result AS ps, procedure_type AS pt
+  SET ps.result_code = pt.procedure_code, ps.result_text = pt.description
+  WHERE pt.procedure_type_id = ps.procedure_type_id;
+ALTER TABLE `procedure_result` DROP COLUMN procedure_type_id;
+#EndIf
+
+#IfMissingColumn procedure_questions tips
+ALTER TABLE `procedure_questions`
+  ADD COLUMN `tips` varchar(255) NOT NULL DEFAULT '' COMMENT 'Additional instructions for answering the question';
+#EndIf
+
+#IfMissingColumn procedure_order_code procedure_name
+ALTER TABLE `procedure_order_code`
+  ADD COLUMN `procedure_name` varchar(255) NOT NULL DEFAULT '' COMMENT
+  'Descriptive name of procedure_code';
+# This severs the link between procedure_order_code and procedure_type:
+UPDATE procedure_order_code AS pc, procedure_order AS po, procedure_type AS pt
+  SET pc.procedure_name = pt.name
+  WHERE po.procedure_order_id = pc.procedure_order_id AND
+  pt.lab_id = po.lab_id AND
+  pt.procedure_code = pc.procedure_code;
+ALTER TABLE `procedure_order_code` DROP COLUMN procedure_type_id;
+#EndIf
+
+#IfMissingColumn procedure_report report_notes
+ALTER TABLE procedure_report
+  ADD COLUMN `report_notes` text NOT NULL DEFAULT '' COMMENT 'Notes from the lab';
+#EndIf
+
index cedf3ab..280f472 100644 (file)
@@ -5332,11 +5332,30 @@ CREATE TABLE gprelations (
   KEY key2  (type2,id2)
 ) ENGINE=MyISAM COMMENT='general purpose relations';
 
+CREATE TABLE `procedure_providers` (
+  `ppid`         bigint(20)   NOT NULL auto_increment,
+  `name`         varchar(255) NOT NULL DEFAULT '',
+  `npi`          varchar(15)  NOT NULL DEFAULT '',
+  `send_app_id`  varchar(255) NOT NULL DEFAULT ''  COMMENT 'Sending application ID (MSH-3.1)',
+  `send_fac_id`  varchar(255) NOT NULL DEFAULT ''  COMMENT 'Sending facility ID (MSH-4.1)',
+  `recv_app_id`  varchar(255) NOT NULL DEFAULT ''  COMMENT 'Receiving application ID (MSH-5.1)',
+  `recv_fac_id`  varchar(255) NOT NULL DEFAULT ''  COMMENT 'Receiving facility ID (MSH-6.1)',
+  `DorP`         char(1)      NOT NULL DEFAULT 'D' COMMENT 'Debugging or Production (MSH-11)',
+  `protocol`     varchar(15)  NOT NULL DEFAULT 'DL',
+  `remote_host`  varchar(255) NOT NULL DEFAULT '',
+  `login`        varchar(255) NOT NULL DEFAULT '',
+  `password`     varchar(255) NOT NULL DEFAULT '',
+  `orders_path`  varchar(255) NOT NULL DEFAULT '',
+  `results_path` varchar(255) NOT NULL DEFAULT '',
+  `notes`        text         NOT NULL DEFAULT '',
+  PRIMARY KEY (`ppid`)
+) ENGINE=MyISAM;
+
 CREATE TABLE `procedure_type` (
   `procedure_type_id`   bigint(20)   NOT NULL AUTO_INCREMENT,
   `parent`              bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references procedure_type.procedure_type_id',
   `name`                varchar(63)  NOT NULL DEFAULT '' COMMENT 'name for this category, procedure or result type',
-  `lab_id`              bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references users.id, 0 means default to parent',
+  `lab_id`              bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references procedure_providers.ppid, 0 means default to parent',
   `procedure_code`      varchar(31)  NOT NULL DEFAULT '' COMMENT 'code identifying this procedure',
   `procedure_type`      varchar(31)  NOT NULL DEFAULT '' COMMENT 'see list proc_type',
   `body_site`           varchar(31)  NOT NULL DEFAULT '' COMMENT 'where to do injection, e.g. arm, buttok',
@@ -5349,37 +5368,78 @@ CREATE TABLE `procedure_type` (
   `units`               varchar(31)  NOT NULL DEFAULT '' COMMENT 'default for procedure_result.units',
   `range`               varchar(255) NOT NULL DEFAULT '' COMMENT 'default for procedure_result.range',
   `seq`                 int(11)      NOT NULL default 0  COMMENT 'sequence number for ordering',
+  `activity`            tinyint(1)   NOT NULL default 1  COMMENT '1=active, 0=inactive',
+  `notes`               varchar(255) NOT NULL default '' COMMENT 'additional notes to enhance description',
   PRIMARY KEY (`procedure_type_id`),
   KEY parent (parent)
 ) ENGINE=MyISAM;
 
+CREATE TABLE `procedure_questions` (
+  `lab_id`              bigint(20)   NOT NULL DEFAULT 0   COMMENT 'references procedure_providers.ppid to identify the lab',
+  `procedure_code`      varchar(31)  NOT NULL DEFAULT ''  COMMENT 'references procedure_type.procedure_code to identify this order type',
+  `question_code`       varchar(31)  NOT NULL DEFAULT ''  COMMENT 'code identifying this question',
+  `seq`                 int(11)      NOT NULL default 0   COMMENT 'sequence number for ordering',
+  `question_text`       varchar(255) NOT NULL DEFAULT ''  COMMENT 'descriptive text for question_code',
+  `required`            tinyint(1)   NOT NULL DEFAULT 0   COMMENT '1 = required, 0 = not',
+  `maxsize`             int          NOT NULL DEFAULT 0   COMMENT 'maximum length if text input field',
+  `fldtype`             char(1)      NOT NULL DEFAULT 'T' COMMENT 'Text, Number, Select, Multiselect, Date, Gestational-age',
+  `options`             text         NOT NULL DEFAULT ''  COMMENT 'choices for fldtype S and T',
+  `tips`                varchar(255) NOT NULL DEFAULT ''  COMMENT 'Additional instructions for answering the question',
+  `activity`            tinyint(1)   NOT NULL DEFAULT 1   COMMENT '1 = active, 0 = inactive',
+  PRIMARY KEY (`lab_id`, `procedure_code`, `question_code`)
+) ENGINE=MyISAM;
+
 CREATE TABLE `procedure_order` (
   `procedure_order_id`     bigint(20)   NOT NULL AUTO_INCREMENT,
-  `procedure_type_id`      bigint(20)   NOT NULL            COMMENT 'references procedure_type.procedure_type_id',
-  `provider_id`            bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references users.id',
+  `provider_id`            bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references users.id, the ordering provider',
   `patient_id`             bigint(20)   NOT NULL            COMMENT 'references patient_data.pid',
   `encounter_id`           bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references form_encounter.encounter',
   `date_collected`         datetime     DEFAULT NULL        COMMENT 'time specimen collected',
   `date_ordered`           date         DEFAULT NULL,
   `order_priority`         varchar(31)  NOT NULL DEFAULT '',
   `order_status`           varchar(31)  NOT NULL DEFAULT '' COMMENT 'pending,routed,complete,canceled',
+  `diagnoses`              text         NOT NULL DEFAULT '' COMMENT 'diagnoses and maybe other coding (e.g. ICD9:111.11)',
   `patient_instructions`   text         NOT NULL DEFAULT '',
   `activity`               tinyint(1)   NOT NULL DEFAULT 1  COMMENT '0 if deleted',
   `control_id`             bigint(20)   NOT NULL            COMMENT 'This is the CONTROL ID that is sent back from lab',
+  `lab_id`                 bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references procedure_providers.ppid',
+  `specimen_type`          varchar(31)  NOT NULL DEFAULT '' COMMENT 'from the Specimen_Type list',
+  `specimen_location`      varchar(31)  NOT NULL DEFAULT '' COMMENT 'from the Specimen_Location list',
+  `specimen_volume`        varchar(30)  NOT NULL DEFAULT '' COMMENT 'from a text input field';
   PRIMARY KEY (`procedure_order_id`),
   KEY datepid (date_ordered, patient_id),
   KEY `patient_id` (`patient_id`)
 ) ENGINE=MyISAM;
 
+CREATE TABLE `procedure_order_code` (
+  `procedure_order_id`  bigint(20)  NOT NULL                COMMENT 'references procedure_order.procedure_order_id',
+  `procedure_order_seq` int(11)     NOT NULL AUTO_INCREMENT COMMENT 'supports multiple tests per order',
+  `procedure_code`      varchar(31) NOT NULL DEFAULT ''     COMMENT 'like procedure_type.procedure_code',
+  `procedure_name`      varchar(255) NOT NULL DEFAULT ''    COMMENT 'descriptive name of the procedure code',
+  `procedure_source`    char(1)     NOT NULL DEFAULT '1'    COMMENT '1=original order, 2=added after order sent',
+  PRIMARY KEY (`procedure_order_id`, `procedure_order_seq`)
+) ENGINE=MyISAM;
+
+CREATE TABLE `procedure_answers` (
+  `procedure_order_id`  bigint(20)   NOT NULL DEFAULT 0  COMMENT 'references procedure_order.procedure_order_id',
+  `procedure_order_seq` int(11)      NOT NULL DEFAULT 0  COMMENT 'references procedure_order_code.procedure_order_seq',
+  `question_code`       varchar(31)  NOT NULL DEFAULT '' COMMENT 'references procedure_questions.question_code',
+  `answer_seq`          int(11)      NOT NULL AUTO_INCREMENT COMMENT 'supports multiple-choice questions',
+  `answer`              varchar(255) NOT NULL DEFAULT '' COMMENT 'answer data',
+  PRIMARY KEY (`procedure_order_id`, `procedure_order_seq`, `question_code`, `answer_seq`)
+) ENGINE=MyISAM;
+
 CREATE TABLE `procedure_report` (
   `procedure_report_id` bigint(20)     NOT NULL AUTO_INCREMENT,
   `procedure_order_id`  bigint(20)     DEFAULT NULL   COMMENT 'references procedure_order.procedure_order_id',
+  `procedure_order_seq` int(11)        NOT NULL DEFAULT 1  COMMENT 'references procedure_order_code.procedure_order_seq',
   `date_collected`      datetime       DEFAULT NULL,
   `date_report`         date           DEFAULT NULL,
   `source`              bigint(20)     NOT NULL DEFAULT 0  COMMENT 'references users.id, who entered this data',
   `specimen_num`        varchar(63)    NOT NULL DEFAULT '',
   `report_status`       varchar(31)    NOT NULL DEFAULT '' COMMENT 'received,complete,error',
-  `review_status`       varchar(31)    NOT NULL DEFAULT 'received' COMMENT 'panding reivew status: received,reviewed',  
+  `review_status`       varchar(31)    NOT NULL DEFAULT 'received' COMMENT 'pending review status: received,reviewed',  
+  `report_notes`        text           NOT NULL DEFAULT '' COMMENT 'notes from the lab',
   PRIMARY KEY (`procedure_report_id`),
   KEY procedure_order_id (procedure_order_id)
 ) ENGINE=MyISAM; 
@@ -5387,7 +5447,9 @@ CREATE TABLE `procedure_report` (
 CREATE TABLE `procedure_result` (
   `procedure_result_id` bigint(20)   NOT NULL AUTO_INCREMENT,
   `procedure_report_id` bigint(20)   NOT NULL            COMMENT 'references procedure_report.procedure_report_id',
-  `procedure_type_id`   bigint(20)   NOT NULL            COMMENT 'references procedure_type.procedure_type_id',
+  `result_data_type`    char(1)      NOT NULL DEFAULT 'S' COMMENT 'N=Numeric, S=String, F=Formatted, E=External, L=Long text as first line of comments',
+  `result_code`         varchar(31)  NOT NULL DEFAULT '' COMMENT 'LOINC code, might match a procedure_type.procedure_code',
+  `result_text`         varchar(255) NOT NULL DEFAULT '' COMMENT 'Description of result_code',
   `date`                datetime     DEFAULT NULL        COMMENT 'lab-provided date specific to this result',
   `facility`            varchar(255) NOT NULL DEFAULT '' COMMENT 'lab-provided testing facility ID',
   `units`               varchar(31)  NOT NULL DEFAULT '',