Skip to content

Commit

Permalink
more billing fixes, support for era posting date override, support fo…
Browse files Browse the repository at this point in the history
…r modifiers in sql-ledger data
  • Loading branch information
sunsetsystems committed Jun 22, 2007
1 parent 9bedab2 commit 55a01e5
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 88 deletions.
8 changes: 8 additions & 0 deletions interface/billing/sl_eob_help.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@
were not applied; these must be processed manually. Currently denied claims and
payment reversals are not handled automatically and so will appear in red.','e')?>

<p><?php xl('If you have entered a Pay Date in the search page, this will
override the posting date of payments and adjustments that are otherwise
taken from the X12 file. This may be useful for reporting purposes, if
you want your receipts reporting to use your posting date rather than the
insurance company\'s processing date. Note that this will also affect
dates of prior payments and adjustments that are put into secondary
claims.','e') ?>

<p><?php xl('The X12 files as well as the resulting HTML output reports are archived
in the "era" subdirectory of the main OpenEMR installation directory. You will
want to refer to these archives from time to time. The URL is ','e') ?>
Expand Down
36 changes: 21 additions & 15 deletions interface/billing/sl_eob_process.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
include_once("remark_codes.php");

$debug = $_GET['debug'] ? 1 : 0; // set to 1 for debugging mode
$paydate = parse_date($_GET['paydate']);
$encount = 0;

$last_ptname = '';
Expand All @@ -31,7 +32,7 @@

function parse_date($date) {
$date = substr(trim($date), 0, 10);
if (preg_match('/^(\d\d\d\d)(\d\d)(\d\d)$/', $date, $matches)) {
if (preg_match('/^(\d\d\d\d)\D*(\d\d)\D*(\d\d)$/', $date, $matches)) {
return $matches[1] . '-' . $matches[2] . '-' . $matches[3];
}
return '';
Expand Down Expand Up @@ -97,7 +98,7 @@ function writeOldDetail(&$prev, $ptname, $invnumber, $dos, $code, $bgcolor) {
//
function era_callback(&$out) {
global $encount, $debug, $claim_status_codes, $adjustment_reasons, $remark_codes;
global $invoice_total, $last_code;
global $invoice_total, $last_code, $paydate;

// Some heading information.
if ($encount == 0) {
Expand Down Expand Up @@ -184,20 +185,25 @@ function era_callback(&$out) {

// Simplify some claim attributes for cleaner code.
$service_date = parse_date($out['dos']);
$check_date = parse_date($out['check_date']);
$production_date = parse_date($out['production_date']);
$check_date = $paydate ? $paydate : parse_date($out['check_date']);
$production_date = $paydate ? $paydate : parse_date($out['production_date']);
$patient_name = $arrow['name'] ? $arrow['name'] :
($out['patient_fname'] . ' ' . $out['patient_lname']);

$error = $inverror;

// This loops once for each service item in this claim.
foreach ($out['svc'] as $svc) {
$prev = $codes[$svc['code']];

// 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];

// This reports detail lines already on file for this service item.
if ($prev) {
writeOldDetail($prev, $patient_name, $invnumber, $service_date, $svc['code'], $bgcolor);
writeOldDetail($prev, $patient_name, $invnumber, $service_date, $codekey, $bgcolor);
// Check for sanity in amount charged.
$prevchg = sprintf("%.2f", $prev['chg'] + $prev['adj']);
if ($prevchg != abs($svc['chg'])) {
Expand All @@ -219,7 +225,7 @@ function era_callback(&$out) {
}
****/

unset($codes[$svc['code']]);
unset($codes[$codekey]);
}

// If the service item is not in our database...
Expand All @@ -228,15 +234,15 @@ function era_callback(&$out) {
// 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:' . $svc['code'] . " Added by $inslabel $production_date";
$description = "CPT4:$codekey Added by $inslabel $production_date";
if (!$error && !$debug) {
slPostCharge($arrow['id'], $svc['chg'], $service_date, $svc['code'],
slPostCharge($arrow['id'], $svc['chg'], $service_date, $codekey,
$insurance_id, $description, $debug);
$invoice_total += $svc['chg'];
}
$class = $error ? 'errdetail' : 'newdetail';
writeDetailLine($bgcolor, $class, $patient_name, $invnumber,
$svc['code'], $production_date, $description,
$codekey, $production_date, $description,
$svc['chg'], ($error ? '' : $invoice_total));

}
Expand Down Expand Up @@ -275,13 +281,13 @@ function era_callback(&$out) {
if ($svc['paid']) {
if (!$error && !$debug) {
slPostPayment($arrow['id'], $svc['paid'], $check_date,
"$inslabel/" . $out['check_number'], $svc['code'], $insurance_id, $debug);
"$inslabel/" . $out['check_number'], $codekey, $insurance_id, $debug);
$invoice_total -= $svc['paid'];
}
$description = "$inslabel/" . $out['check_number'] . ' payment';
if ($svc['paid'] < 0) $description .= ' reversal';
writeDetailLine($bgcolor, $class, $patient_name, $invnumber,
$svc['code'], $check_date, $description,
$codekey, $check_date, $description,
0 - $svc['paid'], ($error ? '' : $invoice_total));
}

Expand Down Expand Up @@ -309,7 +315,7 @@ function era_callback(&$out) {
// Post a zero-dollar adjustment just to save it as a comment.
if (!$error && !$debug) {
slPostAdjustment($arrow['id'], 0, $production_date,
$out['check_number'], $svc['code'], $insurance_id,
$out['check_number'], $codekey, $insurance_id,
$reason, $debug);
}
writeMessageLine($bgcolor, $class, $description . ' ' .
Expand All @@ -319,12 +325,12 @@ function era_callback(&$out) {
else {
if (!$error && !$debug) {
slPostAdjustment($arrow['id'], $adj['amount'], $production_date,
$out['check_number'], $svc['code'], $insurance_id,
$out['check_number'], $codekey, $insurance_id,
"$inslabel adjust code " . $adj['reason_code'], $debug);
$invoice_total -= $adj['amount'];
}
writeDetailLine($bgcolor, $class, $patient_name, $invnumber,
$svc['code'], $production_date, $description,
$codekey, $production_date, $description,
0 - $adj['amount'], ($error ? '' : $invoice_total));
}
}
Expand Down
9 changes: 7 additions & 2 deletions interface/billing/sl_eob_search.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,12 @@ function bucks($amount) {
<head>
<link rel=stylesheet href="<?echo $css_header;?>" type="text/css">
<title><?xl('EOB Posting - Search','e')?></title>
<script type="text/javascript" src="../../library/textformat.js"></script>

<script language="JavaScript">

var mypcc = '1';

function checkAll(checked) {
var f = document.forms[0];
for (var i = 0; i < f.elements.length; ++i) {
Expand Down Expand Up @@ -228,7 +231,8 @@ function npopup(pid) {
</td>
<td>
<input type='text' name='form_paydate' size='10' value='<?php echo $_POST['form_paydate']; ?>'
title='<?xl("Date of payment mm/dd/yyyy","e")?>'>
onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)'
title='<?xl("Date of payment yyyy-mm-dd","e")?>'>
</td>
<td>
<?xl('Amount:','e')?>
Expand Down Expand Up @@ -611,7 +615,8 @@ function npopup(pid) {
function processERA() {
var f = document.forms[0];
var debug = f.form_without.checked ? '1' : '0';
window.open('sl_eob_process.php?eraname=<?php echo $eraname ?>&debug=' + debug, '_blank');
var paydate = f.form_paydate.value;
window.open('sl_eob_process.php?eraname=<?php echo $eraname ?>&debug=' + debug + '&paydate=' + paydate, '_blank');
return false;
}
<?php
Expand Down
139 changes: 91 additions & 48 deletions library/Claim.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,54 @@ class Claim {
var $invoice; // result from get_invoice_summary()
var $payers; // array of arrays, for all payers

function loadPayerInfo(&$billrow) {
global $sl_err;

// Create the $payers array. This contains data for all insurances
// with the current one always at index 0, and the others in payment
// order starting at index 1.
//
$this->payers = array();
$this->payers[0] = array();
$dres = sqlStatement("SELECT * FROM insurance_data WHERE " .
"pid = '{$this->pid}' AND provider != '' " .
"ORDER BY type");
while ($drow = sqlFetchArray($dres)) {
$ins = ($drow['provider'] == $billrow['payer_id']) ?
0 : count($this->payers);
$crow = sqlQuery("SELECT * FROM insurance_companies WHERE " .
"id = '" . $drow['provider'] . "'");
$orow = new InsuranceCompany($drow['provider']);
$this->payers[$ins] = array();
$this->payers[$ins]['data'] = $drow;
$this->payers[$ins]['company'] = $crow;
$this->payers[$ins]['object'] = $orow;
}

$this->using_modifiers = true;

// Get payment and adjustment details if there are any previous payers.
//
$this->invoice = array();
if ($this->payerSequence() != 'P') {
SLConnect();
$arres = SLQuery("select id from ar where invnumber = " .
"'{$this->pid}.{$this->encounter_id}'");
if ($sl_err) die($sl_err);
$arrow = SLGetRow($arres, 0);
if ($arrow) {
$this->invoice = get_invoice_summary($arrow['id'], true);
}
SLClose();
// Secondary claims might not have modifiers in SQL-Ledger data.
// In that case, note that we should not try to match on them.
$this->using_modifiers = false;
foreach ($this->invoice as $key => $trash) {
if (strpos($key, ':')) $this->using_modifiers = true;
}
}
}

// Constructor. Loads relevant database information.
//
function Claim($pid, $encounter_id) {
Expand All @@ -50,9 +98,15 @@ function Claim($pid, $encounter_id) {
$res = sqlStatement($sql);
while ($row = sqlFetchArray($res)) {
if (!$row['units']) $row['units'] = 1;
// Consolidate duplicate procedure codes.
// Load prior payer data at the first opportunity in order to get
// the using_modifiers flag that is referenced below.
if (empty($this->procs)) $this->loadPayerInfo($row);
// Consolidate duplicate procedures.
foreach ($this->procs as $key => $trash) {
if ($this->procs[$key]['code'] == $row['code']) {
if ($this->procs[$key]['code'] == $row['code'] &&
($this->procs[$key]['modifier'] == $row['modifier'] ||
!$this->using_modifiers))
{
$this->procs[$key]['units'] += $row['units'];
$this->procs[$key]['fee'] += $row['fee'];
continue 2; // skip to next table row
Expand Down Expand Up @@ -108,60 +162,29 @@ function Claim($pid, $encounter_id) {
$this->referrer = sqlQuery($sql);
if (!$this->referrer) $this->referrer = array();

// Create the $payers array. This contains data for all insurances
// with the current one always at index 0, and the others in payment
// order starting at index 1.
//
$this->payers = array();
$this->payers[0] = array();
$dres = sqlStatement("SELECT * FROM insurance_data WHERE " .
"pid = '{$this->pid}' AND provider != '' " .
"ORDER BY type");
while ($drow = sqlFetchArray($dres)) {
$ins = ($drow['provider'] == $this->procs[0]['payer_id']) ?
0 : count($this->payers);
$crow = sqlQuery("SELECT * FROM insurance_companies WHERE " .
"id = '" . $drow['provider'] . "'");
$orow = new InsuranceCompany($drow['provider']);
$this->payers[$ins] = array();
$this->payers[$ins]['data'] = $drow;
$this->payers[$ins]['company'] = $crow;
$this->payers[$ins]['object'] = $orow;
}

// Get payment and adjustment details if there are any previous payers.
//
$this->invoice = array();
if ($this->payerSequence() != 'P') {
SLConnect();
$arres = SLQuery("select id from ar where invnumber = " .
"'{$this->pid}.{$this->encounter_id}'");
if ($sl_err) die($sl_err);
$arrow = SLGetRow($arres, 0);
if ($arrow) {
$this->invoice = get_invoice_summary($arrow['id'], true);
}
SLClose();
}

} // end constructor

// Return an array of adjustments from the designated payer for the
// designated procedure code, or for the claim level. For each
// adjustment give date, group code, reason code and amount.
// designated procedure key (might be procedure:modifier), or for the claim
// level. For each adjustment give date, group code, reason code, amount.
//
function payerAdjustments($ins, $code='Claim') {
$aadj = array();
$inslabel = ($this->payerSequence($ins) == 'S') ? 'Ins2' : 'Ins1';

// If we have no modifiers stored in SQL-Ledger for this claim,
// then we cannot use a modifier passed in with the key.
$tmp = strpos($code, ':');
if ($tmp && !$this->using_modifiers) $code = substr($code, 0, $tmp);

// For payments, source always starts with "Ins" or "Pt".
// Nonzero adjustment reason examples:
// Ins1 adjust code 42 (Charges exceed our fee schedule or maximum allowable amount)
// Ins1 adjust code 42 (Charges exceed ... (obsolete))
// Ins1 adjust code 45 (Charges exceed your contracted/ legislated fee arrangement)
// Ins1 adjust code 97 (Payment is included in the allowance for another service/procedure)
// Ins1 adjust code A2 (Contractual adjustment)
// Ins adjust Ins1
// adjust code 42
// adjust code 45
// Zero adjustment reason examples:
// Co-pay: 25.00
// Coinsurance: 11.46 (code 2)
Expand All @@ -184,12 +207,14 @@ function payerAdjustments($ins, $code='Claim') {
$rcode = $tmp[1];
}
else if (preg_match("/$inslabel/i", $rsn, $tmp)) {
// Nothing to do.
}
else if ($inslabel == 'Ins1') {
if (preg_match("/\$adjust code (\S+)/i", $rsn, $tmp)) {
$rcode = $tmp[1];
}
else if ($chg) {
// Nothing to do.
}
else if (preg_match("/Co-pay: (\S+)/i", $rsn, $tmp) ||
preg_match("/Coinsurance: (\S+)/i", $rsn, $tmp)) {
Expand Down Expand Up @@ -219,23 +244,35 @@ function payerAdjustments($ins, $code='Claim') {
return $aadj;
}

// Return the amount and date paid by the designated prior payer.
// If $code is specified then only that procedure code is selected.
// Return date, total payments and total adjustments from the designated
// prior payer. If $code is specified then only that procedure key is
// selected, otherwise it's for the whole claim.
//
function payerPaidAmount($ins, $code='') {
function payerTotals($ins, $code='') {
// If we have no modifiers stored in SQL-Ledger for this claim,
// then we cannot use a modifier passed in with the key.
$tmp = strpos($code, ':');
if ($tmp && !$this->using_modifiers) $code = substr($code, 0, $tmp);

$inslabel = ($this->payerSequence($ins) == 'S') ? 'Ins2' : 'Ins1';
$amount = 0;
$paytotal = 0;
$adjtotal = 0;
$date = '';
foreach($this->invoice as $codekey => $codeval) {
if ($code && $codekey != $code) continue;
foreach ($codeval['dtl'] as $key => $value) {
if (preg_match("/$inslabel/i", $value['src'], $tmp)) {
if (!$date) $date = str_replace('-', '', trim(substr($key, 0, 10)));
$amount += $value['pmt'];
$paytotal += $value['pmt'];
}
}
$aarr = $this->payerAdjustments($ins, $codekey);
foreach ($aarr as $a) {
$adjtotal += $a[3];
if (!$date) $date = $a[0];
}
}
return array(sprintf('%.2f', $amount), $date);
return array($date, sprintf('%.2f', $paytotal), sprintf('%.2f', $adjtotal));
}

// Return the amount already paid by the patient.
Expand Down Expand Up @@ -540,6 +577,12 @@ function cptModifier($prockey) {
return x12clean(trim($this->procs[$prockey]['modifier']));
}

// Returns the procedure code, followed by ":modifier" if there is one.
function cptKey($prockey) {
$tmp = $this->cptModifier($prockey);
return $this->cptCode($prockey) . ($tmp ? ":$tmp" : "");
}

function cptCharges($prockey) {
return x12clean(trim($this->procs[$prockey]['fee']));
}
Expand Down
Loading

0 comments on commit 55a01e5

Please sign in to comment.