4 * Administrative loader for lab compendium data.
6 * Supports loading of lab order codes and related order entry questions from CSV
7 * format into the procedure_order and procedure_questions tables, respectively.
9 * Copyright (C) 2012-2013 Rod Roark <rod@sunsetsystems.com>
11 * LICENSE: This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>.
23 * @author Rod Roark <rod@sunsetsystems.com>
28 require_once("../globals.php");
30 use OpenEMR\Common\Acl\AclMain
;
31 use OpenEMR\Common\Twig\TwigContainer
;
32 use OpenEMR\Core\Header
;
34 // This array is an important reference for the supported labs and their NPI
35 // numbers as known to this program. The clinic must define at least one
36 // procedure provider entry for a lab that has a supported NPI number.
39 '1235138868' => 'Diagnostic Pathology Medical Group',
40 '1235186800' => 'Pathgroup Labs LLC',
41 '1598760985' => 'Yosemite Pathology Medical Group',
45 * Get lab's ID from the users table given its NPI. If none return 0.
47 * @param string $npi The lab's NPI number as known to the system
48 * @return integer The numeric value of the lab's address book entry
50 function getLabID($npi)
53 "SELECT ppid FROM procedure_providers WHERE " .
54 "npi = ? ORDER BY ppid LIMIT 1",
57 if (empty($lrow['ppid'])) {
61 return intval($lrow['ppid']);
64 if (!AclMain
::aclCheckCore('admin', 'super')) {
65 echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Load Compendium")]);
69 $form_step = isset($_POST['form_step']) ?
trim($_POST['form_step']) : '0';
70 $form_status = isset($_POST['form_status' ]) ?
trim($_POST['form_status' ]) : '';
72 if (!empty($_POST['form_import'])) {
76 // When true the current form will submit itself after a brief pause.
77 $auto_continue = false;
80 $EXPORT_FILE = $GLOBALS['temporary_files_dir'] . "/openemr_config.sql";
85 <?php Header
::setupHeader(); ?
>
86 <title
><?php
echo xlt('Load Compendium'); ?
></title
>
90 <div
class="container mt-3">
93 <h2
><?php
echo xlt('Load Lab Compendium'); ?
></h2
>
96 <form
class="jumbotron py-4" method
='post' action
='load_compendium.php' enctype
='multipart/form-data'>
97 <table
class="table table-borderless">
98 <?php
if ($form_step == 0) { ?
>
100 <td
class="text-nowrap">
101 <?php
echo xlt('Vendor'); ?
>
104 <select
class='form-control' name
='vendor'>
105 <?php
foreach ($lab_npi as $key => $value) {
106 echo "<option value='" . attr($key) . "'";
107 if (!getLabID($key)) {
108 // Entries with no matching address book entry will be disabled.
111 echo ">" . text($key) . ": " . text($value) . "</option>";
117 <td
class="text-nowrap">
118 <?php
echo xlt('Action'); ?
>
121 <select
class='form-control' name
='action'>
122 <option value
='1'><?php
echo xlt('Load Order Definitions'); ?
></option
>
123 <option value
='2'><?php
echo xlt('Load Order Entry Questions'); ?
></option
>
124 <option value
='3'><?php
echo xlt('Load OE Question Options'); ?
></option
>
129 <td
class="text-nowrap">
130 <?php
echo xlt('Container Group Name'); ?
>
133 <select
class='form-control' name
='group'>
135 $gres = sqlStatement("SELECT procedure_type_id, name FROM procedure_type " .
136 "WHERE procedure_type = 'grp' ORDER BY name, procedure_type_id");
137 while ($grow = sqlFetchArray($gres)) {
138 echo "<option value='" . attr($grow['procedure_type_id']) . "'>" .
139 text($grow['name']) . "</option>";
145 <td
class="text-nowrap">
146 <?php
echo xlt('File to Upload'); ?
>
149 <div
class="custom-file">
150 <label
class="custom-file-label" for="userfile"><?php
echo xlt('Choose file'); ?
></label
>
151 <input type
='hidden' class="custom-file-input" name
='MAX_FILE_SIZE' value
='4000000' />
152 <input
class='form-control' type
='file' name
='userfile' id
='userfile' />
158 <button type
="submit" class="btn btn-primary btn-save" value
='<?php echo xla('Submit
'); ?>'>
159 <?php
echo xlt('Submit'); ?
>
166 echo " <td colspan='2'>\n";
168 if ($form_step == 1) {
169 // Process uploaded config file.
170 if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {
171 $form_vendor = $_POST['vendor'];
172 $form_action = intval($_POST['action']);
173 $form_group = intval($_POST['group']);
174 $lab_id = getLabID($form_vendor);
176 $form_status .= xlt('Applying') . "...<br />";
177 echo nl2br(text($form_status));
179 $fhcsv = fopen($_FILES['userfile']['tmp_name'], "r");
182 // Vendor = Pathgroup
184 if ($form_vendor == '1235186800') {
185 if ($form_action == 1) { // load compendium
186 // Mark all "ord" rows having the indicated parent as inactive.
188 "UPDATE procedure_type SET activity = 0 WHERE " .
189 "parent = ? AND procedure_type = 'ord'",
193 // What should be uploaded is the "Compendium" spreadsheet provided by
194 // PathGroup, saved in "Text CSV" format from OpenOffice, using its
195 // default settings. Values for each row are:
196 // 0: Order Code : mapped to procedure_code of order type
197 // 1: Order Name : mapped to name of order type
198 // 2: Result Code : mapped to procedure_code of result type
199 // 3: Result Name : mapped to name of result type
201 while (!feof($fhcsv)) {
202 $acsv = fgetcsv($fhcsv, 4096);
203 if (count($acsv) < 4 ||
$acsv[0] == "Order Code") {
207 $standard_code = empty($acsv[2]) ?
'' : ('CPT4:' . $acsv[2]);
209 // Update or insert the order row, if not already done.
211 "SELECT * FROM procedure_type WHERE " .
212 "parent = ? AND procedure_code = ? AND procedure_type = 'ord' " .
213 "ORDER BY procedure_type_id DESC LIMIT 1",
214 array($form_group, $acsv[0])
216 if (empty($trow['procedure_type_id']) ||
$trow['activity'] == 0) {
217 if (empty($trow['procedure_type_id'])) {
219 "INSERT INTO procedure_type SET " .
220 "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?",
221 array($form_group, $acsv[1], $lab_id, $acsv[0], 'ord')
224 $ptid = $trow['procedure_type_id'];
226 "UPDATE procedure_type SET " .
227 "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?, " .
228 "activity = 1 WHERE procedure_type_id = ?",
229 array($form_group, $acsv[1], $lab_id, $acsv[0], 'ord', $ptid)
234 "UPDATE procedure_type SET activity = 0 WHERE " .
235 "parent = ? AND procedure_type = 'res'",
240 // Update or insert the result row.
241 // Not sure we need this, but what the hell.
243 "SELECT * FROM procedure_type WHERE " .
244 "parent = ? AND procedure_code = ? AND procedure_type = 'res' " .
245 "ORDER BY procedure_type_id DESC LIMIT 1",
246 array($ptid, $acsv[2])
248 // The following should always be true, otherwise duplicate input row.
249 if (empty($trow['procedure_type_id']) ||
$trow['activity'] == 0) {
250 if (empty($trow['procedure_type_id'])) {
252 "INSERT INTO procedure_type SET " .
253 "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?",
254 array($ptid, $acsv[3], $lab_id, $acsv[2], 'res')
257 $resid = $trow['procedure_type_id'];
259 "UPDATE procedure_type SET " .
260 "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?, " .
261 "activity = 1 WHERE procedure_type_id = ?",
262 array($ptid, $acsv[3], $lab_id, $acsv[2], 'res', $resid)
268 } elseif ($form_action == 2) { // load questions
269 // Delete the vendor's current questions.
271 "DELETE FROM procedure_questions WHERE lab_id = ?",
275 // What should be uploaded is the "AOE Questions" spreadsheet provided by
276 // PathGroup, saved in "Text CSV" format from OpenOffice, using its
277 // default settings. Values for each row are:
278 // 0: OBRCode (order code)
282 // 4: Required (0 = No, 1 = Yes)
283 // 5: Maxchar (integer length)
284 // 6: FieldType (FT = free text, DD = dropdown, ST = string)
288 while (!feof($fhcsv)) {
289 $acsv = fgetcsv($fhcsv, 4096);
290 if (count($acsv) < 7 ||
$acsv[4] == "Required") {
294 $code = trim($acsv[0]);
299 if ($code != $last_code) {
306 $required = 0 +
$acsv[4];
307 $maxsize = 0 +
$acsv[5];
310 // Figure out field type.
311 if ($acsv[6] == 'DD') {
313 } elseif (stristr($acsv[3], 'mm/dd/yy') !== false) {
315 } elseif (stristr($acsv[3], 'wks_days') !== false) {
317 } elseif ($acsv[6] == 'FT') {
324 "SELECT * FROM procedure_questions WHERE " .
325 "lab_id = ? AND procedure_code = ? AND question_code = ?",
326 array($lab_id, $code, $acsv[1])
329 if (empty($qrow['question_code'])) {
331 "INSERT INTO procedure_questions SET " .
332 "lab_id = ?, procedure_code = ?, question_code = ?, question_text = ?, " .
333 "required = ?, maxsize = ?, fldtype = ?, options = '', tips = ?,
334 activity = 1, seq = ?",
335 array($lab_id, $code, $acsv[1], $acsv[2], $required, $maxsize, $fldtype, $acsv[3], $seq)
339 "UPDATE procedure_questions SET " .
340 "question_text = ?, required = ?, maxsize = ?, fldtype = ?, " .
341 "options = '', tips = ?, activity = 1, seq = ? WHERE " .
342 "lab_id = ? AND procedure_code = ? AND question_code = ?",
343 array($acsv[2], $required, $maxsize, $fldtype, $acsv[3], $seq,
350 // end load questions
351 } elseif ($form_action == 3) { // load question options
352 // What should be uploaded is the "AOE Options" spreadsheet provided
353 // by YPMG, saved in "Text CSV" format from OpenOffice, using its
354 // default settings. Values for each row are:
355 // 0: OBXCode (question code)
356 // 1: OBRCode (procedure code)
357 // 2: Option1 (option text)
358 // 3: Optioncode (the row is duplicated for each possible value)
360 while (!feof($fhcsv)) {
361 $acsv = fgetcsv($fhcsv, 4096);
362 if (count($acsv) < 4 ||
($acsv[0] == "OBXCode")) {
366 $pcode = trim($acsv[1]);
367 $qcode = trim($acsv[0]);
368 $options = trim($acsv[2]) . ':' . trim($acsv[3]);
369 if (empty($pcode) ||
empty($qcode)) {
374 "SELECT * FROM procedure_questions WHERE " .
375 "lab_id = ? AND procedure_code = ? AND question_code = ?",
376 array($lab_id, $pcode, $qcode)
378 if (empty($qrow['procedure_code'])) {
379 continue; // should not happen
381 if ($qrow['activity'] == '1' && $qrow['options'] !== '') {
382 $options = $qrow['options'] . ';' . $options;
386 "UPDATE procedure_questions SET " .
387 "options = ? WHERE " .
388 "lab_id = ? AND procedure_code = ? AND question_code = ?",
389 array($options, $lab_id, $pcode, $qcode)
393 } // end load questions
396 // Vendor = YPMG or DPMG
398 if ($form_vendor == '1598760985' ||
$form_vendor == '1235138868') {
399 if ($form_action == 1) { // load compendium
400 // Mark all "ord" rows having the indicated parent as inactive.
402 "UPDATE procedure_type SET activity = 0 WHERE " .
403 "parent = ? AND procedure_type = 'ord'",
406 // What should be uploaded is the Order Compendium spreadsheet provided
407 // by YPMG, saved in "Text CSV" format from OpenOffice, using its
408 // default settings. Values for each row are:
409 // 0: Order code : mapped to procedure_code
410 // 1: Order Name : mapped to name
411 // 2: Result Code : ignored (will cause multiple occurrences of the same order code)
412 // 3: Result Name : ignored
414 while (!feof($fhcsv)) {
415 $acsv = fgetcsv($fhcsv, 4096);
416 $ordercode = trim($acsv[0]);
417 if (count($acsv) < 2 ||
$ordercode == "Order Code") {
422 "SELECT * FROM procedure_type WHERE " .
423 "parent = ? AND procedure_code = ? AND procedure_type = 'ord' " .
424 "ORDER BY procedure_type_id DESC LIMIT 1",
425 array($form_group, $ordercode)
428 if (empty($trow['procedure_type_id'])) {
430 "INSERT INTO procedure_type SET " .
431 "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?, " .
433 array($form_group, trim($acsv[1]), $lab_id, $ordercode, 'ord')
437 "UPDATE procedure_type SET " .
438 "parent = ?, name = ?, lab_id = ?, procedure_code = ?, procedure_type = ?, " .
440 "WHERE procedure_type_id = ?",
441 array($form_group, trim($acsv[1]), $lab_id, $ordercode, 'ord',
442 $trow['procedure_type_id'])
446 } elseif ($form_action == 2) { // load questions
447 // Mark the vendor's current questions inactive.
449 "DELETE FROM procedure_questions WHERE lab_id = ?",
453 // What should be uploaded is the "AOE Questions" spreadsheet provided
454 // by YPMG, saved in "Text CSV" format from OpenOffice, using its
455 // default settings. Values for each row are:
459 // 3: Is Required (always "false")
460 // 4: Field Type ("Free Text", "Pre-Defined Text" or "Drop Down";
461 // "Drop Down" was previously "Multiselect Pre-Defined Text" and
462 // indicates that more than one choice is allowed)
463 // 5: Response (just one; the row is duplicated for each possible value)
467 while (!feof($fhcsv)) {
468 $acsv = fgetcsv($fhcsv, 4096);
469 if (count($acsv) < 5 ||
($acsv[3] !== "false" && $acsv[3] !== "true")) {
473 $pcode = trim($acsv[0]);
474 $qcode = trim($acsv[1]);
475 $required = strtolower(substr($acsv[3], 0, 1)) == 't' ?
1 : 0;
476 $options = trim($acsv[5]);
477 if (empty($pcode) ||
empty($qcode)) {
481 if ($pcode != $last_code) {
488 // Figure out field type.
490 if (strpos($acsv[4], 'Drop') !== false) {
492 } elseif (strpos($acsv[4], 'Multiselect') !== false) {
497 "SELECT * FROM procedure_questions WHERE " .
498 "lab_id = ? AND procedure_code = ? AND question_code = ?",
499 array($lab_id, $pcode, $qcode)
502 // If this is the first option value and it's a multi-select list,
503 // then prepend '+;' here to indicate that. YPMG does not use those
504 // but keep this note here for future reference.
506 if (empty($qrow['procedure_code'])) {
508 "INSERT INTO procedure_questions SET " .
509 "lab_id = ?, procedure_code = ?, question_code = ?, question_text = ?, " .
510 "fldtype = ?, required = ?, options = ?, seq = ?, activity = 1",
511 array($lab_id, $pcode, $qcode, trim($acsv[2]), $fldtype, $required, $options, $seq)
514 if ($qrow['activity'] == '1' && $qrow['options'] !== '' && $options !== '') {
515 $options = $qrow['options'] . ';' . $options;
519 "UPDATE procedure_questions SET " .
520 "question_text = ?, fldtype = ?, required = ?, options = ?, activity = 1 WHERE " .
521 "lab_id = ? AND procedure_code = ? AND question_code = ?",
522 array(trim($acsv[2]), $fldtype, $required, $options, $lab_id, $pcode, $qcode)
526 } // end load questions
531 echo "<p class='text-danger'>" . xlt('Internal error accessing uploaded file!') . "</p>";
535 echo "<p class='text-danger'>" . xlt('Upload failed!') . "</p>";
539 $auto_continue = true;
542 if ($form_step == 2) {
543 $form_status .= xlt('Done') . ".";
544 echo nl2br(text($form_status));
552 <input type
='hidden' name
='form_step' value
='<?php echo attr($form_step); ?>' />
553 <input type
='hidden' name
='form_status' value
='<?php echo attr($form_status); ?>' />
561 <?php
if ($auto_continue) { ?
>
563 setTimeout("document.forms[0].submit();", 500);