graphic.
[openemr.git] / library / sl_eob.inc.php
blob4f1d06fa86fae64d305e2825cc9b5c9d9ae26d30
1 <?php
2 // Copyright (C) 2005-2006 Rod Roark <rod@sunsetsystems.com>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
9 include_once("sql-ledger.inc");
10 include_once("patient.inc");
11 include_once("billing.inc");
12 include_once("invoice_summary.inc.php");
14 $chart_id_cash = 0;
15 $chart_id_ar = 0;
16 $chart_id_income = 0;
17 $services_id = 0;
19 function slInitialize() {
20 global $chart_id_cash, $chart_id_ar, $chart_id_income, $services_id;
21 global $sl_cash_acc, $sl_ar_acc, $sl_income_acc, $sl_services_id;
22 SLConnect();
24 $chart_id_cash = SLQueryValue("select id from chart where accno = '$sl_cash_acc'");
25 if ($sl_err) die($sl_err);
26 if (! $chart_id_cash) die(xl("There is no COA entry for cash account ") . "'$sl_cash_acc'");
28 $chart_id_ar = SLQueryValue("select id from chart where accno = '$sl_ar_acc'");
29 if ($sl_err) die($sl_err);
30 if (! $chart_id_ar) die(xl("There is no COA entry for AR account ") . "'$sl_ar_acc'");
32 $chart_id_income = SLQueryValue("select id from chart where accno = '$sl_income_acc'");
33 if ($sl_err) die($sl_err);
34 if (! $chart_id_income) die(xl("There is no COA entry for income account ") . "'$sl_income_acc'");
36 $services_id = SLQueryValue("select id from parts where partnumber = '$sl_services_id'");
37 if ($sl_err) die($sl_err);
38 if (! $services_id) die(xl("There is no parts entry for services ID ") . "'$sl_services_id'");
41 function slTerminate() {
42 SLClose();
45 // Try to figure out our invoice number (pid.encounter) from the
46 // claim ID and other stuff in the ERA. This should be straightforward
47 // except that some payers mangle the claim ID that we give them.
49 function slInvoiceNumber(&$out) {
50 $invnumber = $out['our_claim_id'];
51 $atmp = preg_split('/[ -]/', $invnumber);
52 $acount = count($atmp);
54 $pid = 0;
55 $encounter = 0;
56 if ($acount == 2) {
57 $pid = $atmp[0];
58 $encounter = $atmp[1];
60 else if ($acount == 3) {
61 $pid = $atmp[0];
62 $brow = sqlQuery("SELECT encounter FROM billing WHERE " .
63 "pid = '$pid' AND id = '" . $atmp[1] . "' AND activity = 1");
64 $encounter = $brow['encounter'];
66 else if ($acount == 1) {
67 $pres = sqlStatement("SELECT pid FROM patient_data WHERE " .
68 "lname LIKE '" . addslashes($out['patient_lname']) . "' AND " .
69 "fname LIKE '" . addslashes($out['patient_fname']) . "' " .
70 "ORDER BY pid DESC");
71 while ($prow = sqlFetchArray($pres)) {
72 if (strpos($invnumber, $prow['pid']) === 0) {
73 $pid = $prow['pid'];
74 $encounter = substr($invnumber, strlen($pid));
75 break;
80 if ($pid && $encounter) $invnumber = "$pid.$encounter";
81 return array($pid, $encounter, $invnumber);
84 // Insert a row into the acc_trans table.
86 function slAddTransaction($invid, $chartid, $amount, $date, $source, $memo, $insplan, $debug) {
87 global $sl_err;
88 $date = fixDate($date);
89 $query = "INSERT INTO acc_trans ( " .
90 "trans_id, " .
91 "chart_id, " .
92 "amount, " .
93 "transdate, " .
94 "source, " .
95 "project_id, " .
96 "memo " .
97 ") VALUES ( " .
98 "$invid, " . // trans_id
99 "$chartid, " . // chart_id
100 "$amount, " . // amount
101 "'$date', " . // transdate
102 "'$source', " . // source
103 "$insplan, " . // project_id
104 "'$memo' " . // memo
105 ")";
106 if ($debug) {
107 echo $query . "<br>\n";
108 } else {
109 SLQuery($query);
110 if ($sl_err) die($sl_err);
114 // Insert a row into the invoice table.
116 function slAddLineItem($invid, $serialnumber, $amount, $insplan, $description, $debug) {
117 global $sl_err, $services_id;
118 $query = "INSERT INTO invoice ( " .
119 "trans_id, " .
120 "parts_id, " .
121 "description, " .
122 "qty, " .
123 "allocated, " .
124 "sellprice, " .
125 "fxsellprice, " .
126 "discount, " .
127 "unit, " .
128 "project_id, " .
129 "serialnumber " .
130 ") VALUES ( " .
131 "$invid, " . // trans_id
132 "$services_id, " . // parts_id
133 "'$description', " . // description
134 "1, " . // qty
135 "0, " . // allocated
136 "$amount, " . // sellprice
137 "$amount, " . // fxsellprice
138 "0, " . // discount
139 "'', " . // unit
140 "$insplan, " . // project_id
141 "'$serialnumber'" . // serialnumber
142 ")";
143 if ($debug) {
144 echo $query . "<br>\n";
145 } else {
146 SLQuery($query);
147 if ($sl_err) die($sl_err);
151 // Update totals and payment date in the invoice header. Dollar amounts are
152 // stored as double precision floats so we have to be careful about rounding.
154 function slUpdateAR($invid, $amount, $paid = 0, $paydate = "", $debug) {
155 global $sl_err;
156 $paydate = fixDate($paydate);
157 $query = "UPDATE ar SET amount = round(CAST (amount AS numeric) + $amount, 2), " .
158 "netamount = round(CAST (netamount AS numeric) + $amount, 2)";
159 if ($paid) $query .= ", paid = round(CAST (paid AS numeric) + $paid, 2), datepaid = '$paydate'";
160 $query .= " WHERE id = $invid";
161 if ($debug) {
162 echo $query . "<br>\n";
163 } else {
164 SLQuery($query);
165 if ($sl_err) die($sl_err);
169 function slPostPayment($trans_id, $thispay, $thisdate, $thissrc, $code, $thisins, $debug) {
170 global $chart_id_cash, $chart_id_ar;
171 // Post a payment: add to ar, subtract from cash.
172 slAddTransaction($trans_id, $chart_id_ar , $thispay , $thisdate, $thissrc, $code, $thisins, $debug);
173 slAddTransaction($trans_id, $chart_id_cash, 0 - $thispay, $thisdate, $thissrc, $code, $thisins, $debug);
174 slUpdateAR($trans_id, 0, $thispay, $thisdate, $debug);
177 function slPostCharge($trans_id, $thisamt, $thisdate, $code, $thisins, $description, $debug) {
178 global $chart_id_income, $chart_id_ar;
179 // Post an adjustment: add negative invoice item, add to ar, subtract from income
180 slAddLineItem($trans_id, $code, $thisamt, $thisins, $description, $debug);
181 if ($thisamt) {
182 slAddTransaction($trans_id, $chart_id_ar , 0 - $thisamt, $thisdate, $description, $code, $thisins, $debug);
183 slAddTransaction($trans_id, $chart_id_income, $thisamt , $thisdate, $description, $code, $thisins, $debug);
184 slUpdateAR($trans_id, $thisamt, 0, '', $debug);
188 function slPostAdjustment($trans_id, $thisadj, $thisdate, $thissrc, $code, $thisins, $reason, $debug) {
189 global $chart_id_income, $chart_id_ar;
190 // Post an adjustment: add negative invoice item, add to ar, subtract from income
191 $adjdate = fixDate($thisdate);
192 $description = "Adjustment $adjdate $reason";
193 slAddLineItem($trans_id, $code, 0 - $thisadj, $thisins, $description, $debug);
194 if ($thisadj) {
195 slAddTransaction($trans_id, $chart_id_ar, $thisadj, $thisdate, "InvAdj $thissrc", $code, $thisins, $debug);
196 slAddTransaction($trans_id, $chart_id_income, 0 - $thisadj, $thisdate, "InvAdj $thissrc", $code, $thisins, $debug);
197 slUpdateAR($trans_id, 0 - $thisadj, 0, '', $debug);
201 // Do whatever is necessary to make this invoice re-billable.
203 function slSetupSecondary($invid, $debug) {
204 global $sl_err, $GLOBALS;
205 $info_msg = '';
207 // Get some needed items from the SQL-Ledger invoice.
208 $arres = SLQuery("select invnumber, transdate, customer_id, employee_id, " .
209 "shipvia from ar where ar.id = $invid");
210 if ($sl_err) die($sl_err);
211 $arrow = SLGetRow($arres, 0);
212 if (! $arrow) die(xl('There is no match for invoice id') . ' = ' . "$trans_id.");
213 $customer_id = $arrow['customer_id'];
214 $date_of_service = $arrow['transdate'];
215 list($trash, $encounter) = explode(".", $arrow['invnumber']);
217 // Get the OpenEMR PID corresponding to the customer.
218 $pdrow = sqlQuery("SELECT patient_data.pid " .
219 "FROM integration_mapping, patient_data WHERE " .
220 "integration_mapping.foreign_id = $customer_id AND " .
221 "integration_mapping.foreign_table = 'customer' AND " .
222 "patient_data.id = integration_mapping.local_id");
223 $pid = $pdrow['pid'];
224 if (! $pid) die(xl("Cannot find patient from SQL-Ledger customer id") . " = $customer_id.");
226 // Determine the ID of the next insurance company (if any) to be billed.
227 $new_payer_id = -1;
228 $insdone = strtolower($arrow['shipvia']);
229 foreach (array('ins1' => 'primary', 'ins2' => 'secondary', 'ins3' => 'tertiary') as $key => $value) {
230 if (strpos($insdone, $key) === false) {
231 $nprow = sqlQuery("SELECT provider FROM insurance_data WHERE " .
232 "pid = '$pid' AND type = '$value' AND date <= '$date_of_service' " .
233 "ORDER BY date DESC LIMIT 1");
234 if (!empty($nprow['provider'])) {
235 $new_payer_id = $nprow['provider'];
237 break;
241 // Find out if the encounter exists.
242 $ferow = sqlQuery("SELECT pid FROM form_encounter WHERE " .
243 "encounter = $encounter");
244 $encounter_pid = $ferow['pid'];
246 // If it exists, just update the billing items.
247 if ($encounter_pid) {
248 if ($encounter_pid != $pid)
249 die(xl("Expected form_encounter.pid to be ") . $pid . ', ' . xl(' but was ') . $encounter_pid);
251 // If there's a payer ID queue it up, otherwise just reopen it.
252 if ($new_payer_id > 0) {
253 // TBD: implement a default bill_process and target in config.php,
254 // it should not really be hard-coded here.
255 /****
256 $query = "UPDATE billing SET billed = 0, bill_process = 5, " .
257 "target = 'hcfa', payer_id = $new_payer_id, " .
258 "bill_date = NOW(), process_date = NULL, process_file = NULL " .
259 "WHERE encounter = $encounter AND pid = $pid AND activity = 1";
260 ****/
261 if (!$debug)
262 updateClaim(true, $pid, $encounter, $new_payer_id, 1, 5, '', 'hcfa');
263 } else {
264 /****
265 $query = "UPDATE billing SET billed = 0, bill_process = 0, payer_id = -1, " .
266 "bill_date = NULL, process_date = NULL, process_file = NULL " .
267 "WHERE encounter = $encounter AND pid = $pid AND activity = 1";
268 ****/
269 if (!$debug)
270 updateClaim(true, $pid, $encounter, -1, 1, 0, '');
273 /****
274 if ($debug) {
275 echo $query . "<br>\n";
276 } else {
277 sqlQuery($query);
279 ****/
281 $info_msg = xl("Encounter ") . $encounter . xl(" is ready for re-billing.");
282 return;
285 // If we get here then the encounter does not already exist. This should
286 // only happen if A/R was converted from an earlier system. In this case
287 // the encounter ID should be the date of service, and we will create the
288 // encounter.
290 // If it does not exist then it better be (or start with) a date.
291 if (! preg_match("/^20\d\d\d\d\d\d/", $encounter))
292 die(xl("Internal error: encounter '") . $encounter . xl("' should exist but does not."));
294 $employee_id = $arrow['employee_id'];
296 // Get the OpenEMR provider info corresponding to the SQL-Ledger salesman.
297 $drrow = sqlQuery("SELECT users.id, users.username, users.facility_id " .
298 "FROM integration_mapping, users WHERE " .
299 "integration_mapping.foreign_id = $employee_id AND " .
300 "integration_mapping.foreign_table = 'salesman' AND " .
301 "users.id = integration_mapping.local_id");
302 $provider_id = $drrow['id'];
303 if (! $provider_id) die(xl("Cannot find provider from SQL-Ledger employee = ") . $employee_id );
305 if (! $date_of_service) die(xl("Invoice has no date!"));
307 // Generate a new encounter number.
308 $conn = $GLOBALS['adodb']['db'];
309 $new_encounter = $conn->GenID("sequences");
311 // Create the "new encounter".
312 $encounter_id = 0;
313 $query = "INSERT INTO form_encounter ( " .
314 "date, reason, facility_id, pid, encounter, onset_date " .
315 ") VALUES ( " .
316 "'$date_of_service', " .
317 "'" . xl('Imported from Accounting') . "', " .
318 "'" . addslashes($drrow['facility_id']) . "', " .
319 "$pid, " .
320 "$new_encounter, " .
321 "'$date_of_service' " .
322 ")";
323 if ($debug) {
324 echo $query . "<br>\n";
325 echo xl("Call to addForm() goes here.<br>") . "\n";
326 } else {
327 $encounter_id = idSqlStatement($query);
328 if (! $encounter_id) die(xl("Insert failed: ") . $query);
329 addForm($new_encounter, xl("New Patient Encounter"), $encounter_id,
330 "newpatient", $pid, 1, $date_of_service);
331 $info_msg = xl("Encounter ") . $new_encounter . xl(" has been created. ");
334 // For each invoice line item with a billing code we will insert
335 // a billing row with payer_id set to -1. Order the line items
336 // chronologically so that each procedure code will be followed by
337 // its associated icd9 code.
339 $inres = SLQuery("SELECT * FROM invoice WHERE trans_id = $invid " .
340 "ORDER BY id");
341 if ($sl_err) die($sl_err);
343 // When nonzero, this will be the ID of a billing row that needs to
344 // have its justify field set.
345 $proc_ins_id = 0;
347 for ($irow = 0; $irow < SLRowCount($inres); ++$irow) {
348 $row = SLGetRow($inres, $irow);
349 $amount = $row['sellprice'];
351 // Extract the billing code.
352 $code = xl("Unknown");
353 if (preg_match("/([A-Za-z0-9]\d\d\S*)/", $row['serialnumber'], $matches)) {
354 $code = strtoupper($matches[1]);
356 else if (preg_match("/([A-Za-z0-9]\d\d\S*)/", $row['description'], $matches)) {
357 $code = strtoupper($matches[1]);
360 list($code, $modifier) = explode("-", $code);
362 // Set the billing code type and description.
363 $code_type = "";
364 $code_text = "";
366 foreach ($code_types as $key => $value) {
367 if (preg_match("/$key/", $row['serialnumber'])) {
368 $code_type = $key;
369 if ($value['fee']) {
370 $code_text = xl("Procedure") . " $code";
371 } else {
372 $code_text = xl("Diagnosis") . " $code";
373 if ($proc_ins_id) {
374 $query = "UPDATE billing SET justify = '$code' WHERE id = $proc_ins_id";
375 if ($debug) {
376 echo $query . "<br>\n";
377 } else {
378 sqlQuery($query);
380 $proc_ins_id = 0;
383 break;
387 // Skip adjustments.
388 if (! $code_type) continue;
390 // Insert the billing item. If this for a procedure code then save
391 // the row ID so that we can update the "justify" field with the ICD9
392 // code, which should come next in the loop.
394 $query = "INSERT INTO billing ( " .
395 "date, code_type, code, pid, provider_id, user, groupname, authorized, " .
396 "encounter, code_text, activity, payer_id, billed, bill_process, " .
397 "bill_date, modifier, units, fee, justify, target " .
398 ") VALUES ( " .
399 "NOW(), " .
400 "'$code_type', " .
401 "'$code', " .
402 "$pid, " .
403 "$provider_id, " .
404 "'" . $_SESSION['authId'] . "', " .
405 "'" . $_SESSION['authProvider'] . "', " .
406 "1, " .
407 "$new_encounter, " .
408 "'$code_text', " .
409 "1, " .
410 "$new_payer_id, " .
411 ($new_payer_id > 0 ? "1, " : "0, ") .
412 ($new_payer_id > 0 ? "5, " : "0, ") .
413 ($new_payer_id > 0 ? "NOW(), " : "NULL, ") .
414 "'$modifier', " .
415 "0, " .
416 "$amount, " .
417 "'', " .
418 ($new_payer_id > 0 ? "'hcfa' " : "NULL ") .
419 ")";
420 if ($debug) {
421 echo $query . "<br>\n";
422 } else {
423 $proc_ins_id = idSqlStatement($query);
424 if ($code_type != "CPT4" && $code_type != "HCPCS")
425 $proc_ins_id = 0;
429 // Finally, change this invoice number to contain the new encounter number.
431 $new_invnumber = "$pid.$new_encounter";
432 $query = "UPDATE ar SET invnumber = '$new_invnumber' WHERE id = $invid";
433 if ($debug) {
434 echo $query . "<br>\n";
435 } else {
436 SLQuery($query);
437 if ($sl_err) die($sl_err);
438 $info_msg .= xl("This invoice number has been changed to ") . $new_invnumber;
441 return $info_msg;