3 * This provides for manual posting of EOBs. It is invoked from
4 * sl_eob_search.php. For automated (X12 835) remittance posting
5 * see sl_eob_process.php.
7 * Copyright (C) 2005-2016 Rod Roark <rod@sunsetsystems.com>
9 * LICENSE: This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
21 * @author Rod Roark <rod@sunsetsystems.com>
22 * @author Roberto Vasquez <robertogagliotta@gmail.com>
23 * @author Terry Hill <terry@lillysystems.com>
24 * @link http://www.open-emr.org
27 require_once("../globals.php");
28 require_once("$srcdir/log.inc");
29 require_once("$srcdir/patient.inc");
30 require_once("$srcdir/forms.inc");
31 require_once("$srcdir/sl_eob.inc.php");
32 require_once("$srcdir/invoice_summary.inc.php");
33 require_once("../../custom/code_types.inc.php");
35 $debug = 0; // set to 1 for debugging mode
38 // If we permit deletion of transactions. Might change this later.
43 // Format money for display.
45 function bucks($amount)
48 printf("%.2f", $amount);
52 // Delete rows, with logging, for the specified table using the
53 // specified WHERE clause. Borrowed from deleter.php.
55 function row_delete($table, $where)
57 $tres = sqlStatement("SELECT * FROM $table WHERE $where");
59 while ($trow = sqlFetchArray($tres)) {
61 foreach ($trow as $key => $value) {
62 if (! $value ||
$value == '0000-00-00 00:00:00') {
70 $logstring .= $key . "='" . addslashes($value) . "'";
73 newEvent("delete", $_SESSION['authUser'], $_SESSION['authProvider'], 1, "$table: $logstring");
78 $query = "DELETE FROM $table WHERE $where";
79 echo $query . "<br>\n";
86 <?php
html_header_show(); ?
>
87 <link rel
=stylesheet href
="<?php echo $css_header;?>" type
="text/css">
88 <title
><?php
xl('EOB Posting - Invoice', 'e')?
></title
>
89 <script language
="JavaScript">
91 // An insurance radio button is selected.
92 function setins(istr
) {
96 // Compute an adjustment that writes off the balance:
97 function writeoff(code
) {
98 var f
= document
.forms
[0];
99 var belement
= f
['form_line[' + code +
'][bal]'];
100 var pelement
= f
['form_line[' + code +
'][pay]'];
101 var aelement
= f
['form_line[' + code +
'][adj]'];
102 var relement
= f
['form_line[' + code +
'][reason]'];
103 var tmp
= belement
.value
- pelement
.value
;
104 aelement
.value
= Number(tmp
).toFixed(2);
105 if (aelement
.value
&& ! relement
.value
) relement
.selectedIndex
= 1;
109 // Onsubmit handler. A good excuse to write some JavaScript.
110 function validate(f
) {
112 for (var i
= 0; i
< f
.elements
.length
; ++i
) {
113 var ename
= f
.elements
[i
].name
;
115 if (ename
.substring(0, 9) == 'form_del[') {
116 if (f
.elements
[i
].checked
) ++delcount
;
119 var pfxlen
= ename
.indexOf('[pay]');
120 if (pfxlen
< 0) continue;
121 var pfx
= ename
.substring(0, pfxlen
);
122 var code
= pfx
.substring(pfx
.indexOf('[')+
1, pfxlen
-1);
123 if (f
[pfx+
'[pay]'].value || f
[pfx+
'[adj]'].value
) {
124 if (! f
[pfx+
'[date]'].value
) {
125 alert('<?php xl('Date is missing
for code
', 'e
')?>' + code
);
129 if (f
[pfx+
'[pay]'].value
&& isNaN(parseFloat(f
[pfx+
'[pay]'].value
))) {
130 alert('<?php xl('Payment value
for code
', 'e
') ?>' + code +
'<?php xl(' is not a number
', 'e
') ?>');
133 if (f
[pfx+
'[adj]'].value
&& isNaN(parseFloat(f
[pfx+
'[adj]'].value
))) {
134 alert('<?php xl('Adjustment value
for code
', 'e
') ?>' + code +
'<?php xl(' is not a number
', 'e
') ?>');
137 if (f
[pfx+
'[adj]'].value
&& ! f
[pfx+
'[reason]'].value
) {
138 alert('<?php xl('Please select an adjustment reason
for code
', 'e
') ?>' + code
);
141 // TBD: validate the date format
143 // Demand confirmation if deleting anything.
145 if (!confirm('<?php echo xl('Really delete
'); ?> ' + delcount +
146 ' <?php echo xl('transactions
'); ?>?' +
147 ' <?php echo xl('This action will be logged
'); ?>!')
153 <!-- Get current date
-->
155 function getFormattedToday()
157 var today
= new Date();
158 var dd
= today
.getDate();
159 var mm
= today
.getMonth()+
1; //January is 0!
160 var yyyy
= today
.getFullYear();
164 return (yyyy +
'-' + mm +
'-' + dd
);
167 <!-- Update Payment Fields
-->
169 function updateFields(payField
, adjField
, balField
, coPayField
, isFirstProcCode
)
174 var coPayAmount
= 0.0;
176 // coPayFiled will be null if there is no co-pay entry in the fee sheet
178 coPayAmount
= coPayField
.value
;
180 // if balance field is 0.00, its value comes back as null, so check for nul-ness first
182 balAmount
= (balField
.value
) ? balField
.value
: 0;
184 payAmount
= (payField
.value
) ? payField
.value
: 0;
186 //alert('balance = >' + balAmount +'< payAmount = ' + payAmount + ' copay = ' + coPayAmount + ' isFirstProcCode = ' + isFirstProcCode);
188 // subtract the co-pay only from the first procedure code
189 if (isFirstProcCode
== 1)
190 balAmount
= parseFloat(balAmount
) +
parseFloat(coPayAmount
);
192 adjAmount
= balAmount
- payAmount
;
194 // Assign rounded adjustment value back to TextField
195 adjField
.value
= adjAmount
= Math
.round(adjAmount
*100)/100;
200 <body leftmargin
='0' topmargin
='0' marginwidth
='0' marginheight
='0'>
202 $trans_id = 0 +
$_GET['id'];
204 die(xl("You cannot access this page directly."));
207 // A/R case, $trans_id matches form_encounter.id.
208 $ferow = sqlQuery("SELECT e.*, p.fname, p.mname, p.lname " .
209 "FROM form_encounter AS e, patient_data AS p WHERE " .
210 "e.id = '$trans_id' AND p.pid = e.pid");
212 die("There is no encounter with form_encounter.id = '$trans_id'.");
215 $patient_id = 0 +
$ferow['pid'];
216 $encounter_id = 0 +
$ferow['encounter'];
217 $svcdate = substr($ferow['date'], 0, 10);
218 $form_payer_id = 0 +
$_POST['form_payer_id'];
219 $form_reference = $_POST['form_reference'];
220 $form_check_date = fixDate($_POST['form_check_date'], date('Y-m-d'));
221 $form_deposit_date = fixDate($_POST['form_deposit_date'], $form_check_date);
222 $form_pay_total = 0 +
$_POST['form_pay_total'];
225 if (preg_match('/^Ins(\d)/i', $_POST['form_insurance'], $matches)) {
226 $payer_type = $matches[1];
229 if ($_POST['form_save'] ||
$_POST['form_cancel']) {
230 if ($_POST['form_save']) {
232 echo xl("This module is in test mode. The database will not be changed.", '', '<p><b>', "</b><p>\n");
235 $session_id = arGetSession(
242 // The sl_eob_search page needs its invoice links modified to invoke
243 // javascript to load form parms for all the above and submit.
244 // At the same time that page would be modified to work off the
245 // openemr database exclusively.
246 // And back to the sl_eob_invoice page, I think we may want to move
247 // the source input fields from row level to header level.
249 // Handle deletes. row_delete() is borrowed from deleter.php.
250 if ($ALLOW_DELETE && !$debug) {
251 foreach ($_POST['form_del'] as $arseq => $dummy) {
252 row_delete("ar_activity", "pid = '$patient_id' AND " .
253 "encounter = '$encounter_id' AND sequence_no = '$arseq'");
258 foreach ($_POST['form_line'] as $code => $cdata) {
259 $thispay = trim($cdata['pay']);
260 $thisadj = trim($cdata['adj']);
261 $thisins = trim($cdata['ins']);
262 $thiscodetype = trim($cdata['code_type']);
263 $reason = strip_escape_custom($cdata['reason']);
265 // Get the adjustment reason type. Possible values are:
266 // 1 = Charge adjustment
273 $tmp = sqlQuery("SELECT option_value FROM list_options WHERE " .
274 "list_id = 'adjreason' AND activity = 1 AND " .
275 "option_id = '" . add_escape_custom($reason) . "'");
276 if (empty($tmp['option_value'])) {
277 // This should not happen but if it does, apply old logic.
278 if (preg_match("/To copay/", $reason)) {
280 } else if (preg_match("/To ded'ble/", $reason)) {
284 $info_msg .= xl("No adjustment reason type found for") . " \"$reason\". ";
286 $reason_type = $tmp['option_value'];
307 $paytotal +
= $thispay;
310 // Be sure to record adjustment reasons, even for zero adjustments if
311 // they happen to be comments.
312 if ($thisadj ||
($reason && $reason_type == 5)) {
313 // "To copay" and "To ded'ble" need to become a comment in a zero
314 // adjustment, formatted just like sl_eob_process.php.
315 if ($reason_type == '2') {
316 $reason = $_POST['form_insurance'] . " coins: $thisadj";
318 } else if ($reason_type == '3') {
319 $reason = $_POST['form_insurance'] . " dedbl: $thisadj";
321 } else if ($reason_type == '4') {
322 $reason = $_POST['form_insurance'] . " ptresp: $thisadj $reason";
324 } else if ($reason_type == '5') {
325 $reason = $_POST['form_insurance'] . " note: $thisadj $reason";
328 // An adjustment reason including "Ins" is assumed to be assigned by
329 // insurance, and in that case we identify which one by appending
330 // Ins1, Ins2 or Ins3.
331 if (strpos(strtolower($reason), 'ins') !== false) {
332 $reason .= ' ' . $_POST['form_insurance'];
351 // Maintain which insurances are marked as finished.
353 $form_done = 0 +
$_POST['form_done'];
354 $form_stmt_count = 0 +
$_POST['form_stmt_count'];
355 sqlStatement("UPDATE form_encounter " .
356 "SET last_level_closed = $form_done, " .
357 "stmt_count = $form_stmt_count WHERE " .
358 "pid = '$patient_id' AND encounter = '$encounter_id'");
360 if ($_POST['form_secondary']) {
361 arSetupSecondary($patient_id, $encounter_id, $debug);
364 echo "<script language='JavaScript'>\n";
365 echo " if (opener.document.forms[0] !== undefined) {\n";
366 echo " if (opener.document.forms[0].form_amount) {\n";
367 echo " var tmp = opener.document.forms[0].form_amount.value - $paytotal;\n";
368 echo " opener.document.forms[0].form_amount.value = Number(tmp).toFixed(2);\n";
372 echo "<script language='JavaScript'>\n";
376 echo " alert('" . addslashes($info_msg) . "');\n";
380 echo " window.close();\n";
383 echo "</script></body></html>\n";
387 // Get invoice charge details.
388 $codes = ar_get_invoice_summary($patient_id, $encounter_id, true);
390 $pdrow = sqlQuery("select billing_note " .
391 "from patient_data where pid = '$patient_id' limit 1");
395 <form method
='post' action
='sl_eob_invoice.php?id=<?php echo $trans_id ?>'
396 onsubmit
='return validate(this)'>
398 <table border
='0' cellpadding
='3'>
401 <?php
xl('Patient:', 'e')?
>
405 echo $ferow['fname'] . ' ' . $ferow['mname'] . ' ' . $ferow['lname'];
408 <td colspan
="2" rowspan
="3">
410 for ($i = 1; $i <= 3; ++
$i) {
411 $payerid = arGetPayerID($patient_id, $svcdate, $i);
413 $tmp = sqlQuery("SELECT name FROM insurance_companies WHERE id = $payerid");
414 echo "Ins$i: " . $tmp['name'] . "<br />";
420 echo "<td rowspan='3' valign='bottom'>\n";
421 echo xl('Statements Sent:');
423 echo "<td rowspan='3' valign='bottom'>\n";
424 echo "<input type='text' name='form_stmt_count' size='10' value='" .
425 (0 +
$ferow['stmt_count']) . "' />\n";
431 <?php
xl('Provider:', 'e')?
>
435 $tmp = sqlQuery("SELECT fname, mname, lname " .
436 "FROM users WHERE id = " . $ferow['provider_id']);
437 echo text($tmp['fname']) . ' ' . text($tmp['mname']) . ' ' . text($tmp['lname']);
438 $tmp = sqlQuery("SELECT bill_date FROM billing WHERE " .
439 "pid = '$patient_id' AND encounter = '$encounter_id' AND " .
440 "activity = 1 ORDER BY fee DESC, id ASC LIMIT 1");
441 $billdate = substr(($tmp['bill_date'] . "Not Billed"), 0, 10);
447 <?php
xl('Invoice:', 'e')?
>
451 echo "$patient_id.$encounter_id";
458 <?php
xl('Svc Date:', 'e'); ?
>
466 <?php
xl('Done with:', 'e', '', " ")?
>;
468 // Write a checkbox for each insurance. It is to be checked when
469 // we no longer expect any payments from that company for the claim.
470 $last_level_closed = 0 +
$ferow['last_level_closed'];
471 foreach (array(0 => 'None', 1 => 'Ins1', 2 => 'Ins2', 3 => 'Ins3') as $key => $value) {
472 if ($key && !arGetPayerID($patient_id, $svcdate, $key)) {
476 $checked = ($last_level_closed == $key) ?
" checked" : "";
477 echo " <input type='radio' name='form_done' value='$key'$checked />$value \n";
483 echo xl('Check/EOB No.:');
486 echo "<input type='text' name='form_reference' size='10' value='' />\n";
493 <?php
xl('Last Bill Date:', 'e') ?
>
501 <?php
xl('Now posting for:', 'e', '', " ")?
>;
504 // TBD: check the first not-done-with insurance, not always Ins1!
506 <input type
='radio' name
='form_insurance' value
='Ins1' onclick
='setins("Ins1")' checked
/><?php
xl('Ins1', 'e')?
> 
;
507 <input type
='radio' name
='form_insurance' value
='Ins2' onclick
='setins("Ins2")' /><?php
xl('Ins2', 'e')?
> 
;
508 <input type
='radio' name
='form_insurance' value
='Ins3' onclick
='setins("Ins3")' /><?php
xl('Ins3', 'e')?
> 
;
509 <input type
='radio' name
='form_insurance' value
='Pt' onclick
='setins("Pt")' /><?php
xl('Patient', 'e')?
>
512 // TBD: I think the following is unused and can be removed.
514 <input type
='hidden' name
='form_eobs' value
='<?php echo addslashes($arrow['shipvia
']) ?>' />
519 echo xl('Check/EOB Date:');
522 echo "<input type='text' name='form_check_date' size='10' value='' />\n";
532 <input type
="checkbox" name
="form_secondary" value
="1"> <?php
xl('Needs secondary billing', 'e')?
>
534 <input type
='submit' name
='form_save' value
='<?php xl('Save
', 'e
')?>'>
536 <input type
='button' value
='<?php xl('Cancel
', 'e
')?>' onclick
='window.close()'>
540 echo xl('Deposit Date:');
543 echo "<input type='text' name='form_deposit_date' size='10' value='' />\n";
544 echo "<input type='hidden' name='form_payer_id' value='' />\n";
545 echo "<input type='hidden' name='form_orig_reference' value='' />\n";
546 echo "<input type='hidden' name='form_orig_check_date' value='' />\n";
547 echo "<input type='hidden' name='form_orig_deposit_date' value='' />\n";
548 echo "<input type='hidden' name='form_pay_total' value='' />\n";
552 <?php
if (!empty($pdrow['billing_note'])) { ?
>
555 <?php
xl('Billing Note:', 'e')?
>
557 <td colspan
='3' style
='color:red'>
558 <?php
echo $pdrow['billing_note'] ?
>
568 <table border
='0' cellpadding
='2' cellspacing
='0' width
='98%'>
570 <tr bgcolor
="#cccccc">
572 <?php
xl('Code', 'e')?
>
574 <td
class="dehead" align
="right">
575 <?php
xl('Charge', 'e')?
>
577 <td
class="dehead" align
="right">
578 <?php
xl('Balance', 'e')?
> 
;
581 <?php
xl('By/Source', 'e')?
>
584 <?php
xl('Date', 'e')?
>
587 <?php
xl('Pay', 'e')?
>
590 <?php
xl('Adjust', 'e')?
>
593 <?php
xl('Reason', 'e')?
>
595 <?php
if ($ALLOW_DELETE) { ?
>
597 <?php
xl('Del', 'e')?
>
602 $firstProcCodeIndex = -1;
604 foreach ($codes as $code => $cdata) {
606 $bgcolor = "#" . (($encount & 1) ?
"ddddff" : "ffdddd");
609 // remember the index of the first entry whose code is not "CO-PAY", i.e. it's a legitimate proc code
610 if ($firstProcCodeIndex == -1 && strcmp($code, "CO-PAY") !=0) {
611 $firstProcCodeIndex = $encount;
614 // this sorts the details more or less chronologically:
615 ksort($cdata['dtl']);
616 foreach ($cdata['dtl'] as $dkey => $ddata) {
617 $ddate = substr($dkey, 0, 10);
618 if (preg_match('/^(\d\d\d\d)(\d\d)(\d\d)\s*$/', $ddate, $matches)) {
619 $ddate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
624 /*****************************************************************
625 if ($ddata['chg'] > 0)
626 $tmpchg = $ddata['chg'];
627 else if ($ddata['chg'] < 0)
628 $tmpadj = 0 - $ddata['chg'];
629 *****************************************************************/
630 if ($ddata['chg'] != 0) {
631 if (isset($ddata['rsn'])) {
632 $tmpadj = 0 - $ddata['chg'];
634 $tmpchg = $ddata['chg'];
638 <tr bgcolor
='<?php echo $bgcolor ?>'>
640 <?php
echo $dispcode; $dispcode = "" ?
>
642 <td
class="detail" align
="right">
643 <?php
bucks($tmpchg) ?
>
645 <td
class="detail" align
="right">
650 if (isset($ddata['plv'])) {
651 if (!$ddata['plv']) {
654 echo 'Ins' . $ddata['plv'] . '/';
665 <?php
bucks($ddata['pmt']) ?
>
668 <?php
bucks($tmpadj) ?
>
671 <?php
echo $ddata['rsn'] ?
>
673 <?php
if ($ALLOW_DELETE) { ?
>
675 <?php
if (!empty($ddata['arseq'])) { ?
>
676 <input type
="checkbox" name
="form_del[<?php echo $ddata['arseq']; ?>]" />
684 } // end of prior detail line
686 <tr bgcolor
='<?php echo $bgcolor ?>'>
688 <?php
echo $dispcode; $dispcode = "" ?
>
690 <td
class="detail" align
="right">
693 <td
class="detail" align
="right">
694 <input type
="hidden" name
="form_line[<?php echo $code ?>][bal]" value
="<?php bucks($cdata['bal']) ?>">
695 <input type
="hidden" name
="form_line[<?php echo $code ?>][ins]" value
="<?php echo $cdata['ins'] ?>">
696 <input type
="hidden" name
="form_line[<?php echo $code ?>][code_type]" value
="<?php echo $cdata['code_type'] ?>">
697 <?php
printf("%.2f", $cdata['bal']) ?
> 
;
708 <input type
="text" name
="form_line[<?php echo $code ?>][pay]" size
="10"
709 style
="background-color:<?php echo $bgcolor ?>"
710 onKeyUp
="updateFields(document.forms[0]['form_line[<?php echo $code ?>][pay]'],
711 document.forms[0]['form_line[<?php echo $code ?>][adj]'],
712 document.forms[0]['form_line[<?php echo $code ?>][bal]'],
713 document.forms[0]['form_line[CO-PAY][bal]'],
714 <?php echo ($firstProcCodeIndex == $encount) ? 1 : 0 ?>)"/>
717 <input type
="text" name
="form_line[<?php echo $code ?>][adj]" size
="10"
718 value
='<?php echo $totalAdjAmount ?>'
719 style
="background-color:<?php echo $bgcolor ?>" />
720  
; <a href
="" onclick
="return writeoff('<?php echo $code ?>')">W
</a
>
723 <select name
="form_line[<?php echo $code ?>][reason]"
724 style
="background-color:<?php echo $bgcolor ?>">
726 // Adjustment reasons are now taken from the list_options table.
727 echo " <option value=''></option>\n";
728 $ores = sqlStatement("SELECT option_id, title, is_default FROM list_options " .
729 "WHERE list_id = 'adjreason' AND activity = 1 ORDER BY seq, title");
730 while ($orow = sqlFetchArray($ores)) {
731 echo " <option value='" . htmlspecialchars($orow['option_id'], ENT_QUOTES
) . "'";
732 if ($orow['is_default']) {
736 echo ">" . htmlspecialchars($orow['title']) . "</option>\n";
742 // TBD: Maybe a comment field would be good here, for appending
747 <?php
if ($ALLOW_DELETE) { ?
>
761 <script language
="JavaScript">
762 var f1
= opener
.document
.forms
[0];
763 var f2
= document
.forms
[0];
764 if (f1
.form_source
) {
766 // These support creation and lookup of ar_session table entries:
767 echo " f2.form_reference.value = f1.form_source.value;\n";
768 echo " f2.form_check_date.value = f1.form_paydate.value;\n";
769 echo " //f2.form_deposit_date.value = f1.form_deposit_date.value;\n";
770 echo " if (f1.form_deposit_date.value != '')\n";
771 echo " f2.form_deposit_date.value = f1.form_deposit_date.value;\n";
773 echo " f2.form_deposit_date.value = getFormattedToday();\n";
774 echo " f2.form_payer_id.value = f1.form_payer_id.value;\n";
775 echo " f2.form_pay_total.value = f1.form_amount.value;\n";
776 echo " f2.form_orig_reference.value = f1.form_source.value;\n";
777 echo " f2.form_orig_check_date.value = f1.form_paydate.value;\n";
778 echo " f2.form_orig_deposit_date.value = f1.form_deposit_date.value;\n";
780 // While I'm thinking about it, some notes about eob sessions.
781 // If they do not have all of the session key fields in the search
782 // page, then show a warning at the top of the invoice page.
783 // Also when they go to save the invoice page and a session key
784 // field has changed, alert them to that and allow a cancel.
786 // Another point... when posting EOBs, the incoming payer ID might
787 // not match the payer ID for the patient's insurance. This is
788 // because the same payer might be entered more than once into the
789 // insurance_companies table. I don't think it matters much.