Update API.php (#7723)
[openemr.git] / interface / billing / sl_eob_process.php
blobf3bb63377a7eb3e744eb581aec03d9348b56e6c3
1 <?php
3 /**
4 * This processes X12 835 remittances and produces a report.
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 Stephen Waite <stephen.waite@cmsvt.com>
11 * @copyright Copyright (c) 2006-2020 Rod Roark <rod@sunsetsystems.com>
12 * @copyright Copyright (c) 2018 Brady Miller <brady.g.miller@gmail.com>
13 * @copyright Copyright (c) 2019-2020 Stephen Waite <stephen.waite@cmsvt.com>
14 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
17 // Buffer all output so we can archive it to a file.
18 ob_start();
20 require_once("../globals.php");
22 use OpenEMR\Billing\BillingUtilities;
23 use OpenEMR\Billing\InvoiceSummary;
24 use OpenEMR\Billing\ParseERA;
25 use OpenEMR\Billing\SLEOB;
26 use OpenEMR\Common\Csrf\CsrfUtils;
27 use OpenEMR\Core\Header;
28 use OpenEMR\Services\InsuranceService;
30 $debug = $_GET['debug'] ? 1 : 0; // set to 1 for debugging mode
31 $paydate = parse_date($_GET['paydate']);
32 $encount = 0;
34 $last_ptname = '';
35 $last_invnumber = '';
36 $last_code = '';
37 $invoice_total = 0.00;
38 $InsertionId; // last inserted ID of
40 ///////////////////////// Assorted Functions /////////////////////////
42 function parse_date($date)
44 $date = substr(trim($date), 0, 10);
45 if (preg_match('/^(\d\d\d\d)\D*(\d\d)\D*(\d\d)$/', $date, $matches)) {
46 return $matches[1] . '-' . $matches[2] . '-' . $matches[3];
49 return '';
52 function writeMessageLine($bgcolor, $class, $description, $nl2br_process = "false")
54 $dline =
55 " <tr bgcolor='" . attr($bgcolor) . "'>\n" .
56 " <td class='" . attr($class) . "' colspan='4'></td>\n";
57 if ($nl2br_process) {
58 $dline .= " <td class='" . attr($class) . "'>" . nl2br(text($description)) . "</td>\n";
59 } else {
60 $dline .= " <td class='" . attr($class) . "'>" . text($description) . "</td>\n";
62 $dline .=
63 " <td class='" . attr($class) . "' colspan='2'></td>\n" .
64 " </tr>\n";
65 echo $dline;
68 function writeDetailLine(
69 $bgcolor,
70 $class,
71 $ptname,
72 $invnumber,
73 $code,
74 $date,
75 $description,
76 $amount,
77 $balance
78 ) {
80 global $last_ptname, $last_invnumber, $last_code;
81 if ($ptname == $last_ptname) {
82 $ptname = '';
83 } else {
84 $last_ptname = $ptname;
87 if ($invnumber == $last_invnumber) {
88 $invnumber = '';
89 } else {
90 $last_invnumber = $invnumber;
93 if ($code == $last_code) {
94 $code = '';
95 } else {
96 $last_code = $code;
99 if ($amount) {
100 $amount = sprintf("%.2f", $amount);
103 if ($balance) {
104 $balance = sprintf("%.2f", $balance);
107 $dline =
108 " <tr bgcolor='" . attr($bgcolor) . "'>\n" .
109 " <td class='" . attr($class) . "'>" . (($ptname == '&nbsp;') ? '' : text($ptname)) . "</td>\n" .
110 " <td class='" . attr($class) . "'>" . (($invnumber == '&nbsp;') ? '' : text($invnumber)) . "</td>\n" .
111 " <td class='" . attr($class) . "'>" . (($code == '&nbsp;') ? '' : text($code)) . "</td>\n" .
112 " <td class='" . attr($class) . "'>" . text(oeFormatShortDate($date)) . "</td>\n" .
113 " <td class='" . attr($class) . "'>" . text($description) . "</td>\n" .
114 " <td class='" . attr($class) . "' align='right'>" . text(oeFormatMoney($amount)) . "</td>\n" .
115 " <td class='" . attr($class) . "' align='right'>" . text(oeFormatMoney($balance)) . "</td>\n" .
116 " </tr>\n";
117 echo $dline;
120 // This writes detail lines that were already in SQL-Ledger for a given
121 // charge item.
123 function writeOldDetail(&$prev, $ptname, $invnumber, $dos, $code, $bgcolor)
125 global $invoice_total;
126 // $prev['total'] = 0.00; // to accumulate total charges
127 ksort($prev['dtl']);
128 foreach ($prev['dtl'] as $dkey => $ddata) {
129 $ddate = substr($dkey, 0, 10);
130 $description = ($ddata['src'] ?? '') . ($ddata['rsn'] ?? '');
131 if ($ddate == ' ') { // this is the service item
132 $ddate = $dos;
133 $description = 'Service Item';
136 $amount = sprintf("%.2f", (floatval($ddata['chg'] ?? '')) - (floatval($ddata['pmt'] ?? '')));
137 $invoice_total = sprintf("%.2f", $invoice_total + $amount);
138 writeDetailLine(
139 $bgcolor,
140 'olddetail',
141 $ptname,
142 $invnumber,
143 $code,
144 $ddate,
145 $description,
146 $amount,
147 $invoice_total
152 // This is called back by ParseERA::parseERA() once per claim.
155 // TODO: Sort colors here for Bootstrap themes
156 function era_callback_check(&$out)
158 // last inserted ID of ar_session table
159 global $InsertionId;
160 global $StringToEcho,$debug;
162 if (!empty($_GET['original']) && $_GET['original'] == 'original') {
163 $StringToEcho .= "<table class='table'>";
164 $StringToEcho .= "<thead>";
165 $StringToEcho .= "<tr>";
166 $StringToEcho .= "<th scope='col'>" . xlt('Check Number') . "</th>";
167 $StringToEcho .= "<th scope='col'>" . xlt('Payee Name') . "</th>";
168 $StringToEcho .= "<th scope='col'>" . xlt('Payer Name') . "</th>";
169 $StringToEcho .= "<th scope='col'>" . xlt('Check Amount') . "</th>";
170 $StringToEcho .= "</tr>";
171 $StringToEcho .= "</thead>";
172 $StringToEcho .= "<tbody>";
173 $WarningFlag = false;
174 for ($check_count = 1; $check_count <= $out['check_count']; $check_count++) {
175 if ($check_count % 2 == 1) {
176 $bgcolor = '#ddddff';
177 } else {
178 $bgcolor = '#ffdddd';
181 $rs = sqlQ("select reference from ar_session where reference=?", array($out['check_number' . $check_count]));
183 if (sqlNumRows($rs) > 0) {
184 $bgcolor = '#ff0000';
185 $WarningFlag = true;
188 $StringToEcho .= "<tr bgcolor='" . attr($bgcolor) . "'>";
189 $StringToEcho .= "<th scope='row'>";
190 $StringToEcho .= "<input type='checkbox' name='chk" . attr($out['check_number' . $check_count]) . "' id='chk" . attr($out['check_number' . $check_count]) . "'/>";
191 $StringToEcho .= "<label for='chk" . attr($out['check_number' . $check_count]) . "'>";
192 $StringToEcho .= "&nbsp" . text($out['check_number' . $check_count]) . "</label>";
193 $StringToEcho .= "</th>";
194 $StringToEcho .= "<td>" . text($out['payee_name' . $check_count]) . "</td>";
195 $StringToEcho .= "<td>" . text($out['payer_name' . $check_count]) . "</td>";
196 $StringToEcho .= "<td>" . text(number_format($out['check_amount' . $check_count], 2)) . "</td>";
197 $StringToEcho .= "</tr>";
200 $StringToEcho .= "<tr class='table-light'><td align='left'><button type='button' class='btn btn-secondary btn-save' name='Submit1' onclick='checkAll(true)'>" . xlt('Check All') . "</button></td>";
201 $StringToEcho .= "<td><input type='submit' name='CheckSubmit' value='Submit'/></td>";
202 $StringToEcho .= "</tr>";
204 if ($WarningFlag == true) {
205 $StringToEcho .= "<tr class='table-danger'><td colspan='4' align='center'>" . xlt('Warning, Check Number already exist in the database') . "</td></tr>";
207 $StringToEcho .= "</tbody>";
208 $StringToEcho .= "</table>";
209 } else {
210 for ($check_count = 1; $check_count <= $out['check_count']; $check_count++) {
211 $chk_num = $out['check_number' . $check_count];
212 $chk_num = str_replace(' ', '_', $chk_num);
213 if (isset($_REQUEST['chk' . $chk_num])) {
214 $check_date = $out['check_date' . $check_count] ? $out['check_date' . $check_count] : $_REQUEST['paydate'];
215 $post_to_date = $_REQUEST['post_to_date'] != '' ? $_REQUEST['post_to_date'] : date('Y-m-d');
216 $deposit_date = $_REQUEST['deposit_date'] != '' ? $_REQUEST['deposit_date'] : date('Y-m-d');
217 $InsertionId[$out['check_number' . $check_count]] = SLEOB::arPostSession($_REQUEST['InsId'], $out['check_number' . $check_count], $out['check_date' . $check_count], $out['check_amount' . $check_count], $post_to_date, $deposit_date, $debug);
222 function era_callback(&$out)
224 global $encount, $debug;
225 global $invoice_total, $last_code, $paydate;
226 // last inserted ID of ar_session table
227 global $InsertionId;
229 // Some heading information.
230 $chk_123 = $out['check_number'];
231 $chk_123 = str_replace(' ', '_', $chk_123);
232 if (isset($_REQUEST['chk' . $chk_123])) {
233 if ($encount == 0) {
234 writeMessageLine(
235 'var(--white)',
236 'infdetail',
237 "Payer: " . $out['payer_name']
239 if ($debug) {
240 writeMessageLine(
241 'var(--white)',
242 'infdetail',
243 "WITHOUT UPDATE is selected; no changes will be applied."
248 $last_code = '';
249 $invoice_total = 0.00;
250 $bgcolor = (++$encount & 1) ? "#ddddff" : "#ffdddd";
251 list($pid, $encounter, $invnumber) = SLEOB::slInvoiceNumber($out);
253 // Get details, if we have them, for the invoice.
254 $inverror = true;
255 $codes = array();
256 if ($pid && $encounter) {
257 // Get invoice data into $arrow or $ferow.
258 $ferow = sqlQuery("SELECT e.*, p.fname, p.mname, p.lname " .
259 "FROM form_encounter AS e, patient_data AS p WHERE " .
260 "e.pid = ? AND e.encounter = ? AND " .
261 "p.pid = e.pid", array($pid, $encounter));
262 if (empty($ferow)) {
263 $pid = $encounter = 0;
264 $invnumber = $out['our_claim_id'];
265 } else {
266 $inverror = false;
267 $codes = InvoiceSummary::arGetInvoiceSummary($pid, $encounter, true);
268 // $svcdate = substr($ferow['date'], 0, 10);
272 // Show the claim status.
273 $csc = $out['claim_status_code'];
274 $inslabel = 'Ins1';
275 if ($csc == '1' || $csc == '19') {
276 $inslabel = 'Ins1';
279 if ($csc == '2' || $csc == '20') {
280 $inslabel = 'Ins2';
283 if ($csc == '3' || $csc == '21') {
284 $inslabel = 'Ins3';
287 $primary = ($inslabel == 'Ins1');
288 writeMessageLine(
289 $bgcolor,
290 'infdetail',
291 "Claim status $csc: " . BillingUtilities::CLAIM_STATUS_CODES_CLP02[$csc]
294 // Show an error message if the claim is missing or already posted.
295 if ($inverror) {
296 writeMessageLine(
297 $bgcolor,
298 'errdetail',
299 "The following claim is not in our database"
301 } else {
302 // Skip this test. Claims can get multiple CLPs from the same payer!
304 // $insdone = strtolower($arrow['shipvia']);
305 // if (strpos($insdone, 'ins1') !== false) {
306 // $inverror = true;
307 // writeMessageLine($bgcolor, 'errdetail',
308 // "Primary insurance EOB was already posted for the following claim");
309 // }
312 if ($csc == '4') {//Denial case, code is stored in the claims table for display in the billing manager screen with reason explained.
313 $inverror = true;
314 if (!$debug) {
315 if ($pid && $encounter) {
316 $code_value = '';
317 foreach ($out['svc'] as $svc) {
318 foreach ($svc['adj'] as $adj) {//Per code and modifier the reason will be showed in the billing manager.
319 $code_value .= $svc['code'] . '_' . $svc['mod'] . '_' . $adj['group_code'] . '_' . $adj['reason_code'] . ',';
323 $code_value = substr($code_value, 0, -1);
324 //We store the reason code to display it with description in the billing manager screen.
325 //process_file is used as for the denial case file name will not be there, and extra field(to store reason) can be avoided.
326 BillingUtilities::updateClaim(true, $pid, $encounter, $_REQUEST['InsId'], substr($inslabel, 3), 7, 0, $code_value);
330 writeMessageLine(
331 $bgcolor,
332 'errdetail',
333 "Not posting adjustments for denied claims, please follow up manually!"
335 } elseif ($csc == '22') {
336 $inverror = true;
337 writeMessageLine(
338 $bgcolor,
339 'errdetail',
340 "Payment reversals are not automated, please enter manually!"
344 if ($out['warnings']) {
345 writeMessageLine($bgcolor, 'infdetail', rtrim($out['warnings']), true);
348 // Simplify some claim attributes for cleaner code.
349 $service_date = parse_date(isset($out['dos']) ? $out['dos'] : $out['claim_date']);
350 $check_date = $paydate ? $paydate : parse_date($out['check_date']);
351 $production_date = $paydate ? $paydate : parse_date($out['production_date']);
353 $insurance_id = SLEOB::arGetPayerID($pid, $service_date, substr($inslabel, 3));
354 if (empty($ferow['lname'])) {
355 $patient_name = $out['patient_fname'] . ' ' . $out['patient_lname'];
356 } else {
357 $patient_name = $ferow['fname'] . ' ' . $ferow['lname'];
360 $error = $inverror;
362 // create array of cpts and mods for complex matching
363 $codes_arr_keys = array_keys($codes);
364 foreach ($codes_arr_keys as $key => $value) {
365 $tmp = explode(":", $value);
366 $count = count($tmp) - 1;
367 $cpt = $tmp[0];
368 $cpts[] = $cpt;
369 for ($i = 1; $i <= $count; $i++) {
370 $mods[$cpt][] = $tmp[$i] ?? null;
374 // This loops once for each service item in this claim.
375 foreach ($out['svc'] as $svc) {
376 // Treat a modifier in the remit data as part of the procedure key.
377 // This key will then make its way into SQL-Ledger.
378 $codekey = $svc['code'];
379 if ($svc['mod']) {
380 $codekey .= ':' . $svc['mod'];
383 $prev = $codes[$codekey] ?? '';
384 // However sometimes a secondary insurance (take USAA LIFE for instance)
385 // sometimes doesn't return the modifier that was on the service item
386 // processed by the primary payer so try to deal with that
387 if (!$prev) {
388 if (!$svc['mod']) {
389 if (in_array($svc['code'], $cpts ?? [])) {
390 foreach ($cpts as $k => $v) {
391 if ($v == $codekey) {
392 $codekey = $cpt . ':' . implode(':', $mods[$v]);
397 $prev = $codes[$codekey] ?? '';
399 $codetype = ''; //will hold code type, if exists
401 // This reports detail lines already on file for this service item.
402 if ($prev) {
403 $codetype = $codes[$codekey]['code_type'] ?? 'none'; //store code type
404 writeOldDetail($prev, $patient_name, $invnumber, $service_date, $codekey, $bgcolor);
405 // Check for sanity in amount charged.
406 $prevchg = sprintf("%.2f", $prev['chg'] + ($prev['adj'] ?? null));
407 if ($prevchg != abs($svc['chg'])) {
408 writeMessageLine(
409 $bgcolor,
410 'errdetail',
411 "EOB charge amount " . $svc['chg'] . " for this code does not match our invoice"
413 $error = true;
416 unset($codes[$codekey]);
417 } else { // If the service item is not in our database...
418 // This is not an error. If we are not in error mode and not debugging,
419 // insert the service item into billing. Then display it (in green if it
420 // was inserted, or in red if we are in error mode).
421 // Check the global to see if this is preferred to be an error.
422 if ($GLOBALS['add_unmatched_code_from_ins_co_era_to_billing'] ?? '') {
423 $description = "CPT4:$codekey Added by $inslabel $production_date";
424 } else {
425 $error = true;
426 $description = "CPT4:$codekey returned by $inslabel $production_date";
428 if (!$error && !$debug) {
429 SLEOB::arPostCharge(
430 $pid,
431 $encounter,
433 $svc['chg'],
435 $service_date,
436 $codekey,
437 $description,
438 $debug,
440 $codetype ?? ''
442 $invoice_total += $svc['chg'];
445 $class = $error ? 'errdetail' : 'newdetail';
446 writeDetailLine(
447 $bgcolor,
448 $class,
449 $patient_name,
450 $invnumber,
451 $codekey,
452 $production_date,
453 $description,
454 $svc['chg'],
455 ($error ? '' : $invoice_total)
459 $class = $error ? 'errdetail' : 'newdetail';
461 // Report Allowed Amount.
462 if ($svc['allowed'] ?? '') {
463 writeMessageLine(
464 $bgcolor,
465 'infdetail',
466 'Allowed amount is ' . sprintf("%.2f", $svc['allowed'])
470 // Report miscellaneous remarks.
471 if ($svc['remark'] ?? '') {
472 $rmk = $svc['remark'];
473 writeMessageLine($bgcolor, 'infdetail', "$rmk: " .
474 BillingUtilities::REMITTANCE_ADVICE_REMARK_CODES[$rmk]);
477 // Post and report the payment for this service item from the ERA.
478 // By the way a 'Claim' level payment is probably going to be negative,
479 // i.e. a payment reversal.
480 if ($svc['paid'] ?? '') {
481 if (!$error && !$debug) {
482 SLEOB::arPostPayment(
483 $pid,
484 $encounter,
485 $InsertionId[$out['check_number']],
486 $svc['paid'], //$InsertionId[$out['check_number']] gives the session id
487 $codekey,
488 substr($inslabel, 3),
489 $out['check_number'],
490 $debug,
492 $codetype,
493 $date ?? null,
494 $out['payer_claim_id']
496 $invoice_total -= $svc['paid'];
499 $description = "$inslabel/" . $out['check_number'] . ' payment';
500 if ($svc['paid'] < 0) {
501 $description .= ' reversal';
504 writeDetailLine(
505 $bgcolor,
506 $class,
507 $patient_name,
508 $invnumber,
509 $codekey,
510 $check_date,
511 $description,
512 0 - $svc['paid'],
513 ($error ? '' : $invoice_total)
517 // Post and report adjustments from this ERA. Posted adjustment reasons
518 // must be 25 characters or less in order to fit on patient statements.
519 foreach ($svc['adj'] as $adj) {
520 $description = ($adj['reason_code'] ?? '') . ': ' .
521 BillingUtilities::CLAIM_ADJUSTMENT_REASON_CODES[$adj['reason_code'] ?? ''];
522 if ($adj['group_code'] == 'PR' || !$primary) {
523 // Group code PR is Patient Responsibility. Enter these as zero
524 // adjustments to retain the note without crediting the claim.
525 if ($primary) {
526 /****
527 $reason = 'Pt resp: '; // Reasons should be 25 chars or less.
528 if ($adj['reason_code'] == '1') $reason = 'To deductible: ';
529 else if ($adj['reason_code'] == '2') $reason = 'Coinsurance: ';
530 else if ($adj['reason_code'] == '3') $reason = 'Co-pay: ';
531 ****/
532 $reason = "$inslabel ptresp: "; // Reasons should be 25 chars or less.
533 if ($adj['reason_code'] == '1') {
534 $reason = "$inslabel dedbl: ";
535 } elseif ($adj['reason_code'] == '2') {
536 $reason = "$inslabel coins: ";
537 } elseif ($adj['reason_code'] == '3') {
538 $reason = "$inslabel copay: ";
540 } else { // Non-primary insurance adjustments are garbage, either repeating
541 // the primary or are not adjustments at all. Report them as notes
542 // but do not post any amounts.
543 $reason = "$inslabel note " . $adj['reason_code'] . ': ';
544 /****
545 $reason .= sprintf("%.2f", $adj['amount']);
546 ****/
549 $reason .= sprintf("%.2f", $adj['amount']);
550 // Post a zero-dollar adjustment just to save it as a comment.
551 if (!$error && !$debug) {
552 SLEOB::arPostAdjustment(
553 $pid,
554 $encounter,
555 $InsertionId[$out['check_number']],
557 $codekey, //$InsertionId[$out['check_number']] gives the session id
558 substr($inslabel, 3),
559 $reason,
560 $debug,
562 $codetype,
563 $out['payer_claim_id']
567 writeMessageLine($bgcolor, $class, $description . ' ' .
568 sprintf("%.2f", $adj['amount']));
569 } elseif (
570 $svc['paid'] == 0
571 && !(
572 $adj['group_code'] == "CO"
573 && (
574 $adj['reason_code'] == '45'
575 || $adj['reason_code'] == '59'
579 $class = 'errdetail';
580 $error = true;
581 } elseif (!$error && !$debug) {
582 SLEOB::arPostAdjustment(
583 $pid,
584 $encounter,
585 $InsertionId[$out['check_number']],
586 $adj['amount'], //$InsertionId[$out['check_number']] gives the session id
587 $codekey,
588 substr($inslabel, 3),
589 "Adjust code " . $adj['reason_code'],
590 $debug,
592 $codetype ?? '',
593 $out['payer_claim_id']
595 $invoice_total -= $adj['amount'];
598 writeDetailLine(
599 $bgcolor,
600 $class,
601 $patient_name,
602 $invnumber,
603 $codekey,
604 $production_date,
605 $description,
606 0 - $adj['amount'],
607 ($error ? '' : $invoice_total)
610 } // End of service item
612 // Report any existing service items not mentioned in the ERA, and
613 // determine if any of them are still missing an insurance response
614 // (if so, then insurance is not yet done with the claim).
615 $insurance_done = true;
616 foreach ($codes as $code => $prev) {
617 // writeOldDetail($prev, $arrow['name'], $invnumber, $service_date, $code, $bgcolor);
618 writeOldDetail($prev, $patient_name, $invnumber, $service_date, $code, $bgcolor);
619 $got_response = false;
620 foreach ($prev['dtl'] as $ddata) {
621 if ($ddata['pmt'] ?? '' || ($ddata['rsn'] ?? '')) {
622 $got_response = true;
626 if (!$got_response) {
627 $insurance_done = false;
631 // Cleanup: If all is well, mark Ins<x> done and check for secondary billing.
632 if (!$error && !$debug && $insurance_done) {
633 $level_done = 0 + substr($inslabel, 3);
635 if ($out['crossover'] == 1) {//Automatic forward case.So need not again bill from the billing manager screen.
636 sqlStatement("UPDATE form_encounter " .
637 "SET last_level_closed = ?,last_level_billed=? WHERE " .
638 "pid = ? AND encounter = ?", array($level_done, $level_done, $pid, $encounter));
639 writeMessageLine(
640 $bgcolor,
641 'infdetail',
642 'This claim is processed by Insurance ' . $level_done . ' and automatically forwarded to Insurance ' . ($level_done + 1) . ' for processing. '
644 } else {
645 sqlStatement("UPDATE form_encounter " .
646 "SET last_level_closed = ? WHERE " .
647 "pid = ? AND encounter = ?", array($level_done, $pid, $encounter));
650 // Check for secondary insurance.
651 if ($primary && SLEOB::arGetPayerID($pid, $service_date, 2)) {
652 SLEOB::arSetupSecondary($pid, $encounter, $debug, $out['crossover']);
654 if ($out['crossover'] <> 1) {
655 writeMessageLine(
656 $bgcolor,
657 'infdetail',
658 'This claim is now re-queued for secondary paper billing'
666 /////////////////////////// End Functions ////////////////////////////
668 $info_msg = "";
670 if (!CsrfUtils::verifyCsrfToken($_GET["csrf_token_form"])) {
671 CsrfUtils::csrfNotVerified();
674 $eraname = $_GET['eraname'];
676 if (! $eraname) {
677 die(xlt("You cannot access this page directly."));
680 // Open the output file early so that in case it fails, we do not post a
681 // bunch of stuff without saving the report. Also be sure to retain any old
682 // report files. Do not save the report if this is a no-update situation.
684 if (!$debug) {
685 $nameprefix = $GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname";
686 $namesuffix = '';
687 for ($i = 1; is_file("$nameprefix$namesuffix.html"); ++$i) {
688 $namesuffix = "_$i";
691 $fnreport = "$nameprefix$namesuffix.html";
692 $fhreport = fopen($fnreport, 'w');
693 if (!$fhreport) {
694 die(xlt("Cannot create") . " '" . text($fnreport) . "'");
699 <html>
700 <head>
701 <?php Header::setupHeader(); ?>
702 <style>
703 body {
704 font-family: sans-serif;
705 font-size: 0.6875rem;
706 font-weight: normal;
708 .dehead {
709 font-family: sans-serif;
710 font-size: 0.75rem;
711 font-weight: bold;
713 .olddetail {
714 font-family: sans-serif;
715 font-size: 0.75rem;
716 font-weight: normal;
718 .newdetail {
719 color: var(--success);
720 font-family: sans-serif;
721 font-size: 0.75rem;
722 font-weight: normal;
724 .errdetail {
725 color: var(--danger);
726 font-family: sans-serif;
727 font-size: 0.75rem;
728 font-weight: normal;
730 .infdetail {
731 color: var(--primary);
732 font-family: sans-serif;
733 font-size: 0.75rem;
734 font-weight: normal;
736 </style>
737 <title><?php echo xlt('EOB Posting - Electronic Remittances'); ?></title>
738 </head>
739 <body class='m-0'>
740 <form action="sl_eob_process.php" method="get">
741 <input type="hidden" name="csrf_token_form" value="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" />
743 <?php
744 if (!empty($_GET['original']) && $_GET['original'] == 'original') {
745 $alertmsg = ParseERA::parseERAForCheck($GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname.edi", 'era_callback');
746 echo $StringToEcho;
747 } else {
749 <table class='table table-borderless w-100' cellpadding='2' cellspacing='0'>
751 <tr class="table-light">
752 <td class="dehead">
753 <?php echo xlt('Patient'); ?>
754 </td>
755 <td class="dehead">
756 <?php echo xlt('Invoice'); ?>
757 </td>
758 <td class="dehead">
759 <?php echo xlt('Code'); ?>
760 </td>
761 <td class="dehead">
762 <?php echo xlt('Date'); ?>
763 </td>
764 <td class="dehead">
765 <?php echo xlt('Description'); ?>
766 </td>
767 <td class="dehead" align="right">
768 <?php echo xlt('Amount'); ?>&nbsp;
769 </td>
770 <td class="dehead" align="right">
771 <?php echo xl('Balance'); ?>&nbsp;
772 </td>
773 </tr>
775 <?php
776 global $InsertionId;
778 $eraname = $_REQUEST['eraname'];
779 $alertmsg = ParseERA::parseERAForCheck($GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname.edi");
780 $alertmsg = ParseERA::parseERA($GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname.edi", 'era_callback');
781 if (!$debug) {
782 $StringIssue = xl("Total Distribution for following check number is not full") . ': ';
783 $StringPrint = 'No';
784 if (is_countable($InsertionId)) {
785 foreach ($InsertionId as $key => $value) {
786 $rs = sqlQ("select pay_total from ar_session where session_id=?", array($value));
787 $row = sqlFetchArray($rs);
788 $pay_total = $row['pay_total'];
789 $rs = sqlQ(
790 "select sum(pay_amount) sum_pay_amount from ar_activity where deleted IS NULL AND session_id = ?",
791 array($value)
793 $row = sqlFetchArray($rs);
794 $pay_amount = $row['sum_pay_amount'];
796 if (($pay_total - $pay_amount) <> 0) {
797 $StringIssue .= $key . ' ';
798 $StringPrint = 'Yes';
803 if ($StringPrint == 'Yes') {
804 echo "<script>alert(" . js_escape($StringIssue) . ")</script>";
810 </table>
811 <?php
814 <script>
815 <?php
816 if ($alertmsg) {
817 echo " alert(" . js_escape($alertmsg) . ");\n";
820 function checkAll(checked) {
821 var f = document.forms[0];
822 for (var i = 0; i < f.elements.length; ++i) {
823 var etype = f.elements[i].type;
824 if (etype === 'checkbox')
825 f.elements[i].checked = checked;
828 </script>
829 <input type="hidden" name="paydate" value="<?php echo attr(DateToYYYYMMDD($_REQUEST['paydate'])); ?>" />
830 <input type="hidden" name="post_to_date" value="<?php echo attr(DateToYYYYMMDD($_REQUEST['post_to_date'] ?? '')); ?>" />
831 <input type="hidden" name="deposit_date" value="<?php echo attr(DateToYYYYMMDD($_REQUEST['deposit_date'] ?? '')); ?>" />
832 <input type="hidden" name="debug" value="<?php echo attr($_REQUEST['debug']); ?>" />
833 <input type="hidden" name="InsId" value="<?php echo attr($_REQUEST['InsId'] ?? ''); ?>" />
834 <input type="hidden" name="eraname" value="<?php echo attr($eraname); ?>" />
835 </form>
836 </body>
837 </html>
838 <?php
839 // Save all of this script's output to a report file.
840 if (!$debug) {
841 fwrite($fhreport, ob_get_contents());
842 fclose($fhreport);
845 ob_end_flush();