diff --git a/interface/forms/procedure_order/common.php b/interface/forms/procedure_order/common.php
index 5f58aed7991..a841c58674a 100644
--- a/interface/forms/procedure_order/common.php
+++ b/interface/forms/procedure_order/common.php
@@ -26,10 +26,16 @@
use OpenEMR\Common\Csrf\CsrfUtils;
use OpenEMR\Common\Forms\ReasonStatusCodes;
use OpenEMR\Core\Header;
+use OpenEMR\Events\Services\QuestLabTransmitEvent;
+use Symfony\Component\EventDispatcher\EventDispatcher;
if (!$encounter) { // comes from globals.php
die("Internal error: we do not seem to be in an encounter!");
}
+/**
+ * @var EventDispatcher
+ */
+$ed = $GLOBALS['kernel']->getEventDispatcher();
// Defaults for new orders.
$provider_id = getProviderIdOfEncounter($encounter);
@@ -408,6 +414,9 @@ function getListOptions($list_id, $fieldnames = array('option_id', 'title', 'seq
require_once(__DIR__ . "/../../procedure_tools/labcorp/ereq_form.php");
require_once(__DIR__ . "/../../procedure_tools/labcorp/gen_hl7_order.inc.php");
$alertmsg = gen_hl7_order($formid, $hl7, $reqStr);
+ } elseif ($gbl_lab === 'quest') {
+ require_once(__DIR__ . "/../../procedure_tools/quest/gen_hl7_order.inc.php");
+ $alertmsg = gen_hl7_order($formid, $hl7, $reqStr);
} else { // Default lab. Add more labs here.
require_once(__DIR__ . "/../../orders/gen_hl7_order.inc.php");
$alertmsg = gen_hl7_order($formid, $hl7);
@@ -427,6 +436,12 @@ function getListOptions($list_id, $fieldnames = array('option_id', 'title', 'seq
xlt("Order Successfully Sent") . "...\n" .
xlt("Order HL7 Content") .
":\n" . $hl7 . "\n";
+ if ($gbl_lab === 'quest') {
+ $order_log .= xlt("Transmitting order to Quest");
+ $ed->dispatch(new QuestLabTransmitEvent($hl7), QuestLabTransmitEvent::EVENT_LAB_TRANSMIT, 10);
+ $ed->dispatch(new QuestLabTransmitEvent($pid), QuestLabTransmitEvent::EVENT_LAB_POST_ORDER_LOAD, 10);
+ }
+
if ($_POST['form_order_psc']) {
if ($gbl_lab === 'labcorp') {
$order_log .= "\n" . date('Y-m-d H:i') . " " .
@@ -456,6 +471,7 @@ function getListOptions($list_id, $fieldnames = array('option_id', 'title', 'seq
file_put_contents($log_file, $order_log);
}
}
+
unset($_POST['bn_xmit']);
}
unset($_POST['bn_save']);
@@ -967,11 +983,13 @@ function createLabels(e) {
let acctid = ;
let order = f.id.value;
let patient = ;
+ let dob = ;
let pid = ;
let url = top.webroot_url + "/interface/procedure_tools/libs/labs_ajax.php";
// this escapes above
let uri = "?action=print_labels&count=" + encodeURIComponent(count) + "&order=" + encodeURIComponent(order) + "&pid=" + encodeURIComponent(pid) +
"&acctid=" + encodeURIComponent(acctid) + "&patient=" + encodeURIComponent(patient) + "&specimen=" + encodeURIComponent(tarray) +
+ "&dob=" + encodeURIComponent(dob) +
"&csrf_token_form=" + ;
// retrieve the labels
diff --git a/interface/orders/gen_hl7_order.inc.php b/interface/orders/gen_hl7_order.inc.php
index a53e7a05153..1930db20339 100644
--- a/interface/orders/gen_hl7_order.inc.php
+++ b/interface/orders/gen_hl7_order.inc.php
@@ -384,6 +384,8 @@ function gen_hl7_order($orderid, &$out)
$setid = 0;
while ($pcrow = sqlFetchArray($pcres)) {
// Observation Request.
+
+ $dl = '';
$out .= "OBR" .
$d1 . ++$setid . // Set ID
$d1 . str_pad((string)$orderid, 4, "0", STR_PAD_LEFT) . // Placer Order Number
diff --git a/interface/procedure_tools/libs/labs_ajax.php b/interface/procedure_tools/libs/labs_ajax.php
index 9b9414e053a..d10e179eafc 100644
--- a/interface/procedure_tools/libs/labs_ajax.php
+++ b/interface/procedure_tools/libs/labs_ajax.php
@@ -20,6 +20,12 @@
CsrfUtils::csrfNotVerified();
}
+function orderDate($order)
+{
+ $sql = "SELECT DATE_FORMAT(date_ordered, '%m/%d/%Y') AS date_ordered FROM procedure_order WHERE procedure_order_id = ? ";
+ return sqlQuery($sql, [$order]);
+}
+
$action = $_GET['action'];
if ($action === 'code_detail') {
@@ -63,6 +69,8 @@
$specimen = array();
$specimens = explode(";", $_GET['specimen']);
$patient = strtoupper($_GET['patient']);
+ $order_date = orderDate($order);
+ $dob = $_GET['dob'];
$count = 1;
if ($_GET['count']) {
$count = (int)$_GET['count'];
@@ -96,13 +104,14 @@
$pdf->AddPage();
$barcode = '
';
- $pdf->SetFont('', '', 7);
- $pdf->writeCell(0, 3, 'CLIENT #: ' . $client, 0, 1, 'C');
- $pdf->writeCell(0, 3, 'LAB REF #: ' . $ord, 0, 1, 'C');
$pdf->SetFont('', 'B', 8);
$pdf->writeCell(0, 3, $patient, 0, 1, 'C');
+ $pdf->SetFont('', '', 7);
+ $pdf->writeCell(0, 3, 'CLIENT#: ' . $client . '-WDL', 0, 1, 'C');
+ $pdf->writeCell(0, 3, 'DOS: ' . $order_date['date_ordered'], 0, 1, 'C');
+ $pdf->writeCell(0, 3, 'DOB: ' . $dob, 0, 1, 'C');
$code_info = $client . '-' . $ord;
- $barcode .= '
';
+ $barcode .= '';
$pdf->writeHTML($barcode);
}
$count--;
diff --git a/interface/procedure_tools/quest/gen_hl7_order.inc.php b/interface/procedure_tools/quest/gen_hl7_order.inc.php
new file mode 100644
index 00000000000..0df1bd13547
--- /dev/null
+++ b/interface/procedure_tools/quest/gen_hl7_order.inc.php
@@ -0,0 +1,590 @@
+
+*
+* 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 .
+*
+* @package OpenEMR
+* @author Rod Roark
+*/
+
+/*
+* 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("$webserver_root/custom/code_types.inc.php");
+
+use OpenEMR\Common\Logging\EventAuditLogger;
+
+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 1;
+ } else if ($tmp == 'spouse') {
+ return 2;
+ } else if ($tmp == 'child') {
+ return 8;
+ } else if ($tmp == 'other') {
+ return 8;
+ }
+
+ // 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 <= ? OR date IS NULL) 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)
+{
+ $labSample = '';
+ $labNote = '';
+ // Delimiters
+ $d0 = "\r";
+ $d1 = '|';
+ $d2 = '^';
+
+ $today = time();
+ $out = '';
+
+ $porow = sqlQuery(
+ "SELECT " .
+ "po.*, " .
+ "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, ordering provider or lab is missing for order ID '$orderid'";
+ }
+
+ $pcres = sqlStatement(
+ "SELECT " .
+ "pc.procedure_code, pc.procedure_name, pc.procedure_order_seq, pc.diagnoses " .
+ "FROM procedure_order_code AS pc " .
+ "WHERE " .
+ "pc.procedure_order_id = ? AND " .
+ "pc.do_not_send = 0 " .
+ "ORDER BY pc.procedure_order_seq",
+ array($orderid)
+ );
+
+ $padOrderId = trim($porow['send_fac_id']) . "-" . trim(str_pad((string)$orderid, 4, "0", STR_PAD_LEFT));
+ // 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 . $padOrderId . // 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['postal_code']) .
+ $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).
+ $msql = sqlStatement("SELECT drug FROM prescriptions WHERE active=1 AND patient_id=?", [$porow['pid']]);
+ $drugs = array();
+ while ($mres = sqlFetchArray($msql)) {
+ $drugs[] = trim($mres['drug']);
+ }
+ $med_list = count($drugs) > 0 ? implode(",", $drugs) : 'NONE';
+
+ $out .= "NTE" .
+ $d1 . "1" .
+ $d1 . "L" .
+ $d1 . "medications " . $med_list .
+ $d0;
+
+ // 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.
+ $ins_type = trim($porow['billing_type']);
+ $payers = loadPayerInfo($porow['pid'], $porow['date_ordered']);
+ $setid = 0;
+ if ($ins_type == 'T') {
+ // only send primary and secondary insurance
+ 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']['ins_comp_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, 10) . // IN1-37 to 47 all empty
+ $d1 . $ins_type . // Insurance Type
+ $d1 .
+ $d1 .
+ $d0;
+ if ($setid === 2) {
+ break;
+ }
+ }
+ if ($setid === 0) {
+ return "\nInsurance is being billed but patient does not have any payers on record!";
+ }
+ } else { // no insurance record
+ ++$setid;
+ $out .= "IN1|$setid||||||||||||||||||||||||||||||||||||||||||||||$ins_type" . $d0;
+ }
+ if ($ins_type != 'C') {
+ $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['postal_code']) .
+ $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 . '1' . // Relationship
+ $d1 . hl7SSN($porow['ss']) .
+ $d0;
+ }
+ // Common Order.
+ $out .= "ORC" .
+ $d1 . "NW" . // New Order
+ $d1 . $padOrderId . // 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 . $padOrderId . // 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 ICD10 and we'll surely want to make
+ // this more flexible (probably when some lab needs another diagnosis type).
+ $test_diagnosis = $pcrow['diagnoses'];
+ if (empty($test_diagnosis)) { // add default primary dianosis
+ $test_diagnosis = $porow['order_diagnosis'];
+ }
+ $setid2 = 0;
+ if (!empty($test_diagnosis)) {
+ $relcodes = explode(';', $test_diagnosis);
+ foreach ($relcodes as $codestring) {
+ if ($codestring === '') {
+ continue;
+ }
+
+ list($codetype, $code) = explode(':', $codestring);
+ if ($codetype !== 'ICD10') {
+ continue;
+ }
+
+ $desc = lookup_code_descriptions($codestring);
+ $out .= "DG1" .
+ $d1 . ++$setid2 . // Set ID
+ $d1 . // Diagnosis Coding Method
+ $d1 . $code . // Diagnosis Code
+ $d2 . hl7Text(trim($desc)) . // Diagnosis Description
+ $d2 . "I10" . // Diagnosis Type
+ $d1 . $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;
+ }
+ }
+ //recreate OBX here to add to end of hl7
+ $out .= "OBX" .
+ $d1 . "1" .
+ $d1 . "ST" .
+ $d1 . "PR101^CLINICAL INFORMATION" .
+ $d1 .
+ $d1 . $porow['patient_instructions'] .
+ $d0;
+
+ $out .= "OBX" .
+ $d1 . "2" .
+ $d1 . "ST" .
+ $d1 . "PR300^SPECIMEN SOURCE/SAMPLE TYPE" .
+ $d1 .
+ $d1 . $porow['clinical_hx'] .
+ $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 == 'DL' || $pprow['orders_path'] === '') {
+ header("Pragma: public");
+ header("Expires: 0");
+ header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
+ header("Content-Type: application/force-download");
+ header("Content-Disposition: attachment; filename=order_$msgid.hl7");
+ header("Content-Description: File Transfer");
+ echo $out;
+ exit;
+ } else if ($protocol == 'SFTP') {
+ // 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 \phpseclib3\Net\SFTP($remote_host);
+ if (!$sftp->login($pprow['login'], $pprow['password'])) {
+ return xl('Login to this remote host failed') . ": '$remote_host'";
+ }
+
+ if (!$sftp->put($filename, $out)) {
+ return xl('Creating this file on remote host failed') . ": '$filename'";
+ }
+ } else if ($protocol == 'FS') {
+ // Compute the target path/file name.
+ $filename = $msgid . '.txt';
+ if ($pprow['orders_path']) {
+ $filename = $pprow['orders_path'] . '/' . $filename;
+ }
+
+ $fh = fopen("$filename", 'w');
+ if ($fh) {
+ fwrite($fh, $out);
+ fclose($fh);
+ } else {
+ return xl('Cannot create file') . ' "' . "$filename" . '"';
+ }
+ } else {// TBD: Insert "else if ($protocol == '???') {...}" to support other protocols.
+ return xl('This protocol is not implemented') . ": '$protocol'";
+ }
+
+ // Falling through to here indicates success.
+ EventAuditLogger::instance()->newEvent(
+ "proc_order_xmit",
+ $_SESSION['authUser'],
+ $_SESSION['authProvider'],
+ 1,
+ "ID: $msgid Protocol: $protocol Host: $remote_host"
+ );
+ return '';
+}
diff --git a/src/Events/Services/QuestLabTransmitEvent.php b/src/Events/Services/QuestLabTransmitEvent.php
new file mode 100644
index 00000000000..0f340116d21
--- /dev/null
+++ b/src/Events/Services/QuestLabTransmitEvent.php
@@ -0,0 +1,40 @@
+
+ * @copyright Copyright (c) 2023 Juggernaut Systems Express,
+ * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
+ */
+
+namespace OpenEMR\Events\Services;
+
+use Symfony\Contracts\EventDispatcher\Event;
+
+class QuestLabTransmitEvent extends Event
+{
+ /**
+ * This event is triggered when a lab is to be transmitted
+ */
+ const EVENT_LAB_TRANSMIT = 'lab.transmit';
+
+ /**
+ * This event is triggered when a lab requisition form is returned from Quest
+ * Requisition form has to be enabled in the globals
+ */
+ const EVENT_LAB_POST_ORDER_LOAD = 'lab.post_order_load';
+
+ private string $order;
+ public function __construct($hl7)
+ {
+ if (is_string($hl7)) {
+ $this->order = $hl7;
+ }
+ }
+
+ public function getOrder(): string
+ {
+ return $this->order;
+ }
+}