4 * This processes X12 835 remittances and produces a report.
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.
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']);
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];
52 function writeMessageLine($bgcolor, $class, $description, $nl2br_process = "false")
55 " <tr bgcolor='" . attr($bgcolor) . "'>\n" .
56 " <td class='" . attr($class) . "' colspan='4'></td>\n";
58 $dline .= " <td class='" . attr($class) . "'>" . nl2br(text($description)) . "</td>\n";
60 $dline .= " <td class='" . attr($class) . "'>" . text($description) . "</td>\n";
63 " <td class='" . attr($class) . "' colspan='2'></td>\n" .
68 function writeDetailLine(
80 global $last_ptname, $last_invnumber, $last_code;
81 if ($ptname == $last_ptname) {
84 $last_ptname = $ptname;
87 if ($invnumber == $last_invnumber) {
90 $last_invnumber = $invnumber;
93 if ($code == $last_code) {
100 $amount = sprintf("%.2f", $amount);
104 $balance = sprintf("%.2f", $balance);
108 " <tr bgcolor='" . attr($bgcolor) . "'>\n" .
109 " <td class='" . attr($class) . "'>" . (($ptname == ' ') ?
'' : text($ptname)) . "</td>\n" .
110 " <td class='" . attr($class) . "'>" . (($invnumber == ' ') ?
'' : text($invnumber)) . "</td>\n" .
111 " <td class='" . attr($class) . "'>" . (($code == ' ') ?
'' : 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" .
120 // This writes detail lines that were already in SQL-Ledger for a given
123 function writeOldDetail(&$prev, $ptname, $invnumber, $dos, $code, $bgcolor)
125 global $invoice_total;
126 // $prev['total'] = 0.00; // to accumulate total charges
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
133 $description = 'Service Item';
136 $amount = sprintf("%.2f", (floatval($ddata['chg'] ??
'')) - (floatval($ddata['pmt'] ??
'')));
137 $invoice_total = sprintf("%.2f", $invoice_total +
$amount);
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
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';
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';
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 .= " " . 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>";
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
229 // Some heading information.
230 $chk_123 = $out['check_number'];
231 $chk_123 = str_replace(' ', '_', $chk_123);
232 if (isset($_REQUEST['chk' . $chk_123])) {
237 "Payer: " . $out['payer_name']
243 "WITHOUT UPDATE is selected; no changes will be applied."
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.
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));
263 $pid = $encounter = 0;
264 $invnumber = $out['our_claim_id'];
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'];
275 if ($csc == '1' ||
$csc == '19') {
279 if ($csc == '2' ||
$csc == '20') {
283 if ($csc == '3' ||
$csc == '21') {
287 $primary = ($inslabel == 'Ins1');
291 "Claim status $csc: " . BillingUtilities
::CLAIM_STATUS_CODES_CLP02
[$csc]
294 // Show an error message if the claim is missing or already posted.
299 "The following claim is not in our database"
302 // Skip this test. Claims can get multiple CLPs from the same payer!
304 // $insdone = strtolower($arrow['shipvia']);
305 // if (strpos($insdone, 'ins1') !== false) {
307 // writeMessageLine($bgcolor, 'errdetail',
308 // "Primary insurance EOB was already posted for the following claim");
312 if ($csc == '4') {//Denial case, code is stored in the claims table for display in the billing manager screen with reason explained.
315 if ($pid && $encounter) {
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);
333 "Not posting adjustments for denied claims, please follow up manually!"
335 } elseif ($csc == '22') {
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'];
357 $patient_name = $ferow['fname'] . ' ' . $ferow['lname'];
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;
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'];
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
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.
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'])) {
411 "EOB charge amount " . $svc['chg'] . " for this code does not match our invoice"
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";
426 $description = "CPT4:$codekey returned by $inslabel $production_date";
428 if (!$error && !$debug) {
442 $invoice_total +
= $svc['chg'];
445 $class = $error ?
'errdetail' : 'newdetail';
455 ($error ?
'' : $invoice_total)
459 $class = $error ?
'errdetail' : 'newdetail';
461 // Report Allowed Amount.
462 if ($svc['allowed'] ??
'') {
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(
485 $InsertionId[$out['check_number']],
486 $svc['paid'], //$InsertionId[$out['check_number']] gives the session id
488 substr($inslabel, 3),
489 $out['check_number'],
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';
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.
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: ';
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'] . ': ';
545 $reason .= sprintf("%.2f", $adj['amount']);
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(
555 $InsertionId[$out['check_number']],
557 $codekey, //$InsertionId[$out['check_number']] gives the session id
558 substr($inslabel, 3),
563 $out['payer_claim_id']
567 writeMessageLine($bgcolor, $class, $description . ' ' .
568 sprintf("%.2f", $adj['amount']));
572 $adj['group_code'] == "CO"
574 $adj['reason_code'] == '45'
575 ||
$adj['reason_code'] == '59'
579 $class = 'errdetail';
581 } elseif (!$error && !$debug) {
582 SLEOB
::arPostAdjustment(
585 $InsertionId[$out['check_number']],
586 $adj['amount'], //$InsertionId[$out['check_number']] gives the session id
588 substr($inslabel, 3),
589 "Adjust code " . $adj['reason_code'],
593 $out['payer_claim_id']
595 $invoice_total -= $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));
642 'This claim is processed by Insurance ' . $level_done . ' and automatically forwarded to Insurance ' . ($level_done +
1) . ' for processing. '
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) {
658 'This claim is now re-queued for secondary paper billing'
666 /////////////////////////// End Functions ////////////////////////////
670 if (!CsrfUtils
::verifyCsrfToken($_GET["csrf_token_form"])) {
671 CsrfUtils
::csrfNotVerified();
674 $eraname = $_GET['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.
685 $nameprefix = $GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname";
687 for ($i = 1; is_file("$nameprefix$namesuffix.html"); ++
$i) {
691 $fnreport = "$nameprefix$namesuffix.html";
692 $fhreport = fopen($fnreport, 'w');
694 die(xlt("Cannot create") . " '" . text($fnreport) . "'");
701 <?php Header
::setupHeader(); ?
>
704 font
-family
: sans
-serif
;
705 font
-size
: 0.6875rem
;
709 font
-family
: sans
-serif
;
714 font
-family
: sans
-serif
;
719 color
: var(--success
);
720 font
-family
: sans
-serif
;
725 color
: var(--danger
);
726 font
-family
: sans
-serif
;
731 color
: var(--primary
);
732 font
-family
: sans
-serif
;
737 <title
><?php
echo xlt('EOB Posting - Electronic Remittances'); ?
></title
>
740 <form action
="sl_eob_process.php" method
="get">
741 <input type
="hidden" name
="csrf_token_form" value
="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" />
744 if (!empty($_GET['original']) && $_GET['original'] == 'original') {
745 $alertmsg = ParseERA
::parseERAForCheck($GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname.edi", 'era_callback');
749 <table
class='table table-borderless w-100' cellpadding
='2' cellspacing
='0'>
751 <tr
class="table-light">
753 <?php
echo xlt('Patient'); ?
>
756 <?php
echo xlt('Invoice'); ?
>
759 <?php
echo xlt('Code'); ?
>
762 <?php
echo xlt('Date'); ?
>
765 <?php
echo xlt('Description'); ?
>
767 <td
class="dehead" align
="right">
768 <?php
echo xlt('Amount'); ?
> 
;
770 <td
class="dehead" align
="right">
771 <?php
echo xl('Balance'); ?
> 
;
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');
782 $StringIssue = xl("Total Distribution for following check number is not full") . ': ';
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'];
790 "select sum(pay_amount) sum_pay_amount from ar_activity where deleted IS NULL AND session_id = ?",
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>";
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
;
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); ?>" />
839 // Save all of this script's output to a report file.
841 fwrite($fhreport, ob_get_contents());