Merge pull request #1128 from bradymiller/unique-insurance-report-cleanup_1
[openemr.git] / interface / billing / sl_eob_search.php
blobf7863333243f246c99187993f7e68c3271a06ff2
1 <?php
2 /**
3 * This the first of two pages to support posting of EOBs.
4 * The second is sl_eob_invoice.php.
5 * Windows compatibility and statement downloading:
6 * 2009 Bill Cernansky and Tony McCormick [mi-squared.com]
8 * Copyright (C) 2005-2010 Rod Roark <rod@sunsetsystems.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 Jerry Padgett <sjpadgett@gmail.com>
25 * @link http://www.open-emr.org
27 require_once("../globals.php");
28 require_once("$srcdir/patient.inc");
29 require_once("$srcdir/invoice_summary.inc.php");
30 require_once("$srcdir/appointments.inc.php");
31 require_once($GLOBALS['OE_SITE_DIR'] . "/statement.inc.php");
32 require_once("$srcdir/parse_era.inc.php");
33 require_once("$srcdir/sl_eob.inc.php");
34 require_once("$srcdir/api.inc");
35 require_once("$srcdir/forms.inc");
36 require_once("$srcdir/../controllers/C_Document.class.php");
37 require_once("$srcdir/documents.php");
38 require_once("$srcdir/options.inc.php");
39 require_once("$srcdir/acl.inc");
41 $DEBUG = 0; // set to 0 for production, 1 to test
43 $alertmsg = '';
44 $where = '';
45 $eraname = '';
46 $eracount = 0;
47 /* Load dependencies only if we need them */
48 if (! empty($GLOBALS['portal_onsite_two_enable'])) {
49 /* Addition of onsite portal patient notify of invoice and reformated invoice - sjpadgett 01/2017 */
50 require_once("../../portal/lib/portal_mail.inc");
51 require_once("../../portal/lib/appsql.class.php");
53 function is_auth_portal($pid = 0)
55 if ($pData = sqlQuery("SELECT * FROM `patient_data` WHERE `pid` = ?", array(
56 $pid
57 ))) {
58 if ($pData['allow_patient_portal'] != "YES") {
59 return false;
60 } else {
61 $_SESSION['portalUser'] = strtolower($pData['fname']) . $pData['id'];
62 return true;
64 } else {
65 return false;
69 function notify_portal($thispid, array $invoices, $template, $invid)
71 $builddir = $GLOBALS['OE_SITE_DIR'] . '/documents/onsite_portal_documents/templates/' . $thispid;
72 if (! is_dir($builddir)) {
73 mkdir($builddir, 0755, true);
76 if (fixup_invoice($template, $builddir . '/invoice' . $invid . '.tpl') != true) {
77 return false;
80 if (SavePatientAudit($thispid, $invoices) != true) {
81 return false;
82 } // this is all the invoice data for portal auditing
83 $note = xl('You have an invoice due for payment in your Patient Documents. There you may pay, download or print the invoice. Thank you.');
84 if (sendMail($_SESSION['authUser'], $note, xlt('Bill/Collect'), '', '0', $_SESSION['authUser'], $_SESSION['authUser'], $_SESSION['portalUser'], $invoices[0]['patient'], "New", '0') == 1) { // remind admin this was sent
85 sendMail($_SESSION['portalUser'], $note, xlt('Bill/Collect'), '', '0', $_SESSION['authUser'], $_SESSION['authUser'], $_SESSION['portalUser'], $invoices[0]['patient'], "New", '0'); // notify patient
86 } else {
87 return false;
90 return true;
93 function fixup_invoice($template, $ifile)
95 $data = file_get_contents($template);
96 if ($data == "") {
97 return false;
100 if (! file_put_contents($ifile, $data)) {
101 return false;
104 return true;
107 function SavePatientAudit($pid, $invs)
109 $appsql = new ApplicationTable();
110 try {
111 $audit = array();
112 $audit['patient_id'] = $pid;
113 $audit['activity'] = "invoice";
114 $audit['require_audit'] = "0";
115 $audit['pending_action'] = "payment";
116 $audit['action_taken'] = "";
117 $audit['status'] = "waiting transaction";
118 $audit['narrative'] = "Request patient online payment.";
119 $audit['table_action'] = '';
120 $audit['table_args'] = json_encode($invs);
121 $audit['action_user'] = $pid;
122 $audit['action_taken_time'] = "";
123 $audit['checksum'] = "";
124 $edata = $appsql->getPortalAudit($pid, 'payment', 'invoice', "waiting transaction", 0);
125 if ($edata['id'] > 0) {
126 $appsql->portalAudit('update', $edata['id'], $audit);
127 } else {
128 $appsql->portalAudit('insert', '', $audit);
130 } catch (Exception $ex) {
131 return $ex;
134 return true;
138 // This is called back by parse_era() if we are processing X12 835's.
139 function era_callback(&$out)
141 global $where, $eracount, $eraname;
142 // print_r($out); // debugging
143 ++$eracount;
144 // $eraname = $out['isa_control_number'];
145 $eraname = $out['gs_date'] . '_' . ltrim($out['isa_control_number'], '0') .
146 '_' . ltrim($out['payer_id'], '0');
147 list($pid, $encounter, $invnumber) = slInvoiceNumber($out);
149 if ($pid && $encounter) {
150 if ($where) {
151 $where .= ' OR ';
154 $where .= "( f.pid = '$pid' AND f.encounter = '$encounter' )";
158 function bucks($amount)
160 if ($amount) {
161 echo oeFormatMoney($amount);
165 function validEmail($email)
167 if (preg_match("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$^", $email)) {
168 return true;
171 return false;
174 function emailLogin($patient_id, $message)
176 $patientData = sqlQuery("SELECT * FROM `patient_data` WHERE `pid`=?", array($patient_id));
177 if ($patientData['hipaa_allowemail'] != "YES" || empty($patientData['email']) || empty($GLOBALS['patient_reminder_sender_email'])) {
178 return false;
181 if (!(validEmail($patientData['email']))) {
182 return false;
185 if (!(validEmail($GLOBALS['patient_reminder_sender_email']))) {
186 return false;
189 if ($_SESSION['pc_facility']) {
190 $sql = "select * from facility where id=?";
191 $facility = sqlQuery($sql, array($_SESSION['pc_facility']));
192 } else {
193 $sql = "SELECT * FROM facility ORDER BY billing_location DESC LIMIT 1";
194 $facility = sqlQuery($sql);
197 $mail = new MyMailer();
198 $pt_name=$patientData['fname'].' '.$patientData['lname'];
199 $pt_email=$patientData['email'];
200 $email_subject=($facility['name'] . ' ' . xl('Patient Statement Bill'));
201 $email_sender=$GLOBALS['patient_reminder_sender_email'];
202 $mail->AddReplyTo($email_sender, $email_sender);
203 $mail->SetFrom($email_sender, $email_sender);
204 $mail->AddAddress($pt_email, $pt_name);
205 $mail->Subject = $email_subject;
206 $mail->MsgHTML("<html><body><div class='wrapper'>".$message."</div></body></html>");
207 $mail->IsHTML(true);
208 $mail->AltBody = $message;
210 if ($mail->Send()) {
211 return true;
212 } else {
213 $email_status = $mail->ErrorInfo;
214 error_log("EMAIL ERROR: ".$email_status, 0);
215 return false;
219 // Upload a file to the client's browser
221 function upload_file_to_client($file_to_send)
223 header("Pragma: public");
224 header("Expires: 0");
225 header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
226 header("Content-Type: application/force-download");
227 header("Content-Length: " . filesize($file_to_send));
228 header("Content-Disposition: attachment; filename=" . basename($file_to_send));
229 header("Content-Description: File Transfer");
230 readfile($file_to_send);
231 // flush the content to the browser. If you don't do this, the text from the subsequent
232 // output from this script will be in the file instead of sent to the browser.
233 flush();
234 exit(); //added to exit from process properly in order to stop bad html code -ehrlive
235 // sleep one second to ensure there's no follow-on.
236 sleep(1);
239 function upload_file_to_client_email($ppid, $file_to_send)
241 $message = "";
242 global $STMT_TEMP_FILE_PDF;
243 $file = fopen($file_to_send, "r");//this file contains the text to be converted to pdf.
244 while (!feof($file)) {
245 $OneLine=fgets($file);//one line is read
247 $message = $message.$OneLine.'<br>';
249 $countline++;
252 emailLogin($ppid, $message);
255 function upload_file_to_client_pdf($file_to_send, $aPatFirstName = '', $aPatID = null, $flagCFN = false)
257 //modified for statement title name
258 //Function reads a HTML file and converts to pdf.
260 $aPatFName = convert_safe_file_dir_name($aPatFirstName); //modified for statement title name
261 if ($flagCFN) {
262 $STMT_TEMP_FILE_PDF = $GLOBALS['temporary_files_dir'] . "/Stmt_{$aPatFName}_{$aPatID}.pdf";
263 } else {
264 global $STMT_TEMP_FILE_PDF;
267 global $srcdir;
269 if ($GLOBALS['statement_appearance'] == '1') {
270 require_once("$srcdir/html2pdf/vendor/autoload.php");
271 $pdf2 = new HTML2PDF(
272 $GLOBALS['pdf_layout'],
273 $GLOBALS['pdf_size'],
274 $GLOBALS['pdf_language'],
275 true, // default unicode setting is true
276 'UTF-8', // default encoding setting is UTF-8
277 array($GLOBALS['pdf_left_margin'],$GLOBALS['pdf_top_margin'],$GLOBALS['pdf_right_margin'],$GLOBALS['pdf_bottom_margin']),
278 $_SESSION['language_direction'] == 'rtl' ? true : false
280 ob_start();
281 readfile($file_to_send, "r");//this file contains the HTML to be converted to pdf.
282 //echo $file;
283 $content = ob_get_clean();
285 // Fix a nasty html2pdf bug - it ignores document root!
286 global $web_root, $webserver_root;
287 $i = 0;
288 $wrlen = strlen($web_root);
289 $wsrlen = strlen($webserver_root);
290 while (true) {
291 $i = stripos($content, " src='/", $i + 1);
292 if ($i === false) {
293 break;
296 if (substr($content, $i+6, $wrlen) === $web_root &&
297 substr($content, $i+6, $wsrlen) !== $webserver_root) {
298 $content = substr($content, 0, $i + 6) . $webserver_root . substr($content, $i + 6 + $wrlen);
302 $pdf2->WriteHTML($content);
303 $temp_filename = $STMT_TEMP_FILE_PDF;
304 $content_pdf = $pdf2->Output($STMT_TEMP_FILE_PDF, 'F');
305 } else {
306 $pdf = new Cezpdf('LETTER');//pdf creation starts
307 $pdf->ezSetMargins(45, 9, 36, 10);
308 $pdf->selectFont('Courier');
309 $pdf->ezSetY($pdf->ez['pageHeight'] - $pdf->ez['topMargin']);
310 $countline=1;
311 $file = fopen($file_to_send, "r");//this file contains the text to be converted to pdf.
312 while (!feof($file)) {
313 $OneLine=fgets($file);//one line is read
314 if (stristr($OneLine, "\014") == true && !feof($file)) {//form feed means we should start a new page.
315 $pdf->ezNewPage();
316 $pdf->ezSetY($pdf->ez['pageHeight'] - $pdf->ez['topMargin']);
317 str_replace("\014", "", $OneLine);
320 if (stristr($OneLine, 'REMIT TO') == true || stristr($OneLine, 'Visit Date') == true || stristr($OneLine, 'Future Appointments') == true || stristr($OneLine, 'Current') == true) { //lines are made bold when 'REMIT TO' or 'Visit Date' is there.
321 $pdf->ezText('<b>'.$OneLine.'</b>', 12, array('justification' => 'left', 'leading' => 6));
322 } else {
323 $pdf->ezText($OneLine, 12, array('justification' => 'left', 'leading' => 6));
326 $countline++;
329 $fh = @fopen($STMT_TEMP_FILE_PDF, 'w');//stored to a pdf file
330 if ($fh) {
331 fwrite($fh, $pdf->ezOutput());
332 fclose($fh);
336 header("Pragma: public");//this section outputs the pdf file to browser
337 header("Expires: 0");
338 header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
339 header("Content-Type: application/force-download");
340 header("Content-Length: " . filesize($STMT_TEMP_FILE_PDF));
341 header("Content-Disposition: attachment; filename=" . basename($STMT_TEMP_FILE_PDF));
342 header("Content-Description: File Transfer");
343 readfile($STMT_TEMP_FILE_PDF);
344 // flush the content to the browser. If you don't do this, the text from the subsequent
345 // output from this script will be in the file instead of sent to the browser.
346 flush();
347 exit(); //added to exit from process properly in order to stop bad html code -ehrlive
348 // sleep one second to ensure there's no follow-on.
349 sleep(1);
353 $today = date("Y-m-d");
354 // Print or download statements if requested.
356 if (($_POST['form_print'] || $_POST['form_download'] || $_POST['form_email'] || $_POST['form_pdf']) || $_POST['form_portalnotify'] && $_POST['form_cb']) {
357 $fhprint = fopen($STMT_TEMP_FILE, 'w');
359 $sqlBindArray = array();
360 $where = "";
361 foreach ($_POST['form_cb'] as $key => $value) {
362 $where .= " OR f.id = ?";
363 array_push($sqlBindArray, $key);
366 if (!empty($where)) {
367 $where = substr($where, 4);
368 $where = '( ' . $where . ' ) AND';
371 $res = sqlStatement("SELECT " .
372 "f.id, f.date, f.pid, f.encounter, f.stmt_count, f.last_stmt_date, f.last_level_closed, f.last_level_billed, f.billing_note as enc_billing_note, " .
373 "p.fname, p.mname, p.lname, p.street, p.city, p.state, p.postal_code, p.billing_note as pat_billing_note " .
374 "FROM form_encounter AS f, patient_data AS p " .
375 "WHERE $where " .
376 "p.pid = f.pid " .
377 "ORDER BY p.lname, p.fname, f.pid, f.date, f.encounter", $sqlBindArray);
379 $stmt = array();
380 $stmt_count = 0;
382 $flagT = true;
383 $aPatientFirstName = '';
384 $aPatientID = null;
385 $multiplePatients = false;
386 $usePatientNamePdf = false;
388 // get pids for delimits
389 // need to only use summary invoice for multi visits
390 $inv_pid = array();
391 $inv_count = -1;
392 if ($_POST['form_portalnotify']) {
393 foreach ($_POST['form_invpids'] as $key => $v) {
394 if ($_POST['form_cb'][$key]) {
395 array_push($inv_pid, key($v));
399 $rcnt = 0;
400 while ($row = sqlFetchArray($res)) {
401 $rows[] = $row;
402 if (!$inv_pid[$rcnt]) {
403 array_push($inv_pid, $row['pid']);
405 $rcnt++;
407 // This loops once for each invoice/encounter.
409 $rcnt = 0;
410 while ($row = $rows[$rcnt++]) {
411 $svcdate = substr($row['date'], 0, 10);
412 $duedate = $svcdate; // TBD?
413 $duncount = $row['stmt_count'];
414 $enc_note = $row['enc_billing_note'];
416 if ($flagT) {
417 $flagT = false;
418 $aPatientFirstName = $row['fname'];
419 $aPatientID = $row['pid'];
420 $usePatientNamePdf = true;
421 } elseif (!$multiplePatients) {
422 if ($aPatientID != $row['pid']) {
423 $multiplePatients = true;
424 $aPatientFirstName = '';
425 $aPatientID = null;
426 $usePatientNamePdf = false;
430 // If this is a new patient then print the pending statement
431 // and start a new one. This is an associative array:
433 // cid = same as pid
434 // pid = OpenEMR patient ID
435 // patient = patient name
436 // amount = total amount due
437 // adjust = adjustments (already applied to amount)
438 // duedate = due date of the oldest included invoice
439 // age = number of days from duedate to today
440 // to = array of addressee name/address lines
441 // lines = array of:
442 // dos = date of service "yyyy-mm-dd"
443 // desc = description
444 // amount = charge less adjustments
445 // paid = amount paid
446 // notice = 1 for first notice, 2 for second, etc.
447 // detail = array of details, see invoice_summary.inc.php
449 if ($stmt['cid'] != $row['pid']) {
450 if (!empty($stmt)) {
451 ++$stmt_count;
454 $stmt['cid'] = $row['pid'];
455 $stmt['pid'] = $row['pid'];
456 $stmt['dun_count'] = $row['stmt_count'];
457 $stmt['bill_note'] = $row['pat_billing_note'];
458 $stmt['enc_bill_note'] = $row['enc_billing_note'];
459 $stmt['bill_level'] = $row['last_level_billed'];
460 $stmt['level_closed'] = $row['last_level_closed'];
461 $stmt['patient'] = $row['fname'] . ' ' . $row['lname'];
462 $stmt['encounter'] = $row['encounter'];
463 #If you use the field in demographics layout called
464 #guardiansname this will allow you to send statements to the parent
465 #of a child or a guardian etc
466 if (strlen($row['guardiansname']) == 0) {
467 $stmt['to'] = array($row['fname'] . ' ' . $row['lname']);
468 } else {
469 $stmt['to'] = array($row['guardiansname']);
472 if ($row['street']) {
473 $stmt['to'][] = $row['street'];
476 $stmt['to'][] = $row['city'] . ", " . $row['state'] . " " . $row['postal_code'];
477 $stmt['lines'] = array();
478 $stmt['amount'] = '0.00';
479 $stmt['ins_paid'] = 0;
480 $stmt['today'] = $today;
481 $stmt['duedate'] = $duedate;
482 } else {
483 // Report the oldest due date.
484 if ($duedate < $stmt['duedate']) {
485 $stmt['duedate'] = $duedate;
489 // Recompute age at each invoice.
490 $stmt['age'] = round((strtotime($today) - strtotime($stmt['duedate'])) / (24 * 60 * 60));
492 $invlines = ar_get_invoice_summary($row['pid'], $row['encounter'], true);
493 foreach ($invlines as $key => $value) {
494 $line = array();
495 $line['dos'] = $svcdate;
496 if ($GLOBALS['use_custom_statement']) {
497 $line['desc'] = ($key == 'CO-PAY') ? "Patient Payment" : $value['code_text'];
498 } else {
499 $line['desc'] = ($key == 'CO-PAY') ? "Patient Payment" : "Procedure $key";
502 $line['amount'] = sprintf("%.2f", $value['chg']);
503 $line['adjust'] = sprintf("%.2f", $value['adj']);
504 $line['paid'] = sprintf("%.2f", $value['chg'] - $value['bal']);
505 $line['notice'] = $duncount + 1;
506 $line['detail'] = $value['dtl'];
507 $stmt['lines'][] = $line;
508 $stmt['amount'] = sprintf("%.2f", $stmt['amount'] + $value['bal']);
509 $stmt['ins_paid'] = $stmt['ins_paid'] + $value['ins'];
512 // Record that this statement was run.
513 if (! $DEBUG && ! $_POST['form_without']) {
514 sqlStatement("UPDATE form_encounter SET " .
515 "last_stmt_date = '$today', stmt_count = stmt_count + 1 " .
516 "WHERE id = " . $row['id']);
518 $inv_count += 1;
519 if ($_POST['form_portalnotify']) {
520 if (! is_auth_portal($stmt['pid'])) {
521 $alertmsg = xlt('Notification FAILED: Not Portal Authorized');
522 break;
524 $pvoice[] = $stmt;
525 // we don't want to send the portal multiple invoices, thus this. Last invoice for pid is summary.
526 if ($inv_pid[$inv_count] != $inv_pid[$inv_count + 1]) {
527 fwrite($fhprint, make_statement($stmt));
528 if (! notify_portal($stmt['pid'], $pvoice, $STMT_TEMP_FILE, $stmt['pid'] . "-" . $stmt['encounter'])) {
529 $alertmsg = xlt('Notification FAILED');
530 break;
533 $pvoice = array();
534 flush();
535 ftruncate($fhprint, 0);
536 } else {
537 continue;
539 } else {
540 if ($inv_pid[$inv_count] != $inv_pid[$inv_count + 1]) {
541 $tmp = make_statement($stmt);
542 if (empty($tmp)) {
543 $tmp = xlt("This EOB item does not meet minimum print requirements setup in Globals or there is an unknown error.") . " " . xlt("EOB Id") . ":" . $inv_pid[$inv_count] . " " . xlt("Encounter") . ":" . $stmt[encounter] . "\n";
544 $tmp .= "<br />\n\014<br /><br />";
546 fwrite($fhprint, $tmp);
549 } // end while
551 if (!empty($stmt)) {
552 ++$stmt_count;
555 fclose($fhprint);
556 sleep(1);
557 // Download or print the file, as selected
558 if ($_POST['form_download']) {
559 upload_file_to_client($STMT_TEMP_FILE);
560 } elseif ($_POST['form_pdf']) {
561 upload_file_to_client_pdf($STMT_TEMP_FILE, $aPatientFirstName, $aPatientID, $usePatientNamePdf);
562 } elseif ($_POST['form_email']) {
563 upload_file_to_client_email($stmt['pid'], $STMT_TEMP_FILE);
564 } elseif ($_POST['form_portalnotify']) {
565 if ($alertmsg == "") {
566 $alertmsg = xl('Sending Invoice to Patient Portal Completed');
568 } else { // Must be print!
569 if ($DEBUG) {
570 $alertmsg = xl("Printing skipped; see test output in") .' '. $STMT_TEMP_FILE;
571 } else {
572 exec("$STMT_PRINT_CMD $STMT_TEMP_FILE");
573 if ($_POST['form_without']) {
574 $alertmsg = xl('Now printing') .' '. $stmt_count .' '. xl('statements; invoices will not be updated.');
575 } else {
576 $alertmsg = xl('Now printing') .' '. $stmt_count .' '. xl('statements and updating invoices.');
578 } // end not debug
579 } // end not form_download
580 } // end statements requested
582 <html>
583 <head>
584 <?php html_header_show(); ?>
585 <link rel=stylesheet href="<?php echo $css_header;?>" type="text/css">
586 <title><?php xl('EOB Posting - Search', 'e'); ?></title>
587 <script type="text/javascript" src="../../library/textformat.js"></script>
589 <script language="JavaScript">
591 var mypcc = '1';
593 function checkAll(checked) {
594 var f = document.forms[0];
595 for (var i = 0; i < f.elements.length; ++i) {
596 var ename = f.elements[i].name;
597 if (ename.indexOf('form_cb[') == 0)
598 f.elements[i].checked = checked;
602 function npopup(pid) {
603 window.open('sl_eob_patient_note.php?patient_id=' + pid, '_blank', 'width=500,height=250,resizable=1');
604 return false;
607 </script>
609 </head>
611 <body leftmargin='0' topmargin='0' marginwidth='0' marginheight='0'>
612 <center>
614 <form method='post' action='sl_eob_search.php' enctype='multipart/form-data'>
616 <table border='0' cellpadding='5' cellspacing='0'>
617 <tr>
619 <?php
620 // Identify the payer to support resumable posting sessions.
621 echo " <td>\n";
622 echo " " . xl('Payer') . ":\n";
623 echo " </td>\n";
624 echo " <td>\n";
625 $insurancei = getInsuranceProviders();
626 echo " <select name='form_payer_id'>\n";
627 echo " <option value='0'>-- " . xl('Patient') . " --</option>\n";
628 foreach ($insurancei as $iid => $iname) {
629 echo "<option value='$iid'";
630 if ($iid == $_POST['form_payer_id']) {
631 echo " selected";
634 echo ">" . $iname . "</option>\n";
637 echo " </select>\n";
638 echo " </td>\n";
641 <td>
642 <?php xl('Source:', 'e'); ?>
643 </td>
644 <td>
645 <input type='text' name='form_source' size='10' value='<?php echo $_POST['form_source']; ?>'
646 title='<?php xl("A check number or claim number to identify the payment", "e"); ?>'>
647 </td>
648 <td>
649 <?php xl('Pay Date:', 'e'); ?>
650 </td>
651 <td>
652 <input type='text' name='form_paydate' size='10' value='<?php echo $_POST['form_paydate']; ?>'
653 onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)'
654 title='<?php xl("Date of payment yyyy-mm-dd", "e"); ?>'>
655 </td>
657 <td>
658 <?php xl('Deposit Date:', 'e'); ?>
659 </td>
660 <td>
661 <input type='text' name='form_deposit_date' size='10' value='<?php echo $_POST['form_deposit_date']; ?>'
662 onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)'
663 title='<?php xl("Date of bank deposit yyyy-mm-dd", "e"); ?>'>
664 </td>
666 <td>
667 <?php xl('Amount:', 'e'); ?>
668 </td>
669 <td>
670 <input type='text' name='form_amount' size='10' value='<?php echo $_POST['form_amount']; ?>'
671 title='<?php xl("Paid amount that you will allocate", "e"); ?>'>
672 </td>
673 <td align='right'>
674 <a href='sl_eob_help.php' target='_blank'><?php xl('Help', 'e'); ?></a>
675 </td>
677 </tr>
678 </table>
680 <table border='0' cellpadding='5' cellspacing='0'>
682 <tr bgcolor='#ddddff'>
683 <td>
684 <?php xl('Name:', 'e'); ?>
685 </td>
686 <td>
687 <input type='text' name='form_name' size='10' value='<?php echo $_POST['form_name']; ?>'
688 title='<?php xl("Any part of the patient name, or \"last,first\", or \"X-Y\"", "e"); ?>'>
689 </td>
690 <td>
691 <?php xl('Chart ID:', 'e'); ?>
692 </td>
693 <td>
694 <input type='text' name='form_pid' size='10' value='<?php echo $_POST['form_pid']; ?>'
695 title='<?php xl("Patient chart ID", "e"); ?>'>
696 </td>
697 <td>
698 <?php xl('Encounter:', 'e'); ?>
699 </td>
700 <td>
701 <input type='text' name='form_encounter' size='10' value='<?php echo $_POST['form_encounter']; ?>'
702 title='<?php xl("Encounter number", "e"); ?>'>
703 </td>
704 <td>
705 <?php xl('Svc Date:', 'e'); ?>
706 </td>
707 <td>
708 <input type='text' name='form_date' size='10' value='<?php echo $_POST['form_date']; ?>'
709 title='<?php xl("Date of service mm/dd/yyyy", "e"); ?>'>
710 </td>
711 <td>
712 <?php xl('To:', 'e'); ?>
713 </td>
714 <td>
715 <input type='text' name='form_to_date' size='10' value='<?php echo $_POST['form_to_date']; ?>'
716 title='<?php xl("Ending DOS mm/dd/yyyy if you wish to enter a range", "e"); ?>'>
717 </td>
718 <td>
719 <select name='form_category'>
720 <?php
721 foreach (array(xl('Open'), xl('All'), xl('Due Pt'), xl('Due Ins')) as $value) {
722 echo " <option value='$value'";
723 if ($_POST['form_category'] == $value) {
724 echo " selected";
727 echo ">$value</option>\n";
730 </select>
731 </td>
732 <td>
733 <input type='submit' name='form_search' value='<?php xl("Search", "e"); ?>'>
734 </td>
735 </tr>
736 <!-- Filter - only show those who have debt -->
737 <tr bgcolor='#ddddff'>
738 <td colspan='12'>
740 <span><?php echo xlt('Patients with debt');?>
741 <input <?php echo $_POST['only_with_debt']?'checked=checked':'';?> type="checkbox" name="only_with_debt" />
742 </span>
744 </td>
745 </tr>
746 <!-- Support for X12 835 upload -->
747 <tr bgcolor='#ddddff'>
748 <td colspan='12'>
749 <?php xl('Or upload ERA file:', 'e'); ?>
750 <input type="hidden" name="MAX_FILE_SIZE" value="5000000" />
751 <input name="form_erafile" type="file" />
752 </td>
753 </tr>
755 <tr>
756 <td height="1" colspan="10">
757 </td>
758 </tr>
760 </table>
762 <?php
763 if ($_POST['form_search'] || $_POST['form_print']) {
764 $form_name = trim($_POST['form_name']);
765 $form_pid = trim($_POST['form_pid']);
766 $form_encounter = trim($_POST['form_encounter']);
767 $form_date = fixDate($_POST['form_date'], "");
768 $form_to_date = fixDate($_POST['form_to_date'], "");
770 $where = "";
772 // Handle X12 835 file upload.
774 if ($_FILES['form_erafile']['size']) {
775 $tmp_name = $_FILES['form_erafile']['tmp_name'];
777 // Handle .zip extension if present. Probably won't work on Windows.
778 if (strtolower(substr($_FILES['form_erafile']['name'], -4)) == '.zip') {
779 rename($tmp_name, "$tmp_name.zip");
780 exec("unzip -p $tmp_name.zip > $tmp_name");
781 unlink("$tmp_name.zip");
784 echo "<!-- Notes from ERA upload processing:\n";
785 $alertmsg .= parse_era($tmp_name, 'era_callback');
786 echo "-->\n";
787 $erafullname = $GLOBALS['OE_SITE_DIR'] . "/era/$eraname.edi";
789 if (is_file($erafullname)) {
790 $alertmsg .= "Warning: Set $eraname was already uploaded ";
791 if (is_file($GLOBALS['OE_SITE_DIR'] . "/era/$eraname.html")) {
792 $alertmsg .= "and processed. ";
793 } else {
794 $alertmsg .= "but not yet processed. ";
798 rename($tmp_name, $erafullname);
799 } // End 835 upload
801 if ($eracount) {
802 // Note that parse_era() modified $eracount and $where.
803 if (! $where) {
804 $where = '1 = 2';
806 } else {
807 if ($form_name) {
808 if ($where) {
809 $where .= " AND ";
812 // Allow the last name to be followed by a comma and some part of a first name.
813 if (preg_match('/^(.*\S)\s*,\s*(.*)/', $form_name, $matches)) {
814 $where .= "p.lname LIKE '" . $matches[1] . "%' AND p.fname LIKE '" . $matches[2] . "%'";
815 // Allow a filter like "A-C" on the first character of the last name.
816 } else if (preg_match('/^(\S)\s*-\s*(\S)$/', $form_name, $matches)) {
817 $tmp = '1 = 2';
818 while (ord($matches[1]) <= ord($matches[2])) {
819 $tmp .= " OR p.lname LIKE '" . $matches[1] . "%'";
820 $matches[1] = chr(ord($matches[1]) + 1);
823 $where .= "( $tmp ) ";
824 } else {
825 $where .= "p.lname LIKE '%$form_name%'";
829 if ($form_pid) {
830 if ($where) {
831 $where .= " AND ";
834 $where .= "f.pid = '$form_pid'";
837 if ($form_encounter) {
838 if ($where) {
839 $where .= " AND ";
842 $where .= "f.encounter = '$form_encounter'";
845 if ($form_date) {
846 if ($where) {
847 $where .= " AND ";
850 if ($form_to_date) {
851 $where .= "f.date >= '$form_date' AND f.date <= '$form_to_date'";
852 } else {
853 $where .= "f.date = '$form_date'";
857 if (! $where) {
858 if ($_POST['form_category'] == 'All') {
859 die(xl("At least one search parameter is required if you select All."));
860 } else {
861 $where = "1 = 1";
866 // Notes that as of release 4.1.1 the copays are stored
867 // in the ar_activity table marked with a PCP in the account_code column.
868 $query = "SELECT f.id, f.pid, f.encounter, f.date, " .
869 "f.last_level_billed, f.last_level_closed, f.last_stmt_date, f.stmt_count, " .
870 "p.fname, p.mname, p.lname, p.pubpid, p.billing_note, " .
871 "( SELECT SUM(b.fee) FROM billing AS b WHERE " .
872 "b.pid = f.pid AND b.encounter = f.encounter AND " .
873 "b.activity = 1 AND b.code_type != 'COPAY' ) AS charges, " .
874 "( SELECT SUM(a.pay_amount) FROM ar_activity AS a WHERE " .
875 "a.pid = f.pid AND a.encounter = f.encounter AND a.payer_type = 0 AND a.account_code = 'PCP')*-1 AS copays, " .
876 "( SELECT SUM(a.pay_amount) FROM ar_activity AS a WHERE " .
877 "a.pid = f.pid AND a.encounter = f.encounter AND a.account_code != 'PCP') AS payments, " .
878 "( SELECT SUM(a.adj_amount) FROM ar_activity AS a WHERE " .
879 "a.pid = f.pid AND a.encounter = f.encounter ) AS adjustments " .
880 "FROM form_encounter AS f " .
881 "JOIN patient_data AS p ON p.pid = f.pid " .
882 "WHERE $where " .
883 "ORDER BY p.lname, p.fname, p.mname, f.pid, f.encounter";
885 // Note that unlike the SQL-Ledger case, this query does not weed
886 // out encounters that are paid up. Also the use of sub-selects
887 // will require MySQL 4.1 or greater.
889 // echo "<!-- $query -->\n"; // debugging
891 $t_res = sqlStatement($query);
893 $num_invoices = sqlNumRows($t_res);
894 if ($eracount && $num_invoices != $eracount) {
895 $alertmsg .= "Of $eracount remittances, there are $num_invoices " .
896 "matching encounters in OpenEMR. ";
900 <table border='0' cellpadding='1' cellspacing='2' width='98%'>
902 <tr bgcolor="#dddddd">
903 <td class="id">
904 <?php xl('id', 'e');?>
905 </td>
906 <td class="dehead">
907 &nbsp;<?php xl('Patient', 'e'); ?>
908 </td>
909 <td class="dehead">
910 &nbsp;<?php xl('Invoice', 'e'); ?>
911 </td>
912 <td class="dehead">
913 &nbsp;<?php xl('Svc Date', 'e'); ?>
914 </td>
915 <td class="dehead">
916 &nbsp;<?php xl('Last Stmt', 'e'); ?>
917 </td>
918 <td class="dehead" align="right">
919 <?php xl('Charge', 'e'); ?>&nbsp;
920 </td>
921 <td class="dehead" align="right">
922 <?php xl('Adjust', 'e'); ?>&nbsp;
923 </td>
924 <td class="dehead" align="right">
925 <?php xl('Paid', 'e'); ?>&nbsp;
926 </td>
927 <td class="dehead" align="right">
928 <?php xl('Balance', 'e'); ?>&nbsp;
929 </td>
930 <td class="dehead" align="center">
931 <?php xl('Prv', 'e'); ?>
932 </td>
933 <?php if (!$eracount) { ?>
934 <td class="dehead" align="left">
935 <?php xl('Sel', 'e'); ?>
936 </td>
937 <td class="dehead" align="center">
938 <?php xl('Email', 'e'); ?>
939 </td>
941 <?php } ?>
942 </tr>
944 <?php
945 $orow = -1;
947 while ($row = sqlFetchArray($t_res)) {
948 $balance = sprintf("%.2f", $row['charges'] + $row['copays'] - $row['payments'] - $row['adjustments']);
949 //new filter only patients with debt.
950 if ($_POST['only_with_debt'] && $balance <= 0) {
951 continue;
955 if ($_POST['form_category'] != 'All' && $eracount == 0 && $balance == 0) {
956 continue;
959 // $duncount was originally supposed to be the number of times that
960 // the patient was sent a statement for this invoice.
962 $duncount = $row['stmt_count'];
964 // But if we have not yet billed the patient, then compute $duncount as a
965 // negative count of the number of insurance plans for which we have not
966 // yet closed out insurance.
968 if (! $duncount) {
969 for ($i = 1; $i <= 3 && arGetPayerID($row['pid'], $row['date'], $i);
970 ++$i) {
973 $duncount = $row['last_level_closed'] + 1 - $i;
976 $isdueany = ($balance > 0);
978 // An invoice is now due from the patient if money is owed and we are
979 // not waiting for insurance to pay.
981 $isduept = ($duncount >= 0 && $isdueany) ? " checked" : "";
983 // Skip invoices not in the desired "Due..." category.
985 if (substr($_POST['form_category'], 0, 3) == 'Due' && !$isdueany) {
986 continue;
989 if ($_POST['form_category'] == 'Due Ins' && ($duncount >= 0 || !$isdueany)) {
990 continue;
993 if ($_POST['form_category'] == 'Due Pt' && ($duncount < 0 || !$isdueany)) {
994 continue;
997 $bgcolor = ((++$orow & 1) ? "#ffdddd" : "#ddddff");
999 $svcdate = substr($row['date'], 0, 10);
1000 $last_stmt_date = empty($row['last_stmt_date']) ? '' : $row['last_stmt_date'];
1002 // Determine if customer is in collections.
1004 $billnote = $row['billing_note'];
1005 $in_collections = stristr($billnote, 'IN COLLECTIONS') !== false;
1007 <tr bgcolor='<?php echo $bgcolor ?>'>
1008 <td class="detail">
1009 <a href="" onclick="return npopup(<?php echo $row['pid'] ?>)"><?php echo $row['pid'];?></a>
1010 </td>
1011 <td class="detail">
1012 &nbsp;<a href="" onclick="return npopup(<?php echo $row['pid'] ?>)"
1013 ><?php echo $row['lname'] . ', ' . $row['fname']; ?></a>
1014 </td>
1015 <td class="detail">
1016 &nbsp;<a href="sl_eob_invoice.php?id=<?php echo $row['id'] ?>"
1017 target="_blank"><?php echo $row['pid'] . '.' . $row['encounter']; ?></a>
1018 </td>
1019 <td class="detail">
1020 &nbsp;<?php echo oeFormatShortDate($svcdate) ?>
1021 </td>
1022 <td class="detail">
1023 &nbsp;<?php echo oeFormatShortDate($last_stmt_date) ?>
1024 </td>
1025 <td class="detail" align="right">
1026 <?php bucks($row['charges']) ?>&nbsp;
1027 </td>
1028 <td class="detail" align="right">
1029 <?php bucks($row['adjustments']) ?>&nbsp;
1030 </td>
1031 <td class="detail" align="right">
1032 <?php bucks($row['payments'] - $row['copays']); ?>&nbsp;
1033 </td>
1034 <td class="detail" align="right">
1035 <?php bucks($balance); ?>&nbsp;
1036 </td>
1037 <td class="detail" align="center">
1038 <?php echo $duncount ? $duncount : "&nbsp;" ?>
1039 </td>
1040 <?php if (!$eracount) { ?>
1041 <td class="detail" align="left">
1042 <input type='checkbox' name='form_cb[<?php echo($row['id']) ?>]'<?php echo $isduept ?> />
1043 <?php if ($in_collections) {
1044 echo "<b><font color='red'>IC</font></b>";
1045 } ?>
1046 <?php if (function_exists('is_auth_portal') ? is_auth_portal($row['pid']) : false) {
1047 echo(' PPt');
1048 echo("<input type='hidden' name='form_invpids[". $row['id'] ."][". $row['pid'] ."]' />");
1049 $is_portal = true;
1051 </td>
1052 <?php } ?>
1053 <td class="detail" align="left">
1054 <?php
1055 $patientData = sqlQuery("SELECT * FROM `patient_data` WHERE `pid`=?", array($row['pid']));
1056 if ($patientData['hipaa_allowemail'] == "YES" && $patientData['allow_patient_portal'] == "YES" && $patientData['hipaa_notice'] == "YES" && validEmail($patientData['email'])) {
1057 echo xlt("YES");
1058 } else {
1059 echo xlt("NO");
1062 </td>
1064 </tr>
1065 <?php
1066 } // end while
1067 } // end search/print logic
1071 </table>
1074 <?php if ($eracount) { ?>
1075 <input type='button' value='<?php xl('Process ERA File', 'e')?>' onclick='processERA()' /> &nbsp;
1076 <?php } else { ?>
1077 <input type='button' value='<?php xl('Select All', 'e')?>' onclick='checkAll(true)' /> &nbsp;
1078 <input type='button' value='<?php xl('Clear All', 'e')?>' onclick='checkAll(false)' /> &nbsp;
1079 <?php if ($GLOBALS['statement_appearance'] != '1') { ?>
1080 <input type='submit' name='form_print' value='<?php xl('Print Selected Statements', 'e'); ?>' /> &nbsp;
1081 <input type='submit' name='form_download' value='<?php xl('Download Selected Statements', 'e'); ?>' /> &nbsp;
1082 <?php } ?>
1083 <input type='submit' name='form_pdf' value='<?php xl('PDF Download Selected Statements', 'e'); ?>' /> &nbsp;
1084 <input type='submit' name='form_email' value='<?php xl('Email Selected Statements', 'e'); ?>' /> &nbsp;
1085 <?php if ($is_portal) {?>
1086 <input type='submit' name='form_portalnotify' value='<?php xl('Notify via Patient Portal', 'e'); ?>' /> &nbsp;
1087 <?php }
1089 <input type='checkbox' name='form_without' value='1' /> <?php xl('Without Update', 'e'); ?>
1090 </p>
1092 </form>
1093 </center>
1094 <script language="JavaScript">
1095 function processERA() {
1096 var f = document.forms[0];
1097 var debug = f.form_without.checked ? '1' : '0';
1098 var paydate = f.form_paydate.value;
1099 window.open('sl_eob_process.php?eraname=<?php echo $eraname ?>&debug=' + debug + '&paydate=' + paydate + '&original=original', '_blank');
1100 return false;
1102 <?php
1103 if ($alertmsg) {
1104 echo "alert('" . htmlentities($alertmsg) . "');\n";
1108 </script>
1109 </body>
1110 </html>