Example #1
0
function era_callback(&$out)
{
    global $encount, $debug, $claim_status_codes, $adjustment_reasons, $remark_codes;
    global $invoice_total, $last_code, $paydate, $INTEGRATED_AR;
    // Some heading information.
    if ($encount == 0) {
        writeMessageLine('#ffffff', 'infdetail', "Payer: " . htmlentities($out['payer_name']));
        if ($debug) {
            writeMessageLine('#ffffff', 'infdetail', "WITHOUT UPDATE is selected; no changes will be applied.");
        }
    }
    $last_code = '';
    $invoice_total = 0.0;
    $bgcolor = ++$encount & 1 ? "#ddddff" : "#ffdddd";
    list($pid, $encounter, $invnumber) = 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.
        if ($INTEGRATED_AR) {
            $ferow = sqlQuery("SELECT e.*, p.fname, p.mname, p.lname " . "FROM form_encounter AS e, patient_data AS p WHERE " . "e.pid = '{$pid}' AND e.encounter = '{$encounter}' AND " . "p.pid = e.pid");
            if (empty($ferow)) {
                $pid = $encounter = 0;
                $invnumber = $out['our_claim_id'];
            } else {
                $inverror = false;
                $codes = ar_get_invoice_summary($pid, $encounter, true);
                // $svcdate = substr($ferow['date'], 0, 10);
            }
        } else {
            $arres = SLQuery("SELECT ar.id, ar.notes, ar.shipvia, customer.name " . "FROM ar, customer WHERE ar.invnumber = '{$invnumber}' AND " . "customer.id = ar.customer_id");
            if ($sl_err) {
                die($sl_err);
            }
            $arrow = SLGetRow($arres, 0);
            if ($arrow) {
                $inverror = false;
                $codes = get_invoice_summary($arrow['id'], true);
            } else {
                // oops, no such invoice
                $pid = $encounter = 0;
                $invnumber = $out['our_claim_id'];
            }
        }
        // end not internal a/r
    }
    // 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}: " . $claim_status_codes[$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') {
        $inverror = true;
        writeMessageLine($bgcolor, 'errdetail', "Not posting adjustments for denied claims, please follow up manually!");
    } else {
        if ($csc == '22') {
            $inverror = true;
            writeMessageLine($bgcolor, 'errdetail', "Payment reversals are not automated, please enter manually!");
        }
    }
    if ($out['warnings']) {
        writeMessageLine($bgcolor, 'infdetail', nl2br(rtrim($out['warnings'])));
    }
    // Simplify some claim attributes for cleaner code.
    $service_date = parse_date($out['dos']);
    $check_date = $paydate ? $paydate : parse_date($out['check_date']);
    $production_date = $paydate ? $paydate : parse_date($out['production_date']);
    if ($INTEGRATED_AR) {
        $insurance_id = 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'];
        }
    } else {
        $insurance_id = 0;
        foreach ($codes as $cdata) {
            if ($cdata['ins']) {
                $insurance_id = $cdata['ins'];
                break;
            }
        }
        $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) {
        // 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, $codekey, $bgcolor);
            // Check for sanity in amount charged.
            $prevchg = sprintf("%.2f", $prev['chg'] + $prev['adj']);
            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 {
            // 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) {
                if ($INTEGRATED_AR) {
                    arPostCharge($pid, $encounter, 0, $svc['chg'], 1, $service_date, $codekey, $description, $debug);
                } else {
                    slPostCharge($arrow['id'], $svc['chg'], 1, $service_date, $codekey, $insurance_id, $description, $debug);
                }
                $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']) {
            // A problem here is that some payers will include an adjustment
            // reflecting the allowed amount, others not.  So here we need to
            // check if the adjustment exists, and if not then create it.  We
            // assume that any nonzero CO (Contractual Obligation) or PI
            // (Payer Initiated) adjustment is good enough.
            $contract_adj = sprintf("%.2f", $svc['chg'] - $svc['allowed']);
            foreach ($svc['adj'] as $adj) {
                if (($adj['group_code'] == 'CO' || $adj['group_code'] == 'PI') && $adj['amount'] != 0) {
                    $contract_adj = 0;
                }
            }
            if ($contract_adj > 0) {
                $svc['adj'][] = array('group_code' => 'CO', 'reason_code' => 'A2', 'amount' => $contract_adj);
            }
            writeMessageLine($bgcolor, 'infdetail', 'Allowed amount is ' . sprintf("%.2f", $svc['allowed']));
        }
        // Report miscellaneous remarks.
        if ($svc['remark']) {
            $rmk = $svc['remark'];
            writeMessageLine($bgcolor, 'infdetail', "{$rmk}: " . $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) {
                if ($INTEGRATED_AR) {
                    $session_id = arGetSession($insurance_id, $out['check_number'], $check_date);
                    arPostPayment($pid, $encounter, $session_id, $svc['paid'], $codekey, substr($inslabel, 3), $out['check_number'], $debug);
                } else {
                    slPostPayment($arrow['id'], $svc['paid'], $check_date, "{$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, $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'] . ': ' . $adjustment_reasons[$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: ";
                    } else {
                        if ($adj['reason_code'] == '2') {
                            $reason = "{$inslabel} coins: ";
                        } else {
                            if ($adj['reason_code'] == '3') {
                                $reason = "{$inslabel} copay: ";
                            }
                        }
                    }
                } else {
                    $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) {
                    if ($INTEGRATED_AR) {
                        $session_id = arGetSession($insurance_id, $out['check_number'], $check_date);
                        arPostAdjustment($pid, $encounter, $session_id, 0, $codekey, substr($inslabel, 3), $reason, $debug);
                    } else {
                        slPostAdjustment($arrow['id'], 0, $production_date, $out['check_number'], $codekey, $insurance_id, $reason, $debug);
                    }
                }
                writeMessageLine($bgcolor, $class, $description . ' ' . sprintf("%.2f", $adj['amount']));
            } else {
                if (!$error && !$debug) {
                    if ($INTEGRATED_AR) {
                        $session_id = arGetSession($insurance_id, $out['check_number'], $check_date);
                        arPostAdjustment($pid, $encounter, $session_id, $adj['amount'], $codekey, substr($inslabel, 3), "Adjust code " . $adj['reason_code'], $debug);
                    } else {
                        slPostAdjustment($arrow['id'], $adj['amount'], $production_date, $out['check_number'], $codekey, $insurance_id, "{$inslabel} adjust code " . $adj['reason_code'], $debug);
                    }
                    $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) {
        if ($INTEGRATED_AR) {
            $level_done = 0 + substr($inslabel, 3);
            sqlStatement("UPDATE form_encounter " . "SET last_level_closed = {$level_done} WHERE " . "pid = '{$pid}' AND encounter = '{$encounter}'");
            // Check for secondary insurance.
            if ($primary && arGetPayerID($pid, $service_date, 2)) {
                arSetupSecondary($pid, $encounter, $debug);
                writeMessageLine($bgcolor, 'infdetail', 'This claim is now re-queued for secondary paper billing');
            }
        } else {
            $shipvia = 'Done: Ins1';
            if ($inslabel != 'Ins1') {
                $shipvia .= ',Ins2';
            }
            if ($inslabel == 'Ins3') {
                $shipvia .= ',Ins3';
            }
            $query = "UPDATE ar SET shipvia = '{$shipvia}' WHERE id = " . $arrow['id'];
            SLQuery($query);
            if ($sl_err) {
                die($sl_err);
            }
            // Check for secondary insurance.
            $insgot = strtolower($arrow['notes']);
            if ($primary && strpos($insgot, 'ins2') !== false) {
                slSetupSecondary($arrow['id'], $debug);
                writeMessageLine($bgcolor, 'infdetail', 'This claim is now re-queued for secondary paper billing');
            }
        }
    }
}
Example #2
0
    $form_deposit_date = fixDate($_POST['form_deposit_date'], $form_check_date);
    $form_pay_total = 0 + $_POST['form_pay_total'];
} else {
    slInitialize();
}
$payer_type = 0;
if (preg_match('/^Ins(\\d)/i', $_POST['form_insurance'], $matches)) {
    $payer_type = $matches[1];
}
if ($_POST['form_save'] || $_POST['form_cancel']) {
    if ($_POST['form_save']) {
        if ($debug) {
            echo xl("This module is in test mode. The database will not be changed.", '', '<p><b>', "</b><p>\n");
        }
        if ($INTEGRATED_AR) {
            $session_id = arGetSession($form_payer_id, $form_reference, $form_check_date, $form_deposit_date, $form_pay_total);
            // The sl_eob_search page needs its invoice links modified to invoke
            // javascript to load form parms for all the above and submit.
            // At the same time that page would be modified to work off the
            // openemr database exclusively.
            // And back to the sl_eob_invoice page, I think we may want to move
            // the source input fields from row level to header level.
        }
        $paytotal = 0;
        foreach ($_POST['form_line'] as $code => $cdata) {
            if (!$INTEGRATED_AR) {
                $thissrc = trim($cdata['src']);
                $thisdate = trim($cdata['date']);
            }
            $thispay = trim($cdata['pay']);
            $thisadj = trim($cdata['adj']);
Example #3
0
 }
 // end if charge
 $payer_id = empty($dtlinfo['ins']) ? 0 : $dtlinfo['ins'];
 $session_id = 0;
 // Compute a reasonable "source" value.  For payments this will
 // commonly be a check number, for adjustments we have none.
 $source = empty($dtlinfo['src']) ? '' : $dtlinfo['src'];
 $source = preg_replace('!^Ins[123]/!i', '', $source);
 $source = preg_replace('!^Pt/!i', '', $source);
 if ($source == '' && empty($dtlinfo['pmt'])) {
     $source = 'From SQL-Ledger';
 }
 // For insurance payers look up or create the session table entry.
 if ($payer_id) {
     if (!$dry_run) {
         $session_id = arGetSession($payer_id, addslashes($source), $dtldate);
     }
 } else {
     if ($code == '') {
         if (!empty($dtlinfo['pmt'])) {
             // Skip payments that are already present in the billing table as copays.
             foreach ($billing as $key => $brow) {
                 if ($brow['code_type'] == 'COPAY' && 0 - $brow['fee'] == $dtlinfo['pmt']) {
                     unset($billing[$key]);
                     continue 2;
                     // done with this detail item
                 }
             }
         }
         // end if payment
     }