Huge Bootstrap 4 Classes Fix (#2807)
[openemr.git] / interface / patient_file / printed_fee_sheet.php
blob14fe48650ac320e4721cb1a2a66d0fc05b0ef637
1 <?php
2 /**
3 * 2012 - Refactored extensively to allow for creating multiple feesheets on demand
4 * uses a session array of PIDS by Medical Information Integration, LLC - mi-squared.com
6 * @package OpenEMR
7 * @link http://www.open-emr.org
8 * @author Rod Roark <rod@sunsetsystems.com>
9 * @author Brady Miller <brady.g.miller@gmail.com>
10 * @author Ron Pulcer <rspulcer_2k@yahoo.com>
11 * @author Stephen Waite <stephen.waite@cmsvt.com>
12 * @copyright Copyright (c) 2007-2016 Rod Roark <rod@sunsetsystems.com>
13 * @copyright Copyright (c) 2018 Brady Miller <brady.g.miller@gmail.com>
14 * @copyright Copyright (c) 2019 Ron Pulcer <rspulcer_2k@yahoo.com>
15 * @copyright Copyright (c) 2019 Stephen Waite <stephen.waite@cmsvt.com>
16 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
20 require_once("../globals.php");
21 require_once("$srcdir/acl.inc");
22 require_once("$srcdir/appointments.inc.php");
23 require_once("$srcdir/patient.inc");
24 require_once("$srcdir/user.inc");
26 use OpenEMR\Services\FacilityService;
28 $facilityService = new FacilityService();
30 function genColumn($ix)
32 global $html;
33 global $SBCODES;
34 for ($imax = count($SBCODES); $ix < $imax; ++$ix) {
35 $a = explode('|', $SBCODES[$ix], 2);
36 $cmd = trim($a[0]);
37 if ($cmd == '*C') { // column break
38 return++$ix;
41 if ($cmd == '*B') { // Borderless and empty
42 $html .= " <tr><td colspan='5' class='fscode' style='border-width:0 1px 0 0;padding-top:1px;' nowrap>&nbsp;</td></tr>\n";
43 } else if ($cmd == '*G') {
44 $title = text($a[1]);
45 if (!$title) {
46 $title = '&nbsp;';
49 $html .= " <tr><td colspan='5' align='center' class='fsgroup' style='vertical-align:middle' nowrap>$title</td></tr>\n";
50 } else if ($cmd == '*H') {
51 $title = text($a[1]);
52 if (!$title) {
53 $title = '&nbsp;';
56 $html .= " <tr><td colspan='5' class='fshead' style='vertical-align:middle' nowrap>$title</td></tr>\n";
57 } else {
58 $title = text($a[1]);
59 if (!$title) {
60 $title = '&nbsp;';
63 $b = explode(':', $cmd);
64 $html .= " <tr>\n";
65 $html .= " <td class='fscode' style='vertical-align:middle;width:14pt' nowrap>&nbsp;</td>\n";
66 if (count($b) <= 1) {
67 $code = text($b[0]);
68 if (!$code) {
69 $code = '&nbsp;';
72 $html .= " <td class='fscode' style='vertical-align:middle' nowrap>$code</td>\n";
73 $html .= " <td colspan='3' class='fscode' style='vertical-align:middle' nowrap>$title</td>\n";
74 } else {
75 $html .= " <td colspan='2' class='fscode' style='vertical-align:middle' nowrap>" . text($b[0]) . '/' . text($b[1]) . "</td>\n";
76 $html .= " <td colspan='2' class='fscode' style='vertical-align:middle' nowrap>$title</td>\n";
79 $html .= " </tr>\n";
83 return $ix;
86 // MAIN Body
88 // Build output to handle multiple pids and and superbill for each patient.
89 // This value is initially a maximum, and will be recomputed to
90 // distribute lines evenly among the pages. (was 55)
91 $lines_per_page = 55;
93 $lines_in_stats = 8;
95 $header_height = 44; // height of page headers in points
96 // This tells us if patient/encounter data is to be filled in.
97 // 1 = single PID from popup, 2=array of PIDs for session
99 if (empty($_GET['fill'])) {
100 $form_fill = 0;
101 } else {
102 $form_fill = $_GET['fill'];
105 // Show based on session array or single pid?
106 $pid_list = array();
107 $apptdate_list = array();
110 if (!empty($_SESSION['pidList']) and $form_fill == 2) {
111 $pid_list = $_SESSION['pidList'];
112 // If PID list is in Session, then Appt. Date list is expected to be a parallel array
113 $apptdate_list = $_SESSION['apptdateList'];
114 } else if ($form_fill == 1) {
115 array_push($pid_list, $pid); //get from active PID
116 } else {
117 array_push($pid_list, ''); // empty element for blank form
120 // This file is optional. You can create it to customize how the printed
121 // fee sheet looks, otherwise you'll get a mirror of your actual fee sheet.
123 if (file_exists("../../custom/fee_sheet_codes.php")) {
124 include_once("../../custom/fee_sheet_codes.php");
127 // TBD: Move these to globals.php, or make them user-specific.
128 $fontsize = 7;
129 $page_height = 700;
131 $padding = 0;
133 // The $SBCODES table is a simple indexed array whose values are
134 // strings of the form "code|text" where code may be either a billing
135 // code or one of the following:
137 // *H - A main heading, where "text" is its title (to be centered).
138 // *G - Specifies a new category, where "text" is its name.
139 // *B - A borderless blank row.
140 // *C - Ends the current column and starts a new one.
141 // If $SBCODES is not provided, then manufacture it from the Fee Sheet.
143 if (empty($SBCODES)) {
144 $SBCODES = array();
145 $last_category = '';
147 // Create entries based on the fee_sheet_options table.
148 $res = sqlStatement("SELECT * FROM fee_sheet_options " .
149 "ORDER BY fs_category, fs_option");
150 while ($row = sqlFetchArray($res)) {
151 $fs_category = $row['fs_category'];
152 $fs_option = $row['fs_option'];
153 $fs_codes = $row['fs_codes'];
154 if ($fs_category !== $last_category) {
155 $last_category = $fs_category;
156 $SBCODES[] = '*G|' . substr($fs_category, 1);
159 $SBCODES[] = " |" . substr($fs_option, 1);
162 // Create entries based on categories defined within the codes.
163 $pres = sqlStatement("SELECT option_id, title FROM list_options " .
164 "WHERE list_id = 'superbill' AND activity = 1 ORDER BY seq");
165 while ($prow = sqlFetchArray($pres)) {
166 $SBCODES[] = '*G|' . xl_list_label($prow['title']);
167 $res = sqlStatement("SELECT code_type, code, code_text FROM codes " .
168 "WHERE superbill = ? AND active = 1 " .
169 "ORDER BY code_text", array($prow['option_id']));
170 while ($row = sqlFetchArray($res)) {
171 $SBCODES[] = $row['code'] . '|' . $row['code_text'];
175 // Create one more group, for Products.
176 if ($GLOBALS['sell_non_drug_products']) {
177 $SBCODES[] = '*G|' . xl('Products');
178 $tres = sqlStatement("SELECT " .
179 "dt.drug_id, dt.selector, d.name, d.ndc_number " .
180 "FROM drug_templates AS dt, drugs AS d WHERE " .
181 "d.drug_id = dt.drug_id AND d.active = 1 " .
182 "ORDER BY d.name, dt.selector, dt.drug_id");
183 while ($trow = sqlFetchArray($tres)) {
184 $tmp = $trow['selector'];
185 if ($trow['name'] !== $trow['selector']) {
186 $tmp .= ' ' . $trow['name'];
189 $prodcode = empty($trow['ndc_number']) ? ('(' . $trow['drug_id'] . ')') :
190 $trow['ndc_number'];
191 $SBCODES[] = "$prodcode|$tmp";
195 // Extra stuff for the labs section.
196 $SBCODES[] = '*G|' . xl('Notes');
197 $percol = intval((count($SBCODES) + 2) / 3);
198 while (count($SBCODES) < $percol * 3) {
199 $SBCODES[] = '*B|';
202 // Adjust lines per page to distribute lines evenly among the pages.
203 $pages = intval(($percol + $lines_in_stats + $lines_per_page - 1) / $lines_per_page);
204 $lines_per_page = intval(($percol + $lines_in_stats + $pages - 1) / $pages);
206 // Figure out page and column breaks.
207 $pages = 1;
208 $lines = $percol;
209 $page_start_index = 0;
210 while ($lines + $lines_in_stats > $lines_per_page) {
211 ++$pages;
212 $lines_this_page = $lines > $lines_per_page ? $lines_per_page : $lines;
213 $lines -= $lines_this_page;
214 array_splice($SBCODES, $lines_this_page * 3 + $page_start_index, 0, '*C|');
215 array_splice($SBCODES, $lines_this_page * 2 + $page_start_index, 0, '*C|');
216 array_splice($SBCODES, $lines_this_page * 1 + $page_start_index, 0, '*C|');
217 $page_start_index += $lines_this_page * 3 + 3;
220 array_splice($SBCODES, $lines * 2 + $page_start_index, 0, '*C|');
221 array_splice($SBCODES, $lines * 1 + $page_start_index, 0, '*C|');
224 $lheight = sprintf('%d', ($page_height - $header_height) / $lines_per_page);
226 // Common HTML Header information
228 $html = "<html>
229 <head>";
231 $html .= "
232 <style>
233 body {
234 font-family: sans-serif;
235 font-weight: normal;
237 .bordertbl {
238 width: 100%;
239 border-style: solid;
240 border-width: 0 0 1px 1px;
241 border-spacing: 0;
242 border-collapse: collapse;
243 border-color: #999999;
245 td.toprow {
246 height: 1px;
247 padding: 0;
248 border-style: solid;
249 border-width: 0 0 0 0;
250 border-color: #999999;
252 td.fsgroup {
253 height: " . attr($lheight) . "pt;
254 font-family: sans-serif;
255 font-weight: bold;
256 font-size: " . attr($fontsize) . " pt;
257 background-color: #cccccc;
258 padding: " . attr($padding) . "pt 2pt 0pt 2pt;
259 border-style: solid;
260 border-width: 1px 1px 0 0;
261 border-color: #999999;
263 td.fshead {
264 height: " . attr($lheight) . "pt;
265 font-family: sans-serif;
266 font-weight: bold;
267 font-size: " . attr($fontsize) . "pt;
268 padding: " . attr($padding) . "pt 2pt 0pt 2pt;
269 border-style: solid;
270 border-width: 1px 1px 0 0;
271 border-color: #999999;
273 td.fscode {
274 height: " . attr($lheight) . "pt;
275 font-family: sans-serif;
276 font-weight: normal;
277 font-size: " . attr($fontsize) . "pt;
278 padding: " . attr($padding) . "pt 2pt 0pt 2pt;
279 border-style: solid;
280 border-width: 1px 1px 0 0;
281 border-color: #999999;
284 .ftitletable {
285 width: 100%;
286 height: " . attr($header_height) . "pt;
287 margin: 0 0 8pt 0;
289 .ftitlecell1 {
290 width: 33%;
291 vertical-align: top;
292 text-align: left;
293 font-size: 14pt;
294 font-weight: bold;
296 .ftitlecell2 {
297 width: 33%;
298 vertical-align: top;
299 text-align: right;
300 font-size: 9pt;
302 .ftitlecellm {
303 width: 34%;
304 vertical-align: top;
305 text-align: center;
306 font-size: 14pt;
307 font-weight: bold;
310 div.pagebreak {
311 page-break-after: always;
312 height: " . attr($page_height) . "pt;
314 </style>";
316 $html .= "<title>" . text($frow['name']) . "</title>
317 <script type='text/javascript' src='" . $GLOBALS['assets_static_relative'] . "/jquery/dist/jquery.min.js'></script>
318 <script type=\"text/javascript\" src=\"../../library/dialog.js?v=" . $v_js_includes . "\"></script>
319 <script language=\"JavaScript\">";
321 $html .= "
322 $(function() {
323 var win = top.printLogSetup ? top : opener.top;
324 win.printLogSetup(document.getElementById('printbutton'));
327 // Process click on Print button.
328 function printlog_before_print() {
329 var divstyle = document.getElementById('hideonprint').style;
330 divstyle.display = 'none';
333 </script>
334 </head>
335 <body bgcolor='#ffffff'>
336 <form name='theform' method='post' action='printed_fee_sheet.php?fill=" . attr_url($form_fill) . "'
337 onsubmit='return opener.top.restoreSession()'>
338 <div style='text-align: center;'>";
340 $today = date('Y-m-d');
342 $alertmsg = ''; // anything here pops up in an alert box
344 // Get details for the primary facility.
345 $frow = $facilityService->getPrimaryBusinessEntity();
347 // If primary is not set try to old method of guessing...for backward compatibility
348 if (empty($frow)) {
349 $frow = $facilityService->getPrimaryBusinessEntity(array("useLegacyImplementation" => true));
352 // Still missing...
353 if (empty($frow)) {
354 $alertmsg = xl("No Primary Business Entity selected in facility list");
357 $logo = '';
358 $ma_logo_path = "sites/" . $_SESSION['site_id'] . "/images/ma_logo.png";
359 if (is_file("$webserver_root/$ma_logo_path")) {
360 $logo = "<img src='$web_root/$ma_logo_path' style='height:" . round(9 * 5.14) . "pt' />";
361 } else {
362 $logo = "<!-- '$ma_logo_path' does not exist. -->";
365 // Loop on array of PIDS
366 $saved_pages = $pages; //Save calculated page count of a single fee sheet
367 $loop_idx = 0; // counter for appt list
369 foreach ($pid_list as $pid) {
370 $apptdate = $apptdate_list[$loop_idx]; // parallel array to pid_list
371 $appointment = fetchAppointments($apptdate, $apptdate, $pid); // Only expecting one row for pid
372 // Set Pagebreak for multi forms
373 if ($form_fill == 2) {
374 $html .= "<div class=pagebreak>\n";
375 } else {
376 $html .= "<div>\n";
379 if ($form_fill) {
380 // Get the patient's name and chart number.
381 $patdata = getPatientData($pid);
382 // Get the referring providers info
383 $referDoc = getUserIDInfo($patdata['ref_providerID']);
386 // This tracks our position in the $SBCODES array.
387 $cindex = 0;
389 while (--$pages >= 0) {
390 $html .= genFacilityTitle(xl('Superbill/Fee Sheet'), -1, $logo);
391 $html .= '<table style="width: 100%"><tr>' .
392 '<td>' . xlt('Patient') . ': <span style="font-weight: bold;">' . text($patdata['fname']) . ' ' . text($patdata['mname']) . ' ' . text($patdata['lname']) . '</span></td>' .
393 '<td>' . xlt('DOB') . ': <span style="font-weight: bold;">' . text(oeFormatShortDate($patdata['DOB'])) . '</span></td>' .
394 '<td>' . xlt('Date of Service') . ': <span style="font-weight: bold;">' . text(oeFormatShortDate($appointment[0]['pc_eventDate'])) . ' ' . text(oeFormatTime($appointment[0]['pc_startTime'])) . '</span></td>' .
395 '<td>' . xlt('Ref Prov') . ': <span style="font-weight: bold;">' . text($referDoc['fname']) . ' ' . text($referDoc['lname']) . '</span></td>' .
396 '</tr></table>';
397 $html .= "
398 <table class='bordertbl' cellspacing='0' cellpadding='0' width='100%'>
399 <tr>
400 <td valign='top'>
401 <table border='0' cellspacing='0' cellpadding='0' width='100%'>
402 <tr>
403 <td class='toprow' style='width:10%'></td>
404 <td class='toprow' style='width:10%'></td>
405 <td class='toprow' style='width:25%'></td>
406 <td class='toprow' style='width:55%'></td>
407 </tr>";
409 $cindex = genColumn($cindex); // Column 1
411 if ($pages == 0) { // if this is the last page
412 $html .= "<tr>
413 <td colspan='3' valign='top' class='fshead' style='height:" . $lheight * 2 . "pt'>";
414 $html .= xlt('Patient') . ": ";
416 if ($form_fill) {
417 $html .= text($patdata['fname'] . ' ' . $patdata['mname'] . ' ' . $patdata['lname']) . "<br />\n";
418 $html .= text($patdata['street']) . "<br />\n";
419 $html .= text($patdata['city'] . ', ' . $patdata['state'] . ' ' . $patdata['postal_code']) . "\n";
422 $html .= "</td>
423 <td valign='top' class='fshead'>";
424 $html .= xlt('DOB');
425 $html .= ": ";
427 if ($form_fill) {
428 $html .= text($patdata['DOB']);
429 $html .= "<br />";
432 $html .= xlt('ID');
433 $html .= ": ";
435 if ($form_fill) {
436 $html .= text($patdata['pubpid']);
439 $html .= "</td>
440 </tr>
441 <tr>
442 <td colspan='3' valign='top' class='fshead' style='height:${lheight}pt'>";
443 $html .= xlt('Provider');
444 $html .= ": ";
446 $encdata = false;
447 if ($form_fill && $encounter) {
448 $query = "SELECT fe.reason, fe.date, u.fname, u.mname, u.lname, u.username " .
449 "FROM forms AS f " .
450 "JOIN form_encounter AS fe ON fe.id = f.form_id " .
451 "LEFT JOIN users AS u ON u.username = f.user " .
452 "WHERE f.pid = ? AND f.encounter = ? AND f.formdir = 'newpatient' AND f.deleted = 0 " .
453 "ORDER BY f.id LIMIT 1";
454 $encdata = sqlQuery($query, array($pid, $encounter));
455 if (!empty($encdata['username'])) {
456 $html .= $encdata['fname'] . ' ' . $encdata['mname'] . ' ' . $encdata['lname'];
460 $html .= "</td>
461 <td valign='top' class='fshead'>";
462 $html .= xlt('Reason');
463 $html .= ":<br />";
465 if (!empty($encdata)) {
466 $html .= text($encdata['reason']);
469 // Note: You would think that pc_comments would have the Appt. comments,
470 // but it is actually stored in pc_hometext in DB table (openemr_postcalendar_events).
471 $html .= $appointment['pc_hometext'];
473 $html .= "</td>
474 </tr>
475 <tr>
476 <td colspan='4' valign='top' class='fshead' style='height:${lheight}pt'>";
478 if (empty($GLOBALS['ippf_specific'])) {
479 $html .= xlt('Insurance').":";
480 if ($form_fill) {
481 foreach (array('primary', 'secondary', 'tertiary') as $instype) {
482 $query = "SELECT * FROM insurance_data WHERE " .
483 "pid = ? AND type = ? " .
484 "ORDER BY date DESC LIMIT 1";
485 $row = sqlQuery($query, array($pid, $instype));
486 if ($row['provider']) {
487 $icobj = new InsuranceCompany($row['provider']);
488 $adobj = $icobj->get_address();
489 $insco_name = trim($icobj->get_name());
490 if ($instype != 'primary') {
491 $html .= ",";
494 if ($insco_name) {
495 $html .= "&nbsp;" . text($insco_name);
496 } else {
497 $html .= "&nbsp;<font color='red'><b>Missing Name</b></font>";
502 } else {
503 // IPPF wants a visit date box with the current date in it.
504 $html .= xlt('Visit date');
505 $html .= ":<br />\n";
506 if (!empty($encdata)) {
507 $html .= text(substr($encdata['date'], 0, 10));
508 } else {
509 $html .= text(oeFormatShortDate(date('Y-m-d'))) . "\n";
513 $html .= "</td>
514 </tr>
515 <tr>
516 <td colspan='4' valign='top' class='fshead' style='height:${lheight}pt'>";
517 $html .= xlt('Prior Visit');
518 $html .= ":<br />
519 </td>
520 </tr>
521 <tr>
522 <td colspan='4' valign='top' class='fshead' style='height:${lheight}pt'>";
523 $html .= xlt('Today\'s Charges');
524 $html .= ":<br />
525 </td>
526 </tr>
527 <tr>
528 <td colspan='4' valign='top' class='fshead' style='height:${lheight}pt'>";
529 $html .= xlt('Today\'s Balance');
530 $html .= ":<br />
531 </td>
532 </tr>
533 <tr>
534 <td colspan='4' valign='top' class='fshead' style='height:${lheight}pt'>";
535 $html .= xlt('Notes');
536 $html .= ":<br />
537 </td>
538 </tr>";
539 } // end if last page
541 $html .= "</table>
542 </td>
543 <td valign='top'>
544 <table border='0' cellspacing='0' cellpadding='0' width='100%'>
545 <tr>
546 <td class='toprow' style='width:10%'></td>
547 <td class='toprow' style='width:10%'></td>
548 <td class='toprow' style='width:25%'></td>
549 <td class='toprow' style='width:55%'></td>
550 </tr>";
552 $cindex = genColumn($cindex); // Column 2
554 if ($pages == 0) { // if this is the last page
555 $html .= "<tr>
556 <td colspan='4' valign='top' class='fshead' style='height:" . $lheight * 8 . "pt'>";
557 $html .= xlt('Notes');
558 $html .= ":<br />
559 </td>
560 </tr>";
561 } // end if last page
563 $html .= "</table>
564 </td>
565 <td valign='top'>
566 <table border='0' cellspacing='0' cellpadding='0' width='100%'>
567 <tr>
568 <td class='toprow' style='width:10%'></td>
569 <td class='toprow' style='width:10%'></td>
570 <td class='toprow' style='width:25%'></td>
571 <td class='toprow' style='width:55%'></td>
572 </tr>";
574 $cindex = genColumn($cindex); // Column 3
576 if ($pages == 0) { // if this is the last page
577 $html .= "<tr>
578 <td valign='top' colspan='4' class='fshead' style='height:" . $lheight * 6 . "pt;border-width:0 1px 0 0'>
579 &nbsp;
580 </td>
581 </tr>
582 <tr>
583 <td valign='top' colspan='4' class='fshead' style='height:" . $lheight * 2 . "pt'>";
584 $html .= xlt('Signature');
585 $html .= ":<br />
586 </td>
587 </tr>";
588 } // end if last page
590 $html .= "</table>
591 </td>
592 </tr>
594 </table>";
596 $html .= "</div>"; // end of div.pageLetter
597 } // end while
598 $pages = $saved_pages; // reset
599 $loop_idx++; // appt list counter
600 } // end foreach
602 // Common End Code
603 if ($form_fill != 2) { //use native browser 'print' for multipage
604 $html .= "<div id='hideonprint'>
606 <input type='button' value='";
608 $html .= xla('Print');
609 $html .="' id='printbutton' />
610 </div>";
613 $html .= "
614 </div>
615 </form>
616 </body>
617 </html>";
619 // Send final result to display
620 echo $html;