Updated to document new features, particularly multibox buttons.
[openemr.git] / interface / billing / sl_eob_invoice.php
blob0a14000afd58ef63c06d9c26923aee91d631b890
1 <?php
2 // Copyright (C) 2005-2006 Rod Roark <rod@sunsetsystems.com>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
9 // This provides for manual posting of EOBs. It is invoked from
10 // sl_eob_search.php. For automated (X12 835) remittance posting
11 // see sl_eob_process.php.
13 include_once("../globals.php");
14 include_once("../../library/patient.inc");
15 include_once("../../library/forms.inc");
16 include_once("../../library/sl_eob.inc.php");
17 include_once("../../library/invoice_summary.inc.php");
18 include_once("../../custom/code_types.inc.php");
20 $debug = 0; // set to 1 for debugging mode
22 $reasons = array(
23 "", // not choosing this allows a reason with no adjustment amount
24 xl("Ins adjust"),
25 xl("Coll w/o"),
26 xl("Pt released"),
27 xl("Sm debt w/o"),
28 xl("To ded'ble"),
29 xl("To copay"),
30 xl("Bad check"),
31 xl("Bad debt"),
32 xl("Discount"),
33 xl("Hardship w/o"),
34 xl("Ins refund"),
35 xl("Pt refund"),
36 xl("Ins overpaid"),
37 xl("Pt overpaid"),
38 xl("Adm adjust")
41 $info_msg = "";
43 // Format money for display.
45 function bucks($amount) {
46 if ($amount)
47 printf("%.2f", $amount);
50 <html>
51 <head>
52 <link rel=stylesheet href="<?php echo $css_header;?>" type="text/css">
53 <title><?php xl('EOB Posting - Invoice','e')?></title>
54 <script language="JavaScript">
56 // An insurance radio button is selected.
57 function setins(istr) {
58 var f = document.forms[0];
59 for (var i = 0; i < f.elements.length; ++i) {
60 var ename = f.elements[i].name;
61 if (ename.indexOf('[src]') < 0) continue;
62 var evalue = f.elements[i].value;
63 var tmp = evalue.substring(0, 4).toLowerCase();
64 if (tmp >= 'ins1' && tmp <= 'ins3')
65 evalue = evalue.substring(4);
66 else if (evalue.substring(0, 2).toLowerCase() == 'pt')
67 evalue = evalue.substring(2);
68 while (evalue.substring(0, 1) == '/')
69 evalue = evalue.substring(1);
70 f.elements[i].value = istr + '/' + evalue;
72 return true;
75 // Compute an adjustment that writes off the balance:
76 function writeoff(code) {
77 var f = document.forms[0];
78 var belement = f['form_line[' + code + '][bal]'];
79 var pelement = f['form_line[' + code + '][pay]'];
80 var aelement = f['form_line[' + code + '][adj]'];
81 var relement = f['form_line[' + code + '][reason]'];
82 var tmp = belement.value - pelement.value;
83 aelement.value = Number(tmp).toFixed(2);
84 if (aelement.value && ! relement.value) relement.selectedIndex = 1;
85 return false;
88 // Onsubmit handler. A good excuse to write some JavaScript.
89 function validate(f) {
90 for (var i = 0; i < f.elements.length; ++i) {
91 var ename = f.elements[i].name;
92 var pfxlen = ename.indexOf('[pay]');
93 if (pfxlen < 0) continue;
94 var pfx = ename.substring(0, pfxlen);
95 var code = pfx.substring(pfx.indexOf('[')+1, pfxlen-1);
96 if (f[pfx+'[pay]'].value || f[pfx+'[adj]'].value) {
97 var srcobj = f[pfx+'[src]'];
98 while (srcobj.value.length) {
99 var tmp = srcobj.value.substring(srcobj.value.length - 1);
100 if (tmp > ' ' && tmp != '/') break;
101 srcobj.value = srcobj.value.substring(0, srcobj.value.length - 1);
103 var svalue = srcobj.value;
104 if (! svalue) {
105 alert('<?php xl('Source is missing for code ','e') ?>' + code);
106 return false;
107 } else {
108 var tmp = svalue.substring(0, 4).toLowerCase();
109 if (tmp >= 'ins1' && tmp <= 'ins3') {
110 svalue = svalue.substring(4);
111 } else if (svalue.substring(0, 2).toLowerCase() == 'pt') {
112 svalue = svalue.substring(2);
113 } else {
114 alert('<?php xl('Invalid or missing payer in source for code ','e')?>' + code);
115 return false;
117 if (svalue) {
118 if (svalue.substring(0, 1) != '/') {
119 alert('<?php xl('Missing slash after payer in source for code ','e')?>' + code);
120 return false;
122 if (false) { // Please keep this, Oakland Clinic wants it. -- Rod
123 tmp = svalue.substring(1, 3).toLowerCase();
124 if (tmp != 'nm' && tmp != 'ci' && tmp != 'cp' && tmp != 'ne' &&
125 tmp != 'it' && tmp != 'pf' && tmp != 'pp' && tmp != 'ok')
127 alert('<?php xl('Invalid source designation "','e') ?>' + tmp + '<?php xl('" for code ','e') ?>' + code);
128 return false;
130 } // End of OC code
133 if (! f[pfx+'[date]'].value) {
134 alert('<?php xl('Date is missing for code ','e')?>' + code);
135 return false;
138 if (f[pfx+'[pay]'].value && isNaN(parseFloat(f[pfx+'[pay]'].value))) {
139 alert('<?php xl('Payment value for code ','e') ?>' + code + '<?php xl(' is not a number','e') ?>');
140 return false;
142 if (f[pfx+'[adj]'].value && isNaN(parseFloat(f[pfx+'[adj]'].value))) {
143 alert('<?php xl('Adjustment value for code ','e') ?>' + code + '<?php xl(' is not a number','e') ?>');
144 return false;
146 if (f[pfx+'[adj]'].value && ! f[pfx+'[reason]'].value) {
147 alert('<?php xl('Please select an adjustment reason for code ','e') ?>' + code);
148 return false;
150 // TBD: validate the date format
152 return true;
155 </script>
156 </head>
157 <body leftmargin='0' topmargin='0' marginwidth='0' marginheight='0'>
158 <?php
159 $trans_id = $_GET['id'];
160 if (! $trans_id) die(xl("You cannot access this page directly."));
162 slInitialize();
164 if ($_POST['form_save'] || $_POST['form_cancel']) {
165 if ($_POST['form_save']) {
166 if ($debug) {
167 echo xl("This module is in test mode. The database will not be changed.",'','<p><b>',"</b><p>\n");
169 $paytotal = 0;
170 foreach ($_POST['form_line'] as $code => $cdata) {
171 $thissrc = trim($cdata['src']);
172 $thisdate = trim($cdata['date']);
173 $thispay = trim($cdata['pay']);
174 $thisadj = trim($cdata['adj']);
175 $thisins = trim($cdata['ins']);
176 $reason = trim($cdata['reason']);
177 if (strpos(strtolower($reason), 'ins') !== false)
178 $reason .= ' ' . $_POST['form_insurance'];
179 if (! $thisins) $thisins = 0;
181 if ($thispay) {
182 slPostPayment($trans_id, $thispay, $thisdate, $thissrc, $code, $thisins, $debug);
183 $paytotal += $thispay;
186 // Be sure to record adjustment reasons even for zero adjustments.
187 if ($thisadj || $reason) {
188 // "To copay" and "To ded'ble" need to become a comment in a zero
189 // adjustment, formatted just like sl_eob_process.php.
190 if (preg_match("/To copay/", $reason)) {
191 $reason = $_POST['form_insurance'] . " coins: $thisadj";
192 $thisadj = 0;
194 else if (preg_match("/To ded'ble/", $reason)) {
195 $reason = $_POST['form_insurance'] . " dedbl: $thisadj";
196 $thisadj = 0;
198 slPostAdjustment($trans_id, $thisadj, $thisdate, $thissrc, $code, $thisins, $reason, $debug);
202 $form_duedate = fixDate($_POST['form_duedate']);
203 $form_notes = trim($_POST['form_notes']);
205 // Maintain the list of insurances that we mark as finished.
206 // We use the "Ship Via" field of the invoice to hold these.
208 $form_eobs = "";
209 foreach (array('Ins1', 'Ins2', 'Ins3') as $value) {
210 if ($_POST["form_done_$value"]) {
211 if ($form_eobs) $form_eobs .= ","; else $form_eobs = "Done: ";
212 $form_eobs .= $value;
216 $query = "UPDATE ar SET duedate = '$form_duedate', notes = '$form_notes', " .
217 "shipvia = '$form_eobs' WHERE id = $trans_id";
219 if ($debug) {
220 echo $query . "<br>\n";
221 } else {
222 SLQuery($query);
223 if ($sl_err) die($sl_err);
225 if ($_POST['form_secondary']) {
226 slSetupSecondary($trans_id, $debug);
228 echo "<script language='JavaScript'>\n";
229 echo " if (opener.document.forms[0].form_amount) {\n";
230 echo " var tmp = opener.document.forms[0].form_amount.value - $paytotal;\n";
231 echo " opener.document.forms[0].form_amount.value = Number(tmp).toFixed(2);\n";
232 echo " }\n";
233 } else {
234 echo "<script language='JavaScript'>\n";
236 if ($info_msg) echo " alert('$info_msg');\n";
237 if (! $debug) echo " window.close();\n";
238 echo "</script></body></html>\n";
239 SLClose();
240 exit();
243 // Get invoice data into $arrow.
244 $arres = SLQuery("select ar.*, customer.name, employee.name as doctor " .
245 "from ar, customer, employee where ar.id = $trans_id and " .
246 "customer.id = ar.customer_id and employee.id = ar.employee_id");
247 if ($sl_err) die($sl_err);
248 $arrow = SLGetRow($arres, 0);
249 if (! $arrow) die(xl("There is no match for invoice id = ") . $trans_id);
251 // Determine the date of service. An 8-digit encounter number is
252 // presumed to be a date of service imported during conversion.
253 // Otherwise look it up in the form_encounter table.
255 $svcdate = "";
256 list($trash, $encounter) = explode(".", $arrow['invnumber']);
257 if (strlen($encounter) == 8) {
258 $svcdate = substr($encounter, 0, 4) . "-" . substr($encounter, 4, 2) .
259 "-" . substr($encounter, 6, 2);
261 else if ($encounter) {
262 $tmp = sqlQuery("SELECT date FROM form_encounter WHERE " .
263 "encounter = $encounter");
264 $svcdate = substr($tmp['date'], 0, 10);
267 // Get invoice charge details.
268 $codes = get_invoice_summary($trans_id, true);
270 <center>
272 <form method='post' action='sl_eob_invoice.php?id=<?php echo $trans_id ?>'
273 onsubmit='return validate(this)'>
275 <table border='0' cellpadding='3'>
276 <tr>
277 <td>
278 <?php xl('Patient:','e')?>
279 </td>
280 <td>
281 <?php echo $arrow['name'] ?>
282 </td>
283 <td colspan="2" rowspan="3">
284 <textarea name="form_notes" cols="50" style="height:100%"><?php echo $arrow['notes'] ?></textarea>
285 </td>
286 </tr>
287 <tr>
288 <td>
289 <?php xl('Provider:','e')?>
290 </td>
291 <td>
292 <?php echo $arrow['doctor'] ?>
293 </td>
294 </tr>
295 <tr>
296 <td>
297 <?php xl('Invoice:','e')?>
298 </td>
299 <td>
300 <?php echo $arrow['invnumber'] ?>
301 </td>
302 </tr>
304 <tr>
305 <td>
306 <?php xl('Svc Date:','e')?>
307 </td>
308 <td>
309 <?php echo $svcdate ?>
310 </td>
311 <td colspan="2">
312 <!-- <?php echo $arrow['shipvia'] ?> -->
313 <?php xl('Done with:','e','',"&nbsp")?>;
314 <?php
315 // Write a checkbox for each insurance. It is to be checked when
316 // we no longer expect any payments from that company for the claim.
317 // The information is stored in the 'shipvia' field of the invoice.
319 $insgot = strtolower($arrow['notes']);
320 $insdone = strtolower($arrow['shipvia']);
321 foreach (array('Ins1', 'Ins2', 'Ins3') as $value) {
322 $lcvalue = strtolower($value);
323 $checked = (strpos($insdone, $lcvalue) === false) ? "" : " checked";
324 if (strpos($insgot, $lcvalue) !== false) {
325 echo " <input type='checkbox' name='form_done_$value' value='1'$checked />$value&nbsp;\n";
329 </td>
330 </tr>
332 <tr>
333 <td>
334 <?php xl('Bill Date:','e') ?>
335 </td>
336 <td>
337 <?php echo $arrow['transdate'] ?>
338 </td>
339 <td colspan="2">
340 <?php xl('Now posting for:','e','',"&nbsp")?>;
341 <input type='radio' name='form_insurance' value='Ins1' onclick='setins("Ins1")' checked /><?php xl('Ins1','e')?>&nbsp;
342 <input type='radio' name='form_insurance' value='Ins2' onclick='setins("Ins2")' /><?php xl('Ins2','e')?>&nbsp;
343 <input type='radio' name='form_insurance' value='Ins3' onclick='setins("Ins3")' /><?php xl('Ins3','e')?>&nbsp;
344 <input type='radio' name='form_insurance' value='Pt' onclick='setins("Pt")' /><?php xl('Patient','e')?>
345 <input type='hidden' name='form_eobs' value='<?php echo addslashes($arrow['shipvia']) ?>' />
346 </td>
347 </tr>
348 <tr>
349 <td>
350 <?php xl('Due Date:','e')?>
351 </td>
352 <td>
353 <input type='text' name='form_duedate' size='10' value='<?php echo $arrow['duedate'] ?>'
354 title='<?php xl('Due date mm/dd/yyyy or yyyy-mm-dd','e')?>'>
355 </td>
356 <td colspan="2">
357 <input type="checkbox" name="form_secondary" value="1"> <?php xl('Needs secondary billing','e')?>
358 &nbsp;&nbsp;
359 <input type='submit' name='form_save' value='<?php xl('Save','e')?>'>
360 &nbsp;
361 <input type='button' value='<?php xl('Cancel','e')?>' onclick='window.close()'>
362 </td>
363 </tr>
364 <tr>
365 <td height="1">
366 </td>
367 </tr>
368 </table>
370 <table border='0' cellpadding='2' cellspacing='0' width='98%'>
372 <tr bgcolor="#cccccc">
373 <td class="dehead">
374 <?php xl('Code','e')?>
375 </td>
376 <td class="dehead" align="right">
377 <?php xl('Charge','e')?>
378 </td>
379 <td class="dehead" align="right">
380 <?php xl('Balance','e')?>&nbsp;
381 </td>
382 <td class="dehead">
383 <?php xl('Source','e')?>
384 </td>
385 <td class="dehead">
386 <?php xl('Date','e')?>
387 </td>
388 <td class="dehead">
389 <?php xl('Pay','e')?>
390 </td>
391 <td class="dehead">
392 <?php xl('Adjust','e')?>
393 </td>
394 <td class="dehead">
395 <?php xl('Reason','e')?>
396 </td>
397 </tr>
398 <?php
399 $encount = 0;
400 foreach ($codes as $code => $cdata) {
401 ++$encount;
402 $bgcolor = "#" . (($encount & 1) ? "ddddff" : "ffdddd");
403 $dispcode = $code;
404 // this sorts the details more or less chronologically:
405 ksort($cdata['dtl']);
406 foreach ($cdata['dtl'] as $dkey => $ddata) {
407 $ddate = substr($dkey, 0, 10);
408 if (preg_match('/^(\d\d\d\d)(\d\d)(\d\d)\s*$/', $ddate, $matches)) {
409 $ddate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
411 $tmpchg = "";
412 $tmpadj = "";
413 if ($ddata['chg'] > 0)
414 $tmpchg = $ddata['chg'];
415 else if ($ddata['chg'] < 0)
416 $tmpadj = 0 - $ddata['chg'];
418 <tr bgcolor='<?php echo $bgcolor ?>'>
419 <td class="detail">
420 <?php echo $dispcode; $dispcode = "" ?>
421 </td>
422 <td class="detail" align="right">
423 <?php bucks($tmpchg) ?>
424 </td>
425 <td class="detail" align="right">
426 &nbsp;
427 </td>
428 <td class="detail">
429 <?php echo $ddata['src'] ?>
430 </td>
431 <td class="detail">
432 <?php echo $ddate ?>
433 </td>
434 <td class="detail">
435 <?php bucks($ddata['pmt']) ?>
436 </td>
437 <td class="detail">
438 <?php bucks($tmpadj) ?>
439 </td>
440 <td class="detail">
441 <?php echo $ddata['rsn'] ?>
442 </td>
443 </tr>
444 <?php
445 } // end of prior detail line
447 <tr bgcolor='<?php echo $bgcolor ?>'>
448 <td class="detail">
449 <?php echo $dispcode; $dispcode = "" ?>
450 </td>
451 <td class="detail" align="right">
452 &nbsp;
453 </td>
454 <td class="detail" align="right">
455 <input type="hidden" name="form_line[<?php echo $code ?>][bal]" value="<?php bucks($cdata['bal']) ?>">
456 <input type="hidden" name="form_line[<?php echo $code ?>][ins]" value="<?php echo $cdata['ins'] ?>">
457 <?php printf("%.2f", $cdata['bal']) ?>&nbsp;
458 </td>
459 <td class="detail">
460 <input type="text" name="form_line[<?php echo $code ?>][src]" size="10"
461 style="background-color:<?php echo $bgcolor ?>"
462 <?php if (false) { ?>
463 title="NM=notmet, CI=coins, CP=copay, NE=notelig, IT=insterm, PF=ptfull, PP=ptpart"
464 <?php } ?>
466 </td>
467 <td class="detail">
468 <input type="text" name="form_line[<?php echo $code ?>][date]" size="10"
469 style="background-color:<?php echo $bgcolor ?>" />
470 </td>
471 <td class="detail">
472 <input type="text" name="form_line[<?php echo $code ?>][pay]" size="10"
473 style="background-color:<?php echo $bgcolor ?>" />
474 </td>
475 <td class="detail">
476 <input type="text" name="form_line[<?php echo $code ?>][adj]" size="10"
477 style="background-color:<?php echo $bgcolor ?>" />
478 &nbsp; <a href="" onclick="return writeoff('<?php echo $code ?>')">W</a>
479 </td>
480 <td class="detail">
481 <select name="form_line[<?php echo $code ?>][reason]"
482 style="background-color:<?php echo $bgcolor ?>">
483 <?php
484 foreach ($reasons as $value) {
485 echo " <option value=\"$value\">$value</option>\n";
488 </select>
489 </td>
490 </tr>
491 <?php
492 } // end of code
493 SLClose();
496 </table>
497 </form>
498 </center>
499 <script language="JavaScript">
500 var f1 = opener.document.forms[0];
501 var f2 = document.forms[0];
502 if (f1.form_source) {
503 <?php
504 foreach ($codes as $code => $cdata) {
505 echo " f2['form_line[$code][src]'].value = f1.form_source.value;\n";
506 echo " f2['form_line[$code][date]'].value = f1.form_paydate.value;\n";
510 setins("Ins1");
511 </script>
512 </body>
513 </html>