Multiple improvements from IPPF related to layouts. (#1081)
[openemr.git] / interface / orders / procedure_stats.php
blob8d824aeaad6ea26736195f5caa2616e92c252a2a
1 <?php
2 /**
3 * This module creates statistical reports related to lab tests and
4 * other procedure orders.
6 * Copyright (C) 2010-2016 Rod Roark <rod@sunsetsystems.com>
7 * Copyright (C) 2015 Roberto Vasquez <robertogagliotta@gmail.com>
8 * Copyright (C) 2017 Brady Miller <brady.g.miller@gmail.com>
10 * LICENSE: This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
21 * @package OpenEMR
22 * @author Rod Roark <rod@sunsetsystems.com>
23 * @author Roberto Vasquez <robertogagliotta@gmail.com>
24 * @author Brady Miller <brady.g.miller@gmail.com>
25 * @link http://www.open-emr.org
28 require_once("../globals.php");
29 require_once("../../library/patient.inc");
30 require_once("../../library/acl.inc");
31 require_once("../../custom/code_types.inc.php");
32 require_once "$srcdir/options.inc.php";
34 // Might want something different here.
36 if (! acl_check('acct', 'rep')) {
37 die("Unauthorized access.");
40 $from_date = fixDate($_POST['form_from_date']);
41 $to_date = fixDate($_POST['form_to_date'], date('Y-m-d'));
42 $form_by = $_POST['form_by']; // this is a scalar
43 $form_show = $_POST['form_show']; // this is an array
44 $form_facility = isset($_POST['form_facility']) ? $_POST['form_facility'] : '';
45 $form_sexes = isset($_POST['form_sexes']) ? $_POST['form_sexes'] : '3';
46 $form_output = isset($_POST['form_output']) ? 0 + $_POST['form_output'] : 1;
48 if (empty($form_by)) {
49 $form_by = '4';
52 if (empty($form_show)) {
53 $form_show = array('1');
56 // One of these is chosen as the left column, or Y-axis, of the report.
58 $report_title = xl('Procedure Statistics Report');
59 $arr_by = array(
60 4 => xl('Specific Result'),
61 5 => xl('Followups Indicated'),
64 // This will become the array of reportable values.
65 $areport = array();
67 // This accumulates the bottom line totals.
68 $atotals = array();
70 $arr_show = array(
71 // '.total' => array('title' => 'Total Positives'),
72 '.tneg' => array('title' => 'Total Negatives'),
73 '.age' => array('title' => 'Age Category'),
74 ); // info about selectable columns
76 $arr_titles = array(); // will contain column headers
78 // Query layout_options table to generate the $arr_show table.
79 // Table key is the field ID.
80 $lres = sqlStatement("SELECT field_id, title, data_type, list_id, description " .
81 "FROM layout_options WHERE " .
82 "form_id = 'DEM' AND uor > 0 AND field_id NOT LIKE 'em%' " .
83 "ORDER BY group_id, seq, title");
84 while ($lrow = sqlFetchArray($lres)) {
85 $fid = $lrow['field_id'];
86 if ($fid == 'fname' || $fid == 'mname' || $fid == 'lname') {
87 continue;
90 $arr_show[$fid] = $lrow;
91 $arr_titles[$fid] = array();
94 // Compute age in years given a DOB and "as of" date.
96 function getAge($dob, $asof = '')
98 if (empty($asof)) {
99 $asof = date('Y-m-d');
102 $a1 = explode('-', substr($dob, 0, 10));
103 $a2 = explode('-', substr($asof, 0, 10));
104 $age = $a2[0] - $a1[0];
105 if ($a2[1] < $a1[1] || ($a2[1] == $a1[1] && $a2[2] < $a1[2])) {
106 --$age;
109 // echo "<!-- $dob $asof $age -->\n"; // debugging
110 return $age;
113 $cellcount = 0;
115 function genStartRow($att)
117 global $cellcount, $form_output;
118 if ($form_output != 3) {
119 echo " <tr $att>\n";
122 $cellcount = 0;
125 function genEndRow()
127 global $form_output;
128 if ($form_output == 3) {
129 echo "\n";
130 } else {
131 echo " </tr>\n";
135 function getListTitle($list, $option)
137 $row = sqlQuery("SELECT title FROM list_options WHERE " .
138 "list_id = '$list' AND option_id = '$option' AND activity = 1");
139 if (empty($row['title'])) {
140 return $option;
143 return $row['title'];
146 // Usually this generates one cell, but allows for two or more.
148 function genAnyCell($data, $right = false, $class = '')
150 global $cellcount, $form_output;
151 if (!is_array($data)) {
152 $data = array(0 => $data);
155 foreach ($data as $datum) {
156 if ($form_output == 3) {
157 if ($cellcount) {
158 echo ',';
161 echo '"' . $datum . '"';
162 } else {
163 echo " <td";
164 if ($class) {
165 echo " class='$class'";
168 if ($right) {
169 echo " align='right'";
172 echo ">$datum</td>\n";
175 ++$cellcount;
179 function genHeadCell($data, $right = false)
181 genAnyCell($data, $right, 'dehead');
184 // Create an HTML table cell containing a numeric value, and track totals.
186 function genNumCell($num, $cnum)
188 global $atotals, $form_output;
189 $atotals[$cnum] += $num;
190 if (empty($num) && $form_output != 3) {
191 $num = '&nbsp;';
194 genAnyCell($num, true, 'detail');
197 // Helper function called after the reporting key is determined for a row.
199 function loadColumnData($key, $row)
201 global $areport, $arr_titles, $from_date, $to_date, $arr_show;
203 // If no result, do nothing.
204 if (empty($row['abnormal'])) {
205 return;
208 // If first instance of this key, initialize its arrays.
209 if (empty($areport[$key])) {
210 $areport[$key] = array();
211 $areport[$key]['.prp'] = 0; // previous pid
212 $areport[$key]['.wom'] = 0; // number of positive results for women
213 $areport[$key]['.men'] = 0; // number of positive results for men
214 $areport[$key]['.neg'] = 0; // number of negative results
215 $areport[$key]['.age'] = array(0,0,0,0,0,0,0,0,0); // age array
216 foreach ($arr_show as $askey => $dummy) {
217 if (substr($askey, 0, 1) == '.') {
218 continue;
221 $areport[$key][$askey] = array();
225 // Flag this patient as having been encountered for this report row.
226 $areport[$key]['.prp'] = $row['pid'];
228 // Collect abnormal results only, except for a column of total negatives.
229 if ($row['abnormal'] == 'no') {
230 ++$areport[$key]['.neg'];
231 return;
234 // Increment the correct sex category.
235 if (strcasecmp($row['sex'], 'Male') == 0) {
236 ++$areport[$key]['.men'];
237 } else {
238 ++$areport[$key]['.wom'];
241 // Increment the correct age category.
242 $age = getAge(fixDate($row['DOB']), $row['date_ordered']);
243 $i = min(intval(($age - 5) / 5), 8);
244 if ($age < 11) {
245 $i = 0;
248 ++$areport[$key]['.age'][$i];
250 // For each patient attribute to report, this increments the array item
251 // whose key is the attribute's value. This works well for list-based
252 // attributes. A key of "Unspecified" is used where the attribute has
253 // no assigned value.
254 foreach ($arr_show as $askey => $dummy) {
255 if (substr($askey, 0, 1) == '.') {
256 continue;
259 $status = empty($row[$askey]) ? 'Unspecified' : $row[$askey];
260 $areport[$key][$askey][$status] += 1;
261 $arr_titles[$askey][$status] += 1;
265 // This is called for each row returned from the query.
267 function process_result_code($row)
269 global $areport, $arr_titles, $form_by;
271 // Specific Results. One row for each result name.
273 if ($form_by === '4') {
274 $key = $row['order_name'] . ' / ' . $row['result_name'];
275 loadColumnData($key, $row);
276 } // Recommended followup services.
278 else if ($form_by === '5') {
279 if (!empty($row['related_code'])) {
280 $relcodes = explode(';', $row['related_code']);
281 foreach ($relcodes as $codestring) {
282 if ($codestring === '') {
283 continue;
286 // list($codetype, $code) = explode(':', $codestring);
287 // if ($codetype !== 'IPPF') continue;
288 $key = $codestring;
289 loadColumnData($key, $row);
293 } // end function process_result_code()
295 // If we are doing the CSV export then generate the needed HTTP headers.
296 // Otherwise generate HTML.
298 if ($form_output == 3) {
299 header("Pragma: public");
300 header("Expires: 0");
301 header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
302 header("Content-Type: application/force-download");
303 header("Content-Disposition: attachment; filename=service_statistics_report.csv");
304 header("Content-Description: File Transfer");
305 } else {
307 <html>
308 <head>
309 <?php html_header_show(); ?>
310 <title><?php echo $report_title; ?></title>
312 <link rel="stylesheet" href="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.min.css">
314 <style type="text/css">
315 body { font-family:sans-serif; font-size:10pt; font-weight:normal }
316 .dehead { color:#000000; font-family:sans-serif; font-size:10pt; font-weight:bold }
317 .detail { color:#000000; font-family:sans-serif; font-size:10pt; font-weight:normal }
318 </style>
319 <script type="text/javascript" src="../../library/textformat.js?v=<?php echo $v_js_includes; ?>"></script>
320 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-min-3-1-1/index.js"></script>
321 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.full.min.js"></script>
323 <script language="JavaScript">
324 var mypcc = '<?php echo $GLOBALS['phone_country_code'] ?>';
326 $(document).ready(function() {
327 $('.datepicker').datetimepicker({
328 <?php $datetimepicker_timepicker = false; ?>
329 <?php $datetimepicker_showseconds = false; ?>
330 <?php $datetimepicker_formatInput = false; ?>
331 <?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?>
332 <?php // can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
335 </script>
337 </head>
339 <body leftmargin='0' topmargin='0' marginwidth='0' marginheight='0'>
341 <center>
343 <h2><?php echo $report_title; ?></h2>
345 <form name='theform' method='post' action='procedure_stats.php'>
347 <table border='0' cellspacing='5' cellpadding='1'>
349 <tr>
350 <td valign='top' class='dehead' nowrap>
351 <?php xl('Rows', 'e'); ?>:
352 </td>
353 <td valign='top' class='detail'>
354 <select name='form_by' title='Left column of report'>
355 <?php
356 foreach ($arr_by as $key => $value) {
357 echo " <option value='$key'";
358 if ($key == $form_by) {
359 echo " selected";
362 echo ">" . $value . "</option>\n";
365 </select>
366 </td>
367 <td valign='top' class='dehead' nowrap>
368 <?php xl('Filters', 'e'); ?>:
369 </td>
370 <td rowspan='2' colspan='2' class='detail'
371 style='border-style:solid;border-width:1px;border-color:#cccccc'>
372 <table>
373 <tr>
374 <td valign='top' class='detail' nowrap>
375 <?php xl('Sex', 'e'); ?>:
376 </td>
377 <td class='detail' valign='top'>
378 <select name='form_sexes' title='<?php xl('To filter by sex', 'e'); ?>'>
379 <?php
380 foreach (array(3 => xl('Men and Women'), 1 => xl('Women Only'), 2 => xl('Men Only')) as $key => $value) {
381 echo " <option value='$key'";
382 if ($key == $form_sexes) {
383 echo " selected";
386 echo ">$value</option>\n";
389 </select>
390 </td>
391 </tr>
392 <tr>
393 <td valign='top' class='detail' nowrap>
394 <?php xl('Facility', 'e'); ?>:
395 </td>
396 <td valign='top' class='detail'>
397 <?php dropdown_facility(strip_escape_custom($form_facility), 'form_facility', false); ?>
398 </td>
399 </tr>
400 <tr>
401 <td colspan='2' class='detail' nowrap>
402 <?php xl('From', 'e'); ?>
403 <input type='text' class='datepicker' name='form_from_date' id='form_from_date' size='10' value='<?php echo $from_date ?>'
404 title='Start date yyyy-mm-dd'>
405 <?php xl('To', 'e'); ?>
406 <input type='text' class='datepicker' name='form_to_date' id='form_to_date' size='10' value='<?php echo $to_date ?>'
407 title='End date yyyy-mm-dd'>
408 </td>
409 </tr>
410 </table>
411 </td>
412 </tr>
413 <tr>
414 <td valign='top' class='dehead' nowrap>
415 <?php xl('Columns', 'e'); ?>:
416 </td>
417 <td valign='top' class='detail'>
418 <select name='form_show[]' size='4' multiple
419 title='<?php xl('Hold down Ctrl to select multiple items', 'e'); ?>'>
420 <?php
421 foreach ($arr_show as $key => $value) {
422 $title = $value['title'];
423 if (empty($title) || $key == 'title') {
424 $title = $value['description'];
427 echo " <option value='$key'";
428 if (is_array($form_show) && in_array($key, $form_show)) {
429 echo " selected";
432 echo ">$title</option>\n";
435 </select>
436 </td>
437 </tr>
438 <tr>
439 <td valign='top' class='dehead' nowrap>
440 <?php xl('To', 'e'); ?>:
441 </td>
442 <td colspan='3' valign='top' class='detail' nowrap>
443 <?php
444 foreach (array(1 => 'Screen', 2 => 'Printer', 3 => 'Export File') as $key => $value) {
445 echo " <input type='radio' name='form_output' value='$key'";
446 if ($key == $form_output) {
447 echo ' checked';
450 echo " />$value &nbsp;";
453 </td>
454 <td align='right' valign='top' class='detail' nowrap>
455 <input type='submit' name='form_submit' value='<?php xl('Submit', 'e'); ?>'
456 title='<?php xl('Click to generate the report', 'e'); ?>' />
457 </td>
458 </tr>
459 <tr>
460 <td colspan='5' height="1">
461 </td>
462 </tr>
463 </table>
464 <?php
465 } // end not export
467 if ($_POST['form_submit']) {
468 $pd_fields = '';
469 foreach ($arr_show as $askey => $asval) {
470 if (substr($askey, 0, 1) == '.') {
471 continue;
474 if ($askey == 'regdate' || $askey == 'sex' || $askey == 'DOB' ||
475 $askey == 'lname' || $askey == 'fname' || $askey == 'mname' ||
476 $askey == 'contrastart' || $askey == 'referral_source') {
477 continue;
480 $pd_fields .= ', pd.' . $askey;
483 $sexcond = '';
484 if ($form_sexes == '1') {
485 $sexcond = "AND pd.sex NOT LIKE 'Male' ";
486 } else if ($form_sexes == '2') {
487 $sexcond = "AND pd.sex LIKE 'Male' ";
490 // This gets us all results, with encounter and patient
491 // info attached and grouped by patient and encounter.
493 $sqlBindArray = array();
495 $query = "SELECT " .
496 "po.patient_id, po.encounter_id, po.date_ordered, " .
497 "po.provider_id, pd.regdate, " .
498 "pd.sex, pd.DOB, pd.lname, pd.fname, pd.mname, " .
499 "pd.contrastart, pd.referral_source$pd_fields, " .
500 "ps.abnormal, " .
501 // "pto.name AS order_name, ptr.name AS result_name, ptr.related_code " .
502 "pc.procedure_name AS order_name, ptr.name AS result_name, ptr.related_code " .
503 "FROM procedure_order AS po " .
504 "JOIN form_encounter AS fe ON fe.pid = po.patient_id AND fe.encounter = po.encounter_id " .
505 "JOIN patient_data AS pd ON pd.pid = po.patient_id $sexcond" .
506 "JOIN procedure_order_code AS pc ON pc.procedure_order_id = po.procedure_order_id " .
507 "JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id " .
508 "AND pr.procedure_order_seq = pc.procedure_order_seq " .
509 "AND pr.date_report IS NOT NULL " .
510 "JOIN procedure_result AS ps ON ps.procedure_report_id = pr.procedure_report_id " .
511 "AND ps.result_status = 'final' " .
512 // "JOIN procedure_type AS pto ON pto.procedure_type_id = pc.procedure_type_id " .
513 "JOIN procedure_type AS ptr ON ptr.lab_id = po.lab_id AND ptr.procedure_code = ps.result_code " .
514 "AND ptr.procedure_type LIKE 'res%' " .
515 "WHERE po.date_ordered IS NOT NULL AND po.date_ordered >= ? " .
516 "AND po.date_ordered <= ? ";
518 array_push($sqlBindArray, $from_date, $to_date);
520 if ($form_facility) {
521 $query .= "AND fe.facility_id = ? ";
522 array_push($sqlBindArray, $form_facility);
525 $query .= "ORDER BY fe.pid, fe.encounter, ps.result_code"; // needed?
527 $res = sqlStatement($query, $sqlBindArray);
529 while ($row = sqlFetchArray($res)) {
530 process_result_code($row);
533 // Sort everything by key for reporting.
534 ksort($areport);
535 foreach ($arr_titles as $atkey => $dummy) {
536 ksort($arr_titles[$atkey]);
539 if ($form_output != 3) {
540 echo "<table border='0' cellpadding='1' cellspacing='2' width='98%'>\n";
541 } // end not csv export
543 genStartRow("bgcolor='#dddddd'");
545 // genHeadCell($arr_by[$form_by]);
546 // If the key is an MA or IPPF code, then add a column for its description.
547 if ($form_by === '5') {
548 genHeadCell(array($arr_by[$form_by], xl('Description')));
549 } else {
550 genHeadCell($arr_by[$form_by]);
553 // Generate headings for values to be shown.
554 foreach ($form_show as $value) {
555 // if ($value == '.total') { // Total Positives
556 // genHeadCell(xl('Total'));
557 // }
558 if ($value == '.tneg') { // Total Negatives
559 genHeadCell(xl('Negatives'));
560 } else if ($value == '.age') { // Age
561 genHeadCell(xl('0-10'), true);
562 genHeadCell(xl('11-14'), true);
563 genHeadCell(xl('15-19'), true);
564 genHeadCell(xl('20-24'), true);
565 genHeadCell(xl('25-29'), true);
566 genHeadCell(xl('30-34'), true);
567 genHeadCell(xl('35-39'), true);
568 genHeadCell(xl('40-44'), true);
569 genHeadCell(xl('45+'), true);
570 } else if ($arr_show[$value]['list_id']) {
571 foreach ($arr_titles[$value] as $key => $dummy) {
572 genHeadCell(getListTitle($arr_show[$value]['list_id'], $key), true);
574 } else if (!empty($arr_titles[$value])) {
575 foreach ($arr_titles[$value] as $key => $dummy) {
576 genHeadCell($key, true);
581 if ($form_output != 3) {
582 genHeadCell(xl('Positives'), true);
585 genEndRow();
587 $encount = 0;
589 foreach ($areport as $key => $varr) {
590 $bgcolor = (++$encount & 1) ? "#ddddff" : "#ffdddd";
592 $dispkey = $key;
594 // If the key is an MA or IPPF code, then get its description.
595 if ($form_by === '5') {
596 list($codetype, $code) = explode(':', $key);
597 $type = $code_types[$codetype]['id'];
598 $dispkey = array($key, '');
599 $crow = sqlQuery("SELECT code_text FROM codes WHERE " .
600 "code_type = '$type' AND code = '$code' ORDER BY id LIMIT 1");
601 if (!empty($crow['code_text'])) {
602 $dispkey[1] = $crow['code_text'];
606 genStartRow("bgcolor='$bgcolor'");
608 genAnyCell($dispkey, false, 'detail');
610 // This is the column index for accumulating column totals.
611 $cnum = 0;
612 $totalsvcs = $areport[$key]['.wom'] + $areport[$key]['.men'];
614 // Generate data for this row.
615 foreach ($form_show as $value) {
616 // if ($value == '.total') { // Total Positives
617 // genNumCell($totalsvcs, $cnum++);
618 // }
619 if ($value == '.tneg') { // Total Negatives
620 genNumCell($areport[$key]['.neg'], $cnum++);
621 } else if ($value == '.age') { // Age
622 for ($i = 0; $i < 9; ++$i) {
623 genNumCell($areport[$key]['.age'][$i], $cnum++);
625 } else if (!empty($arr_titles[$value])) {
626 foreach ($arr_titles[$value] as $title => $dummy) {
627 genNumCell($areport[$key][$value][$title], $cnum++);
632 // Write the Total column data.
633 if ($form_output != 3) {
634 $atotals[$cnum] += $totalsvcs;
635 genAnyCell($totalsvcs, true, 'dehead');
638 genEndRow();
639 } // end foreach
641 if ($form_output != 3) {
642 // Generate the line of totals.
643 genStartRow("bgcolor='#dddddd'");
645 // genHeadCell(xl('Totals'));
646 // If the key is an MA or IPPF code, then add a column for its description.
647 if ($form_by === '5') {
648 genHeadCell(array(xl('Totals'), ''));
649 } else {
650 genHeadCell(xl('Totals'));
653 for ($cnum = 0; $cnum < count($atotals); ++$cnum) {
654 genHeadCell($atotals[$cnum], true);
657 genEndRow();
658 // End of table.
659 echo "</table>\n";
661 } // end of if refresh or export
663 if ($form_output != 3) {
665 </form>
666 </center>
668 <script language='JavaScript'>
669 <?php if ($form_output == 2) { ?>
670 var win = top.printLogPrint ? top : opener.top;
671 win.printLogPrint(window);
672 <?php } ?>
673 </script>
675 </body>
676 </html>
677 <?php
678 } // end not export