function generate_receipt($patient_id, $encounter = 0) { global $sl_err, $sl_cash_acc, $css_header, $details, $INTEGRATED_AR; // Get details for what we guess is the primary facility. $frow = sqlQuery("SELECT * FROM facility " . "ORDER BY billing_location DESC, accepts_assignment DESC, id LIMIT 1"); $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code,providerID'); // Get the most recent invoice data or that for the specified encounter. // // Adding a provider check so that their info can be displayed on receipts if ($INTEGRATED_AR) { if ($encounter) { $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " . "WHERE pid = ? AND encounter = ?", array($patient_id, $encounter)); } else { $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " . "WHERE pid = ? " . "ORDER BY id DESC LIMIT 1", array($patient_id)); } if (empty($ferow)) { die(xlt("This patient has no activity.")); } $trans_id = $ferow['id']; $encounter = $ferow['encounter']; $svcdate = substr($ferow['date'], 0, 10); if ($GLOBALS['receipts_by_provider']) { if (isset($ferow['provider_id'])) { $encprovider = $ferow['provider_id']; } else { if (isset($patdata['providerID'])) { $encprovider = $patdata['providerID']; } else { $encprovider = -1; } } } if ($encprovider) { $providerrow = sqlQuery("SELECT fname, mname, lname, title, street, streetb, " . "city, state, zip, phone, fax FROM users WHERE id = ?", array($encprovider)); } } else { SLConnect(); // $arres = SLQuery("SELECT * FROM ar WHERE " . "invnumber LIKE '{$patient_id}.%' " . "ORDER BY id DESC LIMIT 1"); if ($sl_err) { die(text($sl_err)); } if (!SLRowCount($arres)) { die(xlt("This patient has no activity.")); } $arrow = SLGetRow($arres, 0); // $trans_id = $arrow['id']; // // Determine the date of service. An 8-digit encounter number is // presumed to be a date of service imported during conversion or // associated with prescriptions only. Otherwise look it up in the // form_encounter table. // $svcdate = ""; list($trash, $encounter) = explode(".", $arrow['invnumber']); if (strlen($encounter) >= 8) { $svcdate = substr($encounter, 0, 4) . "-" . substr($encounter, 4, 2) . "-" . substr($encounter, 6, 2); } else { if ($encounter) { $tmp = sqlQuery("SELECT date FROM form_encounter WHERE " . "encounter = ?", array($encounter)); $svcdate = substr($tmp['date'], 0, 10); } } } // end not $INTEGRATED_AR // Get invoice reference number. $encrow = sqlQuery("SELECT invoice_refno FROM form_encounter WHERE " . "pid = ? AND encounter = ? LIMIT 1", array($patient_id, $encounter)); $invoice_refno = $encrow['invoice_refno']; ?> <html> <head> <?php html_header_show(); ?> <link rel='stylesheet' href='<?php echo $css_header; ?> ' type='text/css'> <title><?php echo xlt('Receipt for Payment'); ?> </title> <script type="text/javascript" src="../../library/dialog.js"></script> <script language="JavaScript"> <?php require $GLOBALS['srcdir'] . "/restoreSession.php"; ?> // Process click on Print button. function printme() { var divstyle = document.getElementById('hideonprint').style; divstyle.display = 'none'; window.print(); return false; } // Process click on Delete button. function deleteme() { dlgopen('deleter.php?billing=<?php echo attr("{$patient_id}.{$encounter}"); ?> ', '_blank', 500, 450); return false; } // Called by the deleteme.php window on a successful delete. function imdeleted() { window.close(); } </script> </head> <body class="body_top"> <center> <?php if ($GLOBALS['receipts_by_provider'] && !empty($providerrow)) { printProviderHeader($providerrow); } else { printFacilityHeader($frow); } echo xlt("Receipt Generated") . ":" . text(date(' F j, Y')); if ($invoice_refno) { echo " " . xlt("Invoice Number") . ": " . text($invoice_refno) . " " . xlt("Service Date") . ": " . text($svcdate); } ?> <br> </b></p> </center> <p> <?php echo text($patdata['fname']) . ' ' . text($patdata['mname']) . ' ' . text($patdata['lname']); ?> <br><?php echo text($patdata['street']); ?> <br><?php echo text($patdata['city']) . ', ' . text($patdata['state']) . ' ' . text($patdata['postal_code']); ?> <br> </p> <center> <table cellpadding='5'> <tr> <td><b><?php echo xlt('Date'); ?> </b></td> <td><b><?php echo xlt('Description'); ?> </b></td> <td align='right'><b><?php echo $details ? xlt('Price') : ' '; ?> </b></td> <td align='right'><b><?php echo $details ? xlt('Qty') : ' '; ?> </b></td> <td align='right'><b><?php echo xlt('Total'); ?> </b></td> </tr> <?php $charges = 0.0; if ($INTEGRATED_AR) { // Product sales $inres = sqlStatement("SELECT s.sale_id, s.sale_date, s.fee, " . "s.quantity, s.drug_id, d.name " . "FROM drug_sales AS s LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " . "WHERE s.pid = ? AND s.encounter = ? " . "ORDER BY s.sale_id", array($patient_id, $encounter)); while ($inrow = sqlFetchArray($inres)) { $charges += sprintf('%01.2f', $inrow['fee']); receiptDetailLine($inrow['sale_date'], $inrow['name'], $inrow['fee'], $inrow['quantity']); } // Service and tax items $inres = sqlStatement("SELECT * FROM billing WHERE " . "pid = ? AND encounter = ? AND " . "code_type != 'COPAY' AND activity = 1 " . "ORDER BY id", array($patient_id, $encounter)); while ($inrow = sqlFetchArray($inres)) { $charges += sprintf('%01.2f', $inrow['fee']); receiptDetailLine($svcdate, $inrow['code_text'], $inrow['fee'], $inrow['units']); } // Adjustments. $inres = sqlStatement("SELECT " . "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " . "s.payer_id, s.reference, s.check_date, s.deposit_date " . "FROM ar_activity AS a " . "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " . "a.pid = ? AND a.encounter = ? AND " . "a.adj_amount != 0 " . "ORDER BY s.check_date, a.sequence_no", array($patient_id, $encounter)); while ($inrow = sqlFetchArray($inres)) { $charges -= sprintf('%01.2f', $inrow['adj_amount']); $payer = empty($inrow['payer_type']) ? 'Pt' : 'Ins' . $inrow['payer_type']; receiptDetailLine($svcdate, $payer . ' ' . $inrow['memo'], 0 - $inrow['adj_amount'], 1); } } else { // Request all line items with money belonging to the invoice. $inres = SLQuery("SELECT * FROM invoice WHERE " . "trans_id = {$trans_id} AND sellprice != 0 ORDER BY id"); if ($sl_err) { die($sl_err); } for ($irow = 0; $irow < SLRowCount($inres); ++$irow) { $row = SLGetRow($inres, $irow); $amount = sprintf('%01.2f', $row['sellprice'] * $row['qty']); $charges += $amount; $desc = preg_replace('/^.{1,6}:/', '', $row['description']); receiptDetailLine($svcdate, $desc, $amount, $row['qty']); } } // end not $INTEGRATED_AR ?> <tr> <td colspan='5'> </td> </tr> <tr> <td><?php echo text(oeFormatShortDate($svcdispdate)); ?> </td> <td><b><?php echo xlt('Total Charges'); ?> </b></td> <td align='right'> </td> <td align='right'> </td> <td align='right'><?php echo text(oeFormatMoney($charges, true)); ?> </td> </tr> <tr> <td colspan='5'> </td> </tr> <?php if ($INTEGRATED_AR) { // Get co-pays. $inres = sqlStatement("SELECT fee, code_text FROM billing WHERE " . "pid = ? AND encounter = ? AND " . "code_type = 'COPAY' AND activity = 1 AND fee != 0 " . "ORDER BY id", array($patient_id, $encounter)); while ($inrow = sqlFetchArray($inres)) { $charges += sprintf('%01.2f', $inrow['fee']); receiptPaymentLine($svcdate, 0 - $inrow['fee'], $inrow['code_text']); } // Get other payments. $inres = sqlStatement("SELECT " . "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " . "s.payer_id, s.reference, s.check_date, s.deposit_date " . "FROM ar_activity AS a " . "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " . "a.pid = ? AND a.encounter = ? AND " . "a.pay_amount != 0 " . "ORDER BY s.check_date, a.sequence_no", array($patient_id, $encounter)); $payer = empty($inrow['payer_type']) ? 'Pt' : 'Ins' . $inrow['payer_type']; while ($inrow = sqlFetchArray($inres)) { $charges -= sprintf('%01.2f', $inrow['pay_amount']); receiptPaymentLine($svcdate, $inrow['pay_amount'], $payer . ' ' . $inrow['reference']); } } else { $chart_id_cash = SLQueryValue("select id from chart where accno = '{$sl_cash_acc}'"); if ($sl_err) { die($sl_err); } if (!$chart_id_cash) { die("There is no COA entry for cash account '{$sl_cash_acc}'"); } // // Request all cash entries belonging to the invoice. $atres = SLQuery("SELECT * FROM acc_trans WHERE " . "trans_id = {$trans_id} AND chart_id = {$chart_id_cash} ORDER BY transdate"); if ($sl_err) { die($sl_err); } // for ($irow = 0; $irow < SLRowCount($atres); ++$irow) { $row = SLGetRow($atres, $irow); $amount = sprintf('%01.2f', $row['amount']); // negative $charges += $amount; $rowsource = $row['source']; if (strtolower($rowsource) == 'co-pay') { $rowsource = ''; } receiptPaymentLine($row['transdate'], 0 - $amount, $rowsource); } } // end not $INTEGRATED_AR ?> <tr> <td colspan='5'> </td> </tr> <tr> <td> </td> <td><b><?php echo xlt('Balance Due'); ?> </b></td> <td colspan='2'> </td> <td align='right'><?php echo text(oeFormatMoney($charges, true)); ?> </td> </tr> </table> </center> <div id='hideonprint'> <p> <a href='#' onclick='return printme();'><?php echo xlt('Print'); ?> </a> <?php if (acl_check('acct', 'disc')) { ?> <a href='#' onclick='return deleteme();'><?php echo xlt('Undo Checkout'); ?> </a> <?php } ?> <?php if ($details) { ?> <a href='pos_checkout.php?details=0&ptid=<?php echo attr($patient_id); ?> &enc=<?php echo attr($encounter); ?> '><?php echo xlt('Hide Details'); ?> </a> <?php } else { ?> <a href='pos_checkout.php?details=1&ptid=<?php echo attr($patient_id); ?> &enc=<?php echo attr($encounter); ?> '><?php echo xlt('Show Details'); ?> </a> <?php } ?> </p> </div> </body> </html> <?php if (!$INTEGRATED_AR) { SLClose(); } }
function responsible_party($trans_id) { global $sl_err; $arres = SLQuery("select * from ar where id = {$trans_id}"); if ($sl_err) { die($sl_err); } $arrow = SLGetRow($arres, 0); if (!$arrow) { die(xl("There is no match for invoice id = ") . $trans_id); } if ($arrow['paid'] >= $arrow['netamount']) { return -1; } $insgot = strtolower($arrow['notes']); $insdone = strtolower($arrow['shipvia']); for ($i = 1; $i <= 3; ++$i) { $lcvalue = "ins{$i}"; if (strpos($insgot, $lcvalue) !== false && strpos($insdone, $lcvalue) === false) { return $i; } } return 0; }
// echo "<!-- $query -->\n"; // debugging $t_res = SLQuery($query); if ($sl_err) { die($sl_err); } $docname = ""; $docnameleft = ""; $main_docid = 0; $doctotal1 = 0; $grandtotal1 = 0; $doctotal2 = 0; $grandtotal2 = 0; $last_trans_id = 0; $skipping = false; for ($irow = 0; $irow < SLRowCount($t_res); ++$irow) { $row = SLGetRow($t_res, $irow); list($patient_id, $encounter_id) = explode(".", $row['invnumber']); // Under some conditions we may skip invoices that matched the SQL query. // if ($row['trans_id'] == $last_trans_id) { if ($skipping) { continue; } // same invoice and not skipping, do nothing. } else { // new invoice $skipping = false; // If a diagnosis code was given then skip any invoices without // that diagnosis. if ($form_icdcode) { if (!SLQueryValue("SELECT count(*) FROM invoice WHERE " . "invoice.trans_id = '" . $row['trans_id'] . "' AND " . "( invoice.description ILIKE 'ICD9:{$form_icdcode} %' OR " . "invoice.serialnumber ILIKE 'ICD9:{$form_icdcode}' )")) {
function era_callback(&$out) { global $encount, $debug, $claim_status_codes, $adjustment_reasons, $remark_codes; global $invoice_total, $last_code, $paydate, $INTEGRATED_AR; global $InsertionId; //last inserted ID of // Some heading information. $chk_123 = $out['check_number']; $chk_123 = str_replace(' ', '_', $chk_123); if (isset($_REQUEST['chk' . $chk_123])) { if ($encount == 0) { writeMessageLine('#ffffff', 'infdetail', "Payer: " . htmlspecialchars($out['payer_name'], ENT_QUOTES)); 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') { //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. 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!"); } 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]; $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']); 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, '', $codetype); } 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) { arPostPayment($pid, $encounter, $InsertionId[$out['check_number']], $svc['paid'], $codekey, substr($inslabel, 3), $out['check_number'], $debug, '', $codetype); } 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) { arPostAdjustment($pid, $encounter, $InsertionId[$out['check_number']], 0, $codekey, substr($inslabel, 3), $reason, $debug, '', $codetype); } 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) { arPostAdjustment($pid, $encounter, $InsertionId[$out['check_number']], $adj['amount'], $codekey, substr($inslabel, 3), "Adjust code " . $adj['reason_code'], $debug, '', $codetype); } 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); 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 = {$level_done},last_level_billed=" . $level_done . " WHERE " . "pid = '{$pid}' AND encounter = '{$encounter}'"); writeMessageLine($bgcolor, 'infdetail', 'This claim is processed by Insurance ' . $level_done . ' and automatically forwarded to Insurance ' . ($level_done + 1) . ' for processing. '); } else { "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, $out['crossover']); if ($out['crossover'] != 1) { 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); if ($out['crossover'] != 1) { writeMessageLine($bgcolor, 'infdetail', 'This claim is now re-queued for secondary paper billing'); } } } } } }
$duept = $brow['amount'] + $srow['amount'] - $drow['payments'] - $drow['adjustments']; } echoLine("form_upay[{$enc}]", $dispdate, $value['charges'], $dpayment_pat, $dpayment + $dadjustment, $duept, $enc, $inscopay, $patcopay); } // Now list previously billed visits. if ($INTEGRATED_AR) { } else { // Query for all open invoices. $query = "SELECT ar.id, ar.invnumber, ar.amount, ar.paid, " . "ar.intnotes, ar.notes, ar.shipvia, " . "(SELECT SUM(invoice.sellprice * invoice.qty) FROM invoice WHERE " . "invoice.trans_id = ar.id AND invoice.sellprice > 0) AS charges, " . "(SELECT SUM(invoice.sellprice * invoice.qty) FROM invoice WHERE " . "invoice.trans_id = ar.id AND invoice.sellprice < 0) AS adjustments, " . "(SELECT SUM(acc_trans.amount) FROM acc_trans WHERE " . "acc_trans.trans_id = ar.id AND acc_trans.chart_id = ? " . "AND acc_trans.source NOT LIKE 'Ins%') AS ptpayments " . "FROM ar WHERE ar.invnumber LIKE ? AND " . "ar.amount != ar.paid " . "ORDER BY ar.invnumber"; $ires = SLQuery($query, array($chart_id_cash, $pid . "%")); if ($sl_err) { die($sl_err); } $num_invoices = SLRowCount($ires); for ($ix = 0; $ix < $num_invoices; ++$ix) { $irow = SLGetRow($ires, $ix); // Get encounter ID and date of service. list($patient_id, $enc) = explode(".", $irow['invnumber']); $tmp = sqlQuery("SELECT LEFT(date, 10) AS encdate FROM form_encounter " . "WHERE encounter = ?", array($enc)); $svcdate = $tmp['encdate']; // Compute $duncount as in sl_eob_search.php to determine if // this invoice is at patient responsibility. $duncount = substr_count(strtolower($irow['intnotes']), "statement sent"); if (!$duncount) { $insgot = strtolower($irow['notes']); $inseobs = strtolower($irow['shipvia']); foreach (array('ins1', 'ins2', 'ins3') as $value) { if (strpos($insgot, $value) !== false && strpos($inseobs, $value) === false) { --$duncount; } }
function slSetupSecondary($invid, $debug) { global $sl_err, $GLOBALS; if ($GLOBALS['oer_config']['ws_accounting']['enabled'] === 2) { die("Internal error calling slSetupSecondary()"); } $info_msg = ''; // Get some needed items from the SQL-Ledger invoice. $arres = SLQuery("select invnumber, transdate, customer_id, employee_id, " . "shipvia from ar where ar.id = {$invid}"); if ($sl_err) { die($sl_err); } $arrow = SLGetRow($arres, 0); if (!$arrow) { die(xl('There is no match for invoice id') . ' = ' . "{$trans_id}."); } $customer_id = $arrow['customer_id']; $date_of_service = $arrow['transdate']; list($trash, $encounter) = explode(".", $arrow['invnumber']); // Get the OpenEMR PID corresponding to the customer. $pdrow = sqlQuery("SELECT patient_data.pid " . "FROM integration_mapping, patient_data WHERE " . "integration_mapping.foreign_id = {$customer_id} AND " . "integration_mapping.foreign_table = 'customer' AND " . "patient_data.id = integration_mapping.local_id"); $pid = $pdrow['pid']; if (!$pid) { die(xl("Cannot find patient from SQL-Ledger customer id") . " = {$customer_id}."); } // Determine the ID of the next insurance company (if any) to be billed. $new_payer_id = -1; $new_payer_type = -1; $insdone = strtolower($arrow['shipvia']); foreach (array('ins1' => 'primary', 'ins2' => 'secondary', 'ins3' => 'tertiary') as $key => $value) { if (strpos($insdone, $key) === false) { $nprow = sqlQuery("SELECT provider FROM insurance_data WHERE " . "pid = '{$pid}' AND type = '{$value}' AND date <= '{$date_of_service}' " . "ORDER BY date DESC LIMIT 1"); if (!empty($nprow['provider'])) { $new_payer_id = $nprow['provider']; $new_payer_type = substr($key, 3); } break; } } // Find out if the encounter exists. $ferow = sqlQuery("SELECT pid FROM form_encounter WHERE " . "encounter = {$encounter}"); $encounter_pid = $ferow['pid']; // If it exists, just update the billing items. if ($encounter_pid) { if ($encounter_pid != $pid) { die(xl("Expected form_encounter.pid to be ") . $pid . ', ' . xl(' but was ') . $encounter_pid); } // If there's a payer ID queue it up, otherwise just reopen it. if ($new_payer_id > 0) { // TBD: implement a default bill_process and target in config.php, // it should not really be hard-coded here. if (!$debug) { updateClaim(true, $pid, $encounter, $new_payer_id, $new_payer_type, 1, 5, '', 'hcfa'); } } else { if (!$debug) { updateClaim(true, $pid, $encounter, -1, -1, 1, 0, ''); } } $info_msg = xl("Encounter ") . $encounter . xl(" is ready for re-billing."); return; } // If we get here then the encounter does not already exist. This should // only happen if A/R was converted from an earlier system. In this case // the encounter ID should be the date of service, and we will create the // encounter. // If it does not exist then it better be (or start with) a date. if (!preg_match("/^20\\d\\d\\d\\d\\d\\d/", $encounter)) { die(xl("Internal error: encounter '") . $encounter . xl("' should exist but does not.")); } $employee_id = $arrow['employee_id']; // Get the OpenEMR provider info corresponding to the SQL-Ledger salesman. $drrow = sqlQuery("SELECT users.id, users.username, users.facility_id " . "FROM integration_mapping, users WHERE " . "integration_mapping.foreign_id = {$employee_id} AND " . "integration_mapping.foreign_table = 'salesman' AND " . "users.id = integration_mapping.local_id"); $provider_id = $drrow['id']; if (!$provider_id) { die(xl("Cannot find provider from SQL-Ledger employee = ") . $employee_id); } if (!$date_of_service) { die(xl("Invoice has no date!")); } // Generate a new encounter number. $conn = $GLOBALS['adodb']['db']; $new_encounter = $conn->GenID("sequences"); // Create the "new encounter". $encounter_id = 0; $query = "INSERT INTO form_encounter ( " . "date, reason, facility_id, pid, encounter, onset_date, provider_id " . ") VALUES ( " . "'{$date_of_service}', " . "'" . xl('Imported from Accounting') . "', " . "'" . addslashes($drrow['facility_id']) . "', " . "{$pid}, " . "{$new_encounter}, " . "'{$date_of_service}', " . "'{$provider_id}' " . ")"; if ($debug) { echo $query . "<br>\n"; echo xl("Call to addForm() goes here.<br>") . "\n"; } else { $encounter_id = idSqlStatement($query); if (!$encounter_id) { die(xl("Insert failed: ") . $query); } addForm($new_encounter, xl("New Patient Encounter"), $encounter_id, "newpatient", $pid, 1, $date_of_service); $info_msg = xl("Encounter ") . $new_encounter . xl(" has been created. "); } // For each invoice line item with a billing code we will insert // a billing row with payer_id set to -1. Order the line items // chronologically so that each procedure code will be followed by // its associated icd9 code. $inres = SLQuery("SELECT * FROM invoice WHERE trans_id = {$invid} " . "ORDER BY id"); if ($sl_err) { die($sl_err); } // When nonzero, this will be the ID of a billing row that needs to // have its justify field set. $proc_ins_id = 0; for ($irow = 0; $irow < SLRowCount($inres); ++$irow) { $row = SLGetRow($inres, $irow); $amount = sprintf('%01.2f', $row['sellprice'] * $row['qty']); // Extract the billing code. $code = xl("Unknown"); if (preg_match("/([A-Za-z0-9]\\d\\d\\S*)/", $row['serialnumber'], $matches)) { $code = strtoupper($matches[1]); } else { if (preg_match("/([A-Za-z0-9]\\d\\d\\S*)/", $row['description'], $matches)) { $code = strtoupper($matches[1]); } } list($code, $modifier) = explode("-", $code); // Set the billing code type and description. $code_type = ""; $code_text = ""; foreach ($code_types as $key => $value) { if (preg_match("/{$key}/", $row['serialnumber'])) { $code_type = $key; if ($value['fee']) { $code_text = xl("Procedure") . " {$code}"; } else { $code_text = xl("Diagnosis") . " {$code}"; if ($proc_ins_id) { $query = "UPDATE billing SET justify = '{$code}' WHERE id = {$proc_ins_id}"; if ($debug) { echo $query . "<br>\n"; } else { sqlQuery($query); } $proc_ins_id = 0; } } break; } } // Skip adjustments. if (!$code_type) { continue; } // Insert the billing item. If this for a procedure code then save // the row ID so that we can update the "justify" field with the ICD9 // code, which should come next in the loop. // $query = "INSERT INTO billing ( " . "date, code_type, code, pid, provider_id, user, groupname, authorized, " . "encounter, code_text, activity, payer_id, billed, bill_process, " . "bill_date, modifier, units, fee, justify, target " . ") VALUES ( " . "NOW(), " . "'{$code_type}', " . "'{$code}', " . "{$pid}, " . "0, " . "'" . $_SESSION['authId'] . "', " . "'" . $_SESSION['authProvider'] . "', " . "1, " . "{$new_encounter}, " . "'{$code_text}', " . "1, " . "{$new_payer_id}, " . ($new_payer_id > 0 ? "1, " : "0, ") . ($new_payer_id > 0 ? "5, " : "0, ") . ($new_payer_id > 0 ? "NOW(), " : "NULL, ") . "'{$modifier}', " . "0, " . "{$amount}, " . "'', " . ($new_payer_id > 0 ? "'hcfa' " : "NULL ") . ")"; if ($debug) { echo $query . "<br>\n"; } else { $proc_ins_id = idSqlStatement($query); if ($code_type != "CPT4" && $code_type != "HCPCS") { $proc_ins_id = 0; } } } // Finally, change this invoice number to contain the new encounter number. // $new_invnumber = "{$pid}.{$new_encounter}"; $query = "UPDATE ar SET invnumber = '{$new_invnumber}' WHERE id = {$invid}"; if ($debug) { echo $query . "<br>\n"; } else { SLQuery($query); if ($sl_err) { die($sl_err); } $info_msg .= xl("This invoice number has been changed to ") . $new_invnumber; } return $info_msg; }
$ferow = sqlQuery("SELECT e.*, p.fname, p.mname, p.lname " . "FROM form_encounter AS e, patient_data AS p WHERE " . "e.pid = '$patient_id' AND e.encounter = '$encounter_id' AND ". "p.pid = e.pid"); if (empty($ferow)) die("There is no encounter $patient_id.$encounter_id."); $svcdate = substr($ferow['date'], 0, 10); *****************************************************************/ // Get invoice charge details. $codes = ar_get_invoice_summary($patient_id, $encounter_id, true); } else { // Get invoice data into $arrow. $arres = SLQuery("select ar.*, customer.name, employee.name as doctor " . "from ar, customer, employee where ar.id = {$trans_id} and " . "customer.id = ar.customer_id and employee.id = ar.employee_id"); if ($sl_err) { die($sl_err); } $arrow = SLGetRow($arres, 0); if (!$arrow) { die(xl("There is no match for invoice id = ") . $trans_id); } // // Determine the date of service. An 8-digit encounter number is // presumed to be a date of service imported during conversion. // Otherwise look it up in the form_encounter table. // $svcdate = ""; list($patient_id, $encounter) = explode(".", $arrow['invnumber']); if (strlen($encounter) == 8) { $svcdate = substr($encounter, 0, 4) . "-" . substr($encounter, 4, 2) . "-" . substr($encounter, 6, 2); } else { if ($encounter) { $tmp = sqlQuery("SELECT date FROM form_encounter WHERE " . "encounter = {$encounter}");
function loadPayerInfo(&$billrow) { global $sl_err; $encounter_date = substr($this->encounter['date'], 0, 10); // 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(); $query = "SELECT * FROM insurance_data WHERE " . "pid = '{$this->pid}' AND " . "date <= '{$encounter_date}' " . "ORDER BY type ASC, date DESC"; $dres = sqlStatement($query); $prevtype = ''; while ($drow = sqlFetchArray($dres)) { if (strcmp($prevtype, $drow['type']) == 0) { continue; } $prevtype = $drow['type']; // Very important to look at entries with a missing provider because // they indicate no insurance as of the given date. if (empty($drow['provider'])) { continue; } $ins = count($this->payers); if ($drow['provider'] == $billrow['payer_id'] && empty($this->payers[0]['data'])) { $ins = 0; } $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 kludge hands most cases of a rare ambiguous situation, where // the primary insurance company is the same as the secondary. It seems // nobody planned for that! // for ($i = 1; $i < count($this->payers); ++$i) { if ($billrow['process_date'] && $this->payers[0]['data']['provider'] == $this->payers[$i]['data']['provider']) { $tmp = $this->payers[0]; $this->payers[0] = $this->payers[$i]; $this->payers[$i] = $tmp; } } $this->using_modifiers = true; // Get payment and adjustment details if there are any previous payers. // $this->invoice = array(); if ($this->payerSequence() != 'P') { if ($GLOBALS['oer_config']['ws_accounting']['enabled'] === 2) { $this->invoice = ar_get_invoice_summary($this->pid, $this->encounter_id, true); } else { if ($GLOBALS['oer_config']['ws_accounting']['enabled']) { 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; } } } }
$arow = sqlQuery("SELECT SUM(fee) AS amount FROM billing WHERE " . "pid = '{$patient_id}' AND encounter = '{$encounter_id}' AND " . "activity = 1 AND code_type != 'COPAY'"); $inv_amount += $arow['amount']; $arow = sqlQuery("SELECT SUM(fee) AS amount FROM billing WHERE " . "pid = '{$patient_id}' AND encounter = '{$encounter_id}' AND " . "activity = 1 AND code_type = 'COPAY'"); $inv_paid = 0 - $arow['amount']; $arow = sqlQuery("SELECT SUM(pay_amount) AS pay, " . "sum(adj_amount) AS adj FROM ar_activity WHERE " . "pid = '{$patient_id}' AND encounter = '{$encounter_id}'"); $inv_paid += $arow['pay']; $inv_amount -= $arow['adj']; } else { $ares = SLQuery("SELECT duedate, amount, paid FROM ar WHERE " . "ar.invnumber = '{$invnumber}'"); if ($sl_err) { die($sl_err); } if (SLRowCount($ares) == 0) { continue; } $arow = SLGetRow($ares, 0); $inv_amount = $arow['amount']; $inv_paid = $arow['paid']; $inv_duedate = $arow['duedate']; } $total_amount += bucks($inv_amount); $total_paid += bucks($inv_paid); $bgcolor = $irow & 1 ? "#ffdddd" : "#ddddff"; ?> <tr bgcolor='<?php echo $bgcolor; ?> '> <td class="detail"> <?php echo $row['lname'] . ', ' . $row['fname'] . ' ' . $row['mname'];
<br><br> <span class='text'> <?php SLConnect(); echo "<p>Be patient, this will take a while...</p>"; flush(); // This marker will eventually tell us which encounters have no // matching invoice. if (!$dry_run) { sqlStatement("UPDATE form_encounter SET last_level_billed = -1"); } $invoice_count = 0; $activity_count = 0; $res = SLQuery("SELECT id, invnumber, transdate, shipvia, intnotes " . "FROM ar WHERE invnumber LIKE '%.%' ORDER BY id"); for ($irow = 0; $irow < SLRowCount($res); ++$irow) { $row = SLGetRow($res, $irow); list($pid, $encounter) = explode(".", $row['invnumber']); $tmp = sqlQuery("SELECT count(*) AS count FROM form_encounter WHERE " . "pid = '{$pid}' AND encounter = '{$encounter}'"); if ($tmp['count'] == 0) { echo "SQL-Ledger invoice {$pid}.{$encounter} has no matching encounter " . "and is ignored. This will affect financial reports!<br />\n"; continue; } $billing = array(); $provider_id = 0; $last_biller = 0; $svcdate = $row['transdate']; if (!$dry_run) { // Delete any TAX rows from billing for encounters in SQL-Ledger. sqlStatement("UPDATE billing SET activity = 0 WHERE " . "pid = '{$pid}' AND encounter = '{$encounter}' AND " . "code_type = 'TAX'"); } // Get all billing table items with money for this encounter, and