You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
812 lines
30 KiB
812 lines
30 KiB
<?php |
|
|
|
/** |
|
* This processes X12 835 remittances and produces a report. |
|
* |
|
* @package OpenEMR |
|
* @link http://www.open-emr.org |
|
* @author Rod Roark <rod@sunsetsystems.com> |
|
* @author Brady Miller <brady.g.miller@gmail.com> |
|
* @author Stephen Waite <stephen.waite@cmsvt.com> |
|
* @copyright Copyright (c) 2006-2020 Rod Roark <rod@sunsetsystems.com> |
|
* @copyright Copyright (c) 2018 Brady Miller <brady.g.miller@gmail.com> |
|
* @copyright Copyright (c) 2019-2020 Stephen Waite <stephen.waite@cmsvt.com> |
|
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 |
|
*/ |
|
|
|
// Buffer all output so we can archive it to a file. |
|
ob_start(); |
|
|
|
require_once("../globals.php"); |
|
|
|
use OpenEMR\Billing\BillingUtilities; |
|
use OpenEMR\Billing\InvoiceSummary; |
|
use OpenEMR\Billing\ParseERA; |
|
use OpenEMR\Billing\SLEOB; |
|
use OpenEMR\Common\Csrf\CsrfUtils; |
|
use OpenEMR\Core\Header; |
|
use OpenEMR\Services\InsuranceService; |
|
|
|
$debug = $_GET['debug'] ? 1 : 0; // set to 1 for debugging mode |
|
$paydate = parse_date($_GET['paydate']); |
|
$encount = 0; |
|
|
|
$last_ptname = ''; |
|
$last_invnumber = ''; |
|
$last_code = ''; |
|
$invoice_total = 0.00; |
|
$InsertionId; // last inserted ID of |
|
|
|
///////////////////////// Assorted Functions ///////////////////////// |
|
|
|
function parse_date($date) |
|
{ |
|
$date = substr(trim($date), 0, 10); |
|
if (preg_match('/^(\d\d\d\d)\D*(\d\d)\D*(\d\d)$/', $date, $matches)) { |
|
return $matches[1] . '-' . $matches[2] . '-' . $matches[3]; |
|
} |
|
|
|
return ''; |
|
} |
|
|
|
function writeMessageLine($bgcolor, $class, $description, $nl2br_process = "false") |
|
{ |
|
$dline = |
|
" <tr bgcolor='" . attr($bgcolor) . "'>\n" . |
|
" <td class='" . attr($class) . "' colspan='4'></td>\n"; |
|
if ($nl2br_process) { |
|
$dline .= " <td class='" . attr($class) . "'>" . nl2br(text($description)) . "</td>\n"; |
|
} else { |
|
$dline .= " <td class='" . attr($class) . "'>" . text($description) . "</td>\n"; |
|
} |
|
$dline .= |
|
" <td class='" . attr($class) . "' colspan='2'></td>\n" . |
|
" </tr>\n"; |
|
echo $dline; |
|
} |
|
|
|
function writeDetailLine( |
|
$bgcolor, |
|
$class, |
|
$ptname, |
|
$invnumber, |
|
$code, |
|
$date, |
|
$description, |
|
$amount, |
|
$balance |
|
) { |
|
|
|
global $last_ptname, $last_invnumber, $last_code; |
|
if ($ptname == $last_ptname) { |
|
$ptname = ''; |
|
} else { |
|
$last_ptname = $ptname; |
|
} |
|
|
|
if ($invnumber == $last_invnumber) { |
|
$invnumber = ''; |
|
} else { |
|
$last_invnumber = $invnumber; |
|
} |
|
|
|
if ($code == $last_code) { |
|
$code = ''; |
|
} else { |
|
$last_code = $code; |
|
} |
|
|
|
if ($amount) { |
|
$amount = sprintf("%.2f", $amount); |
|
} |
|
|
|
if ($balance) { |
|
$balance = sprintf("%.2f", $balance); |
|
} |
|
|
|
$dline = |
|
" <tr bgcolor='" . attr($bgcolor) . "'>\n" . |
|
" <td class='" . attr($class) . "'>" . text($ptname) . "</td>\n" . |
|
" <td class='" . attr($class) . "'>" . text($invnumber) . "</td>\n" . |
|
" <td class='" . attr($class) . "'>" . text($code) . "</td>\n" . |
|
" <td class='" . attr($class) . "'>" . text(oeFormatShortDate($date)) . "</td>\n" . |
|
" <td class='" . attr($class) . "'>" . text($description) . "</td>\n" . |
|
" <td class='" . attr($class) . "' align='right'>" . text(oeFormatMoney($amount)) . "</td>\n" . |
|
" <td class='" . attr($class) . "' align='right'>" . text(oeFormatMoney($balance)) . "</td>\n" . |
|
" </tr>\n"; |
|
echo $dline; |
|
} |
|
|
|
// This writes detail lines that were already in SQL-Ledger for a given |
|
// charge item. |
|
// |
|
function writeOldDetail(&$prev, $ptname, $invnumber, $dos, $code, $bgcolor) |
|
{ |
|
global $invoice_total; |
|
// $prev['total'] = 0.00; // to accumulate total charges |
|
ksort($prev['dtl']); |
|
foreach ($prev['dtl'] as $dkey => $ddata) { |
|
$ddate = substr($dkey, 0, 10); |
|
$description = ($ddata['src'] ?? '') . ($ddata['rsn'] ?? ''); |
|
if ($ddate == ' ') { // this is the service item |
|
$ddate = $dos; |
|
$description = 'Service Item'; |
|
} |
|
|
|
$amount = sprintf("%.2f", (int)($ddata['chg'] ?? '') - (int)($ddata['pmt'] ?? '')); |
|
$invoice_total = sprintf("%.2f", $invoice_total + $amount); |
|
writeDetailLine( |
|
$bgcolor, |
|
'olddetail', |
|
$ptname, |
|
$invnumber, |
|
$code, |
|
$ddate, |
|
$description, |
|
$amount, |
|
$invoice_total |
|
); |
|
} |
|
} |
|
|
|
// This is called back by ParseERA::parseERA() once per claim. |
|
// |
|
|
|
// TODO: Sort colors here for Bootstrap themes |
|
function era_callback_check(&$out) |
|
{ |
|
// last inserted ID of ar_session table |
|
global $InsertionId; |
|
global $StringToEcho,$debug; |
|
|
|
if (!empty($_GET['original']) && $_GET['original'] == 'original') { |
|
$StringToEcho .= "<table class='table'>"; |
|
$StringToEcho .= "<thead>"; |
|
$StringToEcho .= "<tr>"; |
|
$StringToEcho .= "<th scope='col'>" . xlt('Check Number') . "</th>"; |
|
$StringToEcho .= "<th scope='col'>" . xlt('Payee Name') . "</th>"; |
|
$StringToEcho .= "<th scope='col'>" . xlt('Payer Name') . "</th>"; |
|
$StringToEcho .= "<th scope='col'>" . xlt('Check Amount') . "</th>"; |
|
$StringToEcho .= "</tr>"; |
|
$StringToEcho .= "</thead>"; |
|
$StringToEcho .= "<tbody>"; |
|
$WarningFlag = false; |
|
for ($check_count = 1; $check_count <= $out['check_count']; $check_count++) { |
|
if ($check_count % 2 == 1) { |
|
$bgcolor = '#ddddff'; |
|
} else { |
|
$bgcolor = '#ffdddd'; |
|
} |
|
|
|
$rs = sqlQ("select reference from ar_session where reference=?", array($out['check_number' . $check_count])); |
|
|
|
if (sqlNumRows($rs) > 0) { |
|
$bgcolor = '#ff0000'; |
|
$WarningFlag = true; |
|
} |
|
|
|
$StringToEcho .= "<tr bgcolor='" . attr($bgcolor) . "'>"; |
|
$StringToEcho .= "<th scope='row'>"; |
|
$StringToEcho .= "<input type='checkbox' name='chk" . attr($out['check_number' . $check_count]) . "' id='chk" . attr($out['check_number' . $check_count]) . "'/>"; |
|
$StringToEcho .= "<label for='chk" . attr($out['check_number' . $check_count]) . "'>"; |
|
$StringToEcho .= " " . text($out['check_number' . $check_count]) . "</label>"; |
|
$StringToEcho .= "</th>"; |
|
$StringToEcho .= "<td>" . text($out['payee_name' . $check_count]) . "</td>"; |
|
$StringToEcho .= "<td>" . text($out['payer_name' . $check_count]) . "</td>"; |
|
$StringToEcho .= "<td>" . text(number_format($out['check_amount' . $check_count], 2)) . "</td>"; |
|
$StringToEcho .= "</tr>"; |
|
} |
|
|
|
$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>"; |
|
$StringToEcho .= "<td><input type='submit' name='CheckSubmit' value='Submit'/></td>"; |
|
$StringToEcho .= "</tr>"; |
|
|
|
if ($WarningFlag == true) { |
|
$StringToEcho .= "<tr class='table-danger'><td colspan='4' align='center'>" . xlt('Warning, Check Number already exist in the database') . "</td></tr>"; |
|
} |
|
$StringToEcho .= "</tbody>"; |
|
$StringToEcho .= "</table>"; |
|
} else { |
|
for ($check_count = 1; $check_count <= $out['check_count']; $check_count++) { |
|
$chk_num = $out['check_number' . $check_count]; |
|
$chk_num = str_replace(' ', '_', $chk_num); |
|
if (isset($_REQUEST['chk' . $chk_num])) { |
|
$check_date = $out['check_date' . $check_count] ? $out['check_date' . $check_count] : $_REQUEST['paydate']; |
|
$post_to_date = $_REQUEST['post_to_date'] != '' ? $_REQUEST['post_to_date'] : date('Y-m-d'); |
|
$deposit_date = $_REQUEST['deposit_date'] != '' ? $_REQUEST['deposit_date'] : date('Y-m-d'); |
|
$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); |
|
} |
|
} |
|
} |
|
} |
|
function era_callback(&$out) |
|
{ |
|
global $encount, $debug; |
|
global $invoice_total, $last_code, $paydate; |
|
// last inserted ID of ar_session table |
|
global $InsertionId; |
|
|
|
// Some heading information. |
|
$chk_123 = $out['check_number']; |
|
$chk_123 = str_replace(' ', '_', $chk_123); |
|
if (isset($_REQUEST['chk' . $chk_123])) { |
|
if ($encount == 0) { |
|
writeMessageLine( |
|
'var(--white)', |
|
'infdetail', |
|
"Payer: " . $out['payer_name'] |
|
); |
|
if ($debug) { |
|
writeMessageLine( |
|
'var(--white)', |
|
'infdetail', |
|
"WITHOUT UPDATE is selected; no changes will be applied." |
|
); |
|
} |
|
} |
|
|
|
$last_code = ''; |
|
$invoice_total = 0.00; |
|
$bgcolor = (++$encount & 1) ? "#ddddff" : "#ffdddd"; |
|
list($pid, $encounter, $invnumber) = SLEOB::slInvoiceNumber($out); |
|
|
|
// Get details, if we have them, for the invoice. |
|
$inverror = true; |
|
$codes = array(); |
|
if ($pid && $encounter) { |
|
// Get invoice data into $arrow or $ferow. |
|
$ferow = sqlQuery("SELECT e.*, p.fname, p.mname, p.lname " . |
|
"FROM form_encounter AS e, patient_data AS p WHERE " . |
|
"e.pid = ? AND e.encounter = ? AND " . |
|
"p.pid = e.pid", array($pid, $encounter)); |
|
if (empty($ferow)) { |
|
$pid = $encounter = 0; |
|
$invnumber = $out['our_claim_id']; |
|
} else { |
|
$inverror = false; |
|
$codes = InvoiceSummary::arGetInvoiceSummary($pid, $encounter, true); |
|
// $svcdate = substr($ferow['date'], 0, 10); |
|
} |
|
} |
|
|
|
// Show the claim status. |
|
$csc = $out['claim_status_code']; |
|
$inslabel = 'Ins1'; |
|
if ($csc == '1' || $csc == '19') { |
|
$inslabel = 'Ins1'; |
|
} |
|
|
|
if ($csc == '2' || $csc == '20') { |
|
$inslabel = 'Ins2'; |
|
} |
|
|
|
if ($csc == '3' || $csc == '21') { |
|
$inslabel = 'Ins3'; |
|
} |
|
|
|
$primary = ($inslabel == 'Ins1'); |
|
writeMessageLine( |
|
$bgcolor, |
|
'infdetail', |
|
"Claim status $csc: " . BillingUtilities::CLAIM_STATUS_CODES_CLP02[$csc] |
|
); |
|
|
|
// Show an error message if the claim is missing or already posted. |
|
if ($inverror) { |
|
writeMessageLine( |
|
$bgcolor, |
|
'errdetail', |
|
"The following claim is not in our database" |
|
); |
|
} else { |
|
// Skip this test. Claims can get multiple CLPs from the same payer! |
|
// |
|
// $insdone = strtolower($arrow['shipvia']); |
|
// if (strpos($insdone, 'ins1') !== false) { |
|
// $inverror = true; |
|
// writeMessageLine($bgcolor, 'errdetail', |
|
// "Primary insurance EOB was already posted for the following claim"); |
|
// } |
|
} |
|
|
|
if ($csc == '4') {//Denial case, code is stored in the claims table for display in the billing manager screen with reason explained. |
|
$inverror = true; |
|
if (!$debug) { |
|
if ($pid && $encounter) { |
|
$code_value = ''; |
|
foreach ($out['svc'] as $svc) { |
|
foreach ($svc['adj'] as $adj) {//Per code and modifier the reason will be showed in the billing manager. |
|
$code_value .= $svc['code'] . '_' . $svc['mod'] . '_' . $adj['group_code'] . '_' . $adj['reason_code'] . ','; |
|
} |
|
} |
|
|
|
$code_value = substr($code_value, 0, -1); |
|
//We store the reason code to display it with description in the billing manager screen. |
|
//process_file is used as for the denial case file name will not be there, and extra field(to store reason) can be avoided. |
|
BillingUtilities::updateClaim(true, $pid, $encounter, $_REQUEST['InsId'], substr($inslabel, 3), 7, 0, $code_value); |
|
} |
|
} |
|
|
|
writeMessageLine( |
|
$bgcolor, |
|
'errdetail', |
|
"Not posting adjustments for denied claims, please follow up manually!" |
|
); |
|
} elseif ($csc == '22') { |
|
$inverror = true; |
|
writeMessageLine( |
|
$bgcolor, |
|
'errdetail', |
|
"Payment reversals are not automated, please enter manually!" |
|
); |
|
} |
|
|
|
if ($out['warnings']) { |
|
writeMessageLine($bgcolor, 'infdetail', rtrim($out['warnings']), true); |
|
} |
|
|
|
// Simplify some claim attributes for cleaner code. |
|
$service_date = parse_date(isset($out['dos']) ? $out['dos'] : $out['claim_date']); |
|
$check_date = $paydate ? $paydate : parse_date($out['check_date']); |
|
$production_date = $paydate ? $paydate : parse_date($out['production_date']); |
|
|
|
$insurance_id = SLEOB::arGetPayerID($pid, $service_date, substr($inslabel, 3)); |
|
if (empty($ferow['lname'])) { |
|
$patient_name = $out['patient_fname'] . ' ' . $out['patient_lname']; |
|
} else { |
|
$patient_name = $ferow['fname'] . ' ' . $ferow['lname']; |
|
} |
|
|
|
$error = $inverror; |
|
|
|
// This loops once for each service item in this claim. |
|
foreach ($out['svc'] as $svc) { |
|
// Treat a modifier in the remit data as part of the procedure key. |
|
// This key will then make its way into SQL-Ledger. |
|
$codekey = $svc['code']; |
|
if ($svc['mod']) { |
|
$codekey .= ':' . $svc['mod']; |
|
} |
|
|
|
$prev = $codes[$codekey] ?? ''; |
|
$codetype = ''; //will hold code type, if exists |
|
|
|
// This reports detail lines already on file for this service item. |
|
if ($prev) { |
|
$codetype = $codes[$codekey]['code_type']; //store code type |
|
writeOldDetail($prev, $patient_name, $invnumber, $service_date, $codekey, $bgcolor); |
|
// Check for sanity in amount charged. |
|
$prevchg = sprintf("%.2f", $prev['chg'] + ($prev['adj'] ?? null)); |
|
if ($prevchg != abs($svc['chg'])) { |
|
writeMessageLine( |
|
$bgcolor, |
|
'errdetail', |
|
"EOB charge amount " . $svc['chg'] . " for this code does not match our invoice" |
|
); |
|
$error = true; |
|
} |
|
|
|
// Check for already-existing primary remittance activity. |
|
// Removed this check because it was not allowing for copays manually |
|
// entered into the invoice under a non-copay billing code. |
|
/**** |
|
if ((sprintf("%.2f",$prev['chg']) != sprintf("%.2f",$prev['bal']) || |
|
$prev['adj'] != 0) && $primary) |
|
{ |
|
writeMessageLine($bgcolor, 'errdetail', |
|
"This service item already has primary payments and/or adjustments!"); |
|
$error = true; |
|
} |
|
****/ |
|
|
|
unset($codes[$codekey]); |
|
} else { // If the service item is not in our database... |
|
// This is not an error. If we are not in error mode and not debugging, |
|
// insert the service item into SL. Then display it (in green if it |
|
// was inserted, or in red if we are in error mode). |
|
$description = "CPT4:$codekey Added by $inslabel $production_date"; |
|
if (!$error && !$debug) { |
|
SLEOB::arPostCharge( |
|
$pid, |
|
$encounter, |
|
0, |
|
$svc['chg'], |
|
1, |
|
$service_date, |
|
$codekey, |
|
$description, |
|
$debug, |
|
'', |
|
$codetype ?? '' |
|
); |
|
$invoice_total += $svc['chg']; |
|
} |
|
|
|
$class = $error ? 'errdetail' : 'newdetail'; |
|
writeDetailLine( |
|
$bgcolor, |
|
$class, |
|
$patient_name, |
|
$invnumber, |
|
$codekey, |
|
$production_date, |
|
$description, |
|
$svc['chg'], |
|
($error ? '' : $invoice_total) |
|
); |
|
} |
|
|
|
$class = $error ? 'errdetail' : 'newdetail'; |
|
|
|
// Report Allowed Amount. |
|
if ($svc['allowed'] ?? '') { |
|
writeMessageLine( |
|
$bgcolor, |
|
'infdetail', |
|
'Allowed amount is ' . sprintf("%.2f", $svc['allowed']) |
|
); |
|
} |
|
|
|
// Report miscellaneous remarks. |
|
if ($svc['remark'] ?? '') { |
|
$rmk = $svc['remark']; |
|
writeMessageLine($bgcolor, 'infdetail', "$rmk: " . |
|
BillingUtilities::REMITTANCE_ADVICE_REMARK_CODES[$rmk]); |
|
} |
|
|
|
// Post and report the payment for this service item from the ERA. |
|
// By the way a 'Claim' level payment is probably going to be negative, |
|
// i.e. a payment reversal. |
|
if ($svc['paid'] ?? '') { |
|
if (!$error && !$debug) { |
|
SLEOB::arPostPayment( |
|
$pid, |
|
$encounter, |
|
$InsertionId[$out['check_number']], |
|
$svc['paid'], //$InsertionId[$out['check_number']] gives the session id |
|
$codekey, |
|
substr($inslabel, 3), |
|
$out['check_number'], |
|
$debug, |
|
'', |
|
$codetype |
|
); |
|
$invoice_total -= $svc['paid']; |
|
} |
|
|
|
$description = "$inslabel/" . $out['check_number'] . ' payment'; |
|
if ($svc['paid'] < 0) { |
|
$description .= ' reversal'; |
|
} |
|
|
|
writeDetailLine( |
|
$bgcolor, |
|
$class, |
|
$patient_name, |
|
$invnumber, |
|
$codekey, |
|
$check_date, |
|
$description, |
|
0 - $svc['paid'], |
|
($error ? '' : $invoice_total) |
|
); |
|
} |
|
|
|
// Post and report adjustments from this ERA. Posted adjustment reasons |
|
// must be 25 characters or less in order to fit on patient statements. |
|
foreach ($svc['adj'] as $adj) { |
|
$description = $adj['reason_code'] ?? '' . ': ' . |
|
BillingUtilities::CLAIM_ADJUSTMENT_REASON_CODES[$adj['reason_code'] ?? '']; |
|
if ($adj['group_code'] == 'PR' || !$primary) { |
|
// Group code PR is Patient Responsibility. Enter these as zero |
|
// adjustments to retain the note without crediting the claim. |
|
if ($primary) { |
|
/**** |
|
$reason = 'Pt resp: '; // Reasons should be 25 chars or less. |
|
if ($adj['reason_code'] == '1') $reason = 'To deductible: '; |
|
else if ($adj['reason_code'] == '2') $reason = 'Coinsurance: '; |
|
else if ($adj['reason_code'] == '3') $reason = 'Co-pay: '; |
|
****/ |
|
$reason = "$inslabel ptresp: "; // Reasons should be 25 chars or less. |
|
if ($adj['reason_code'] == '1') { |
|
$reason = "$inslabel dedbl: "; |
|
} elseif ($adj['reason_code'] == '2') { |
|
$reason = "$inslabel coins: "; |
|
} elseif ($adj['reason_code'] == '3') { |
|
$reason = "$inslabel copay: "; |
|
} |
|
} else { // Non-primary insurance adjustments are garbage, either repeating |
|
// the primary or are not adjustments at all. Report them as notes |
|
// but do not post any amounts. |
|
$reason = "$inslabel note " . $adj['reason_code'] . ': '; |
|
/**** |
|
$reason .= sprintf("%.2f", $adj['amount']); |
|
****/ |
|
} |
|
|
|
$reason .= sprintf("%.2f", $adj['amount']); |
|
// Post a zero-dollar adjustment just to save it as a comment. |
|
if (!$error && !$debug) { |
|
SLEOB::arPostAdjustment( |
|
$pid, |
|
$encounter, |
|
$InsertionId[$out['check_number']], |
|
0, |
|
$codekey, //$InsertionId[$out['check_number']] gives the session id |
|
substr($inslabel, 3), |
|
$reason, |
|
$debug, |
|
'', |
|
$codetype |
|
); |
|
} |
|
|
|
writeMessageLine($bgcolor, $class, $description . ' ' . |
|
sprintf("%.2f", $adj['amount'])); |
|
} else { // Other group codes for primary insurance are real adjustments. |
|
if (!$error && !$debug) { |
|
SLEOB::arPostAdjustment( |
|
$pid, |
|
$encounter, |
|
$InsertionId[$out['check_number']], |
|
$adj['amount'], //$InsertionId[$out['check_number']] gives the session id |
|
$codekey, |
|
substr($inslabel, 3), |
|
"Adjust code " . $adj['reason_code'], |
|
$debug, |
|
'', |
|
$codetype ?? '' |
|
); |
|
$invoice_total -= $adj['amount']; |
|
} |
|
|
|
writeDetailLine( |
|
$bgcolor, |
|
$class, |
|
$patient_name, |
|
$invnumber, |
|
$codekey, |
|
$production_date, |
|
$description, |
|
0 - $adj['amount'], |
|
($error ? '' : $invoice_total) |
|
); |
|
} |
|
} |
|
} // End of service item |
|
|
|
// Report any existing service items not mentioned in the ERA, and |
|
// determine if any of them are still missing an insurance response |
|
// (if so, then insurance is not yet done with the claim). |
|
$insurance_done = true; |
|
foreach ($codes as $code => $prev) { |
|
// writeOldDetail($prev, $arrow['name'], $invnumber, $service_date, $code, $bgcolor); |
|
writeOldDetail($prev, $patient_name, $invnumber, $service_date, $code, $bgcolor); |
|
$got_response = false; |
|
foreach ($prev['dtl'] as $ddata) { |
|
if ($ddata['pmt'] ?? '' || ($ddata['rsn'] ?? '')) { |
|
$got_response = true; |
|
} |
|
} |
|
|
|
if (!$got_response) { |
|
$insurance_done = false; |
|
} |
|
} |
|
|
|
// Cleanup: If all is well, mark Ins<x> done and check for secondary billing. |
|
if (!$error && !$debug && $insurance_done) { |
|
$level_done = 0 + substr($inslabel, 3); |
|
|
|
if ($out['crossover'] == 1) {//Automatic forward case.So need not again bill from the billing manager screen. |
|
sqlStatement("UPDATE form_encounter " . |
|
"SET last_level_closed = ?,last_level_billed=? WHERE " . |
|
"pid = ? AND encounter = ?", array($level_done, $level_done, $pid, $encounter)); |
|
writeMessageLine( |
|
$bgcolor, |
|
'infdetail', |
|
'This claim is processed by Insurance ' . $level_done . ' and automatically forwarded to Insurance ' . ($level_done + 1) . ' for processing. ' |
|
); |
|
} else { |
|
sqlStatement("UPDATE form_encounter " . |
|
"SET last_level_closed = ? WHERE " . |
|
"pid = ? AND encounter = ?", array($level_done, $pid, $encounter)); |
|
} |
|
|
|
// Check for secondary insurance. |
|
if ($primary && SLEOB::arGetPayerID($pid, $service_date, 2)) { |
|
SLEOB::arSetupSecondary($pid, $encounter, $debug, $out['crossover']); |
|
|
|
if ($out['crossover'] <> 1) { |
|
writeMessageLine( |
|
$bgcolor, |
|
'infdetail', |
|
'This claim is now re-queued for secondary paper billing' |
|
); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
/////////////////////////// End Functions //////////////////////////// |
|
|
|
$info_msg = ""; |
|
|
|
if (!CsrfUtils::verifyCsrfToken($_GET["csrf_token_form"])) { |
|
CsrfUtils::csrfNotVerified(); |
|
} |
|
|
|
$eraname = $_GET['eraname']; |
|
|
|
if (! $eraname) { |
|
die(xlt("You cannot access this page directly.")); |
|
} |
|
|
|
// Open the output file early so that in case it fails, we do not post a |
|
// bunch of stuff without saving the report. Also be sure to retain any old |
|
// report files. Do not save the report if this is a no-update situation. |
|
// |
|
if (!$debug) { |
|
$nameprefix = $GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname"; |
|
$namesuffix = ''; |
|
for ($i = 1; is_file("$nameprefix$namesuffix.html"); ++$i) { |
|
$namesuffix = "_$i"; |
|
} |
|
|
|
$fnreport = "$nameprefix$namesuffix.html"; |
|
$fhreport = fopen($fnreport, 'w'); |
|
if (!$fhreport) { |
|
die(xlt("Cannot create") . " '" . text($fnreport) . "'"); |
|
} |
|
} |
|
|
|
?> |
|
<html> |
|
<head> |
|
<?php Header::setupHeader(); ?> |
|
<style> |
|
body { |
|
font-family: sans-serif; |
|
font-size: 0.6875rem; |
|
font-weight: normal; |
|
} |
|
.dehead { |
|
font-family: sans-serif; |
|
font-size: 0.75rem; |
|
font-weight: bold; |
|
} |
|
.olddetail { |
|
font-family: sans-serif; |
|
font-size: 0.75rem; |
|
font-weight: normal; |
|
} |
|
.newdetail { |
|
color: var(--success); |
|
font-family: sans-serif; |
|
font-size: 0.75rem; |
|
font-weight: normal; |
|
} |
|
.errdetail { |
|
color: var(--danger); |
|
font-family: sans-serif; |
|
font-size: 0.75rem; |
|
font-weight: normal; |
|
} |
|
.infdetail { |
|
color: var(--primary); |
|
font-family: sans-serif; |
|
font-size: 0.75rem; |
|
font-weight: normal; |
|
} |
|
</style> |
|
<title><?php echo xlt('EOB Posting - Electronic Remittances'); ?></title> |
|
</head> |
|
<body class='m-0'> |
|
<form action="sl_eob_process.php" method="get"> |
|
<input type="hidden" name="csrf_token_form" value="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" /> |
|
|
|
<?php |
|
if (!empty($_GET['original']) && $_GET['original'] == 'original') { |
|
$alertmsg = ParseERA::parseERAForCheck($GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname.edi", 'era_callback'); |
|
echo $StringToEcho; |
|
} else { |
|
?> |
|
<table class='table table-borderless w-100' cellpadding='2' cellspacing='0'> |
|
|
|
<tr class="table-light"> |
|
<td class="dehead"> |
|
<?php echo xlt('Patient'); ?> |
|
</td> |
|
<td class="dehead"> |
|
<?php echo xlt('Invoice'); ?> |
|
</td> |
|
<td class="dehead"> |
|
<?php echo xlt('Code'); ?> |
|
</td> |
|
<td class="dehead"> |
|
<?php echo xlt('Date'); ?> |
|
</td> |
|
<td class="dehead"> |
|
<?php echo xlt('Description'); ?> |
|
</td> |
|
<td class="dehead" align="right"> |
|
<?php echo xlt('Amount'); ?> |
|
</td> |
|
<td class="dehead" align="right"> |
|
<?php echo xl('Balance'); ?> |
|
</td> |
|
</tr> |
|
|
|
<?php |
|
global $InsertionId; |
|
|
|
$eraname = $_REQUEST['eraname']; |
|
$alertmsg = ParseERA::parseERAForCheck($GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname.edi"); |
|
$alertmsg = ParseERA::parseERA($GLOBALS['OE_SITE_DIR'] . "/documents/era/$eraname.edi", 'era_callback'); |
|
if (!$debug) { |
|
$StringIssue = xl("Total Distribution for following check number is not full") . ': '; |
|
$StringPrint = 'No'; |
|
if (is_countable($InsertionId)) { |
|
foreach ($InsertionId as $key => $value) { |
|
$rs = sqlQ("select pay_total from ar_session where session_id=?", array($value)); |
|
$row = sqlFetchArray($rs); |
|
$pay_total = $row['pay_total']; |
|
$rs = sqlQ( |
|
"select sum(pay_amount) sum_pay_amount from ar_activity where deleted IS NULL AND session_id = ?", |
|
array($value) |
|
); |
|
$row = sqlFetchArray($rs); |
|
$pay_amount = $row['sum_pay_amount']; |
|
|
|
if (($pay_total - $pay_amount) <> 0) { |
|
$StringIssue .= $key . ' '; |
|
$StringPrint = 'Yes'; |
|
} |
|
} |
|
} |
|
|
|
if ($StringPrint == 'Yes') { |
|
echo "<script>alert(" . js_escape($StringIssue) . ")</script>"; |
|
} |
|
} |
|
|
|
|
|
?> |
|
</table> |
|
<?php |
|
} |
|
?> |
|
<script> |
|
<?php |
|
if ($alertmsg) { |
|
echo " alert(" . js_escape($alertmsg) . ");\n"; |
|
} |
|
?> |
|
function checkAll(checked) { |
|
var f = document.forms[0]; |
|
for (var i = 0; i < f.elements.length; ++i) { |
|
var etype = f.elements[i].type; |
|
if (etype === 'checkbox') |
|
f.elements[i].checked = checked; |
|
} |
|
} |
|
</script> |
|
<input type="hidden" name="paydate" value="<?php echo attr(DateToYYYYMMDD($_REQUEST['paydate'])); ?>" /> |
|
<input type="hidden" name="post_to_date" value="<?php echo attr(DateToYYYYMMDD($_REQUEST['post_to_date'] ?? '')); ?>" /> |
|
<input type="hidden" name="deposit_date" value="<?php echo attr(DateToYYYYMMDD($_REQUEST['deposit_date'] ?? '')); ?>" /> |
|
<input type="hidden" name="debug" value="<?php echo attr($_REQUEST['debug']); ?>" /> |
|
<input type="hidden" name="InsId" value="<?php echo attr($_REQUEST['InsId'] ?? ''); ?>" /> |
|
<input type="hidden" name="eraname" value="<?php echo attr($eraname); ?>" /> |
|
</form> |
|
</body> |
|
</html> |
|
<?php |
|
// Save all of this script's output to a report file. |
|
if (!$debug) { |
|
fwrite($fhreport, ob_get_contents()); |
|
fclose($fhreport); |
|
} |
|
|
|
ob_end_flush(); |
|
?>
|
|
|