added additional testing support for couchdb docker in dev environment
[openemr.git] / library / billing.inc
blob4429819557dc6656276cf06bd4dd89ad96906bce
1 <?php
4 function getBillingById($id, $cols = "*")
6     return sqlQuery("select $cols from billing where id='$id' and activity=1 order by date DESC limit 0,1");
9 function getBillingByPid($pid, $cols = "*")
11     return  sqlQuery("select $cols from billing where pid ='$pid' and activity=1 order by date DESC limit 0,1");
14 function getBillingByEncounter($pid, $encounter, $cols = "code_type, code, code_text")
16     $res = sqlStatement("select $cols from billing where encounter = ? and pid=? and activity=1 order by code_type, date ASC", array($encounter,$pid));
18     $all=array();
19     for ($iter=0; $row=sqlFetchArray($res); $iter++) {
20         $all[$iter] = $row;
21     }
23     return $all;
26 function addBilling(
27     $encounter_id,
28     $code_type,
29     $code,
30     $code_text,
31     $pid,
32     $authorized = "0",
33     $provider,
34     $modifier = "",
35     $units = "",
36     $fee = "0.00",
37     $ndc_info = '',
38     $justify = '',
39     $billed = 0,
40     $notecodes = '',
41     $pricelevel = '',
42     $revenue_code = ""
43 ) {
45     $sql = "insert into billing (date, encounter, code_type, code, code_text, " .
46     "pid, authorized, user, groupname, activity, billed, provider_id, " .
47     "modifier, units, fee, ndc_info, justify, notecodes, pricelevel, revenue_code) values (" .
48     "NOW(), ?, ?, ?, ?, ?, ?, ?, ?,  1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
49     return sqlInsert($sql, array($encounter_id,$code_type,$code,$code_text,$pid,$authorized,
50         $_SESSION['authId'],$_SESSION['authProvider'],$billed,$provider,$modifier,$units,$fee,
51     $ndc_info,$justify,$notecodes,$pricelevel,$revenue_code));
54 function authorizeBilling($id, $authorized = "1")
56     sqlQuery("update billing set authorized = '$authorized' where id = '$id'");
59 function deleteBilling($id)
61     sqlStatement("update billing set activity = 0 where id = '$id'");
64 function clearBilling($id)
66     sqlStatement("update billing set justify = '' where id = '$id'");
69 // This function supports the Billing page (billing_process.php),
70 // and initiation of secondary processing (sl_eob.inc.php).
71 // It is called in the following situations:
73 // * billing_process.php sets bill_time, bill_process, payer and target on
74 //   queueing a claim for processing.  Create claims row.
75 // * billing_process.php sets claim status to 2, and payer, on marking a
76 //   claim as billed without actually generating any billing.  Create a
77 //   claims row.  In this case bill_process will remain at 0 and process_time
78 //   and process_file will not be set.
79 // * billing_process.php sets bill_process, payer, target and x12 partner
80 //   before calling gen_x12_837.  Create a claims row.
81 // * billing_process.php sets claim status to 2 (billed), bill_process to 2,
82 //   process_time and process_file after calling gen_x12_837.  Claims row
83 //   already exists.
84 // * billing_process.php sets claim status to 2 (billed) after creating
85 //   an electronic batch (hcfa-only with recent changes).  Claims
86 //   row already exists.
87 // * EOB posting updates claim status to mark a payer as done.  Claims row
88 //   already exists.
89 // * EOB posting reopens an encounter for billing a secondary payer.  Create
90 //   a claims row.
92 // $newversion should be passed to us to indicate if a new claims row
93 // is to be generated, otherwise one must already exist.  The payer, if
94 // passed in for the latter case, must match the existing claim.
96 // Currently on the billing page the user can select any of the patient's
97 // payers.  That logic will tailor the payer choices to the encounter date.
99 function updateClaim(
100     $newversion,
101     $patient_id,
102     $encounter_id,
103     $payer_id = -1,
104     $payer_type = -1,
105     $status = -1,
106     $bill_process = -1,
107     $process_file = '',
108     $target = '',
109     $partner_id = -1,
110     $crossover = 0,
111     $submitted_claim = ''
112 ) {
114     $sqlBindArray = array();
115     if (!$newversion) {
116         $sql = "SELECT * FROM claims WHERE patient_id = ? AND " .
117         "encounter_id = ? AND status > 0 AND status < 4 ";
118         array_push($sqlBindArray, $patient_id, $encounter_id);
119         if ($payer_id >= 0) {
120             $sql .= "AND payer_id = ? ";
121             $sqlBindArray[] = $payer_id;
122         }
124         $sql .= "ORDER BY version DESC LIMIT 1";
125         $row = sqlQuery($sql, $sqlBindArray);
126         if (!$row) {
127             return 0;
128         }
130         if ($payer_id     < 0) {
131             $payer_id     = $row['payer_id'];
132         }
134         if ($status       < 0) {
135             $status       = $row['status'];
136         }
138         if ($bill_process < 0) {
139             $bill_process = $row['bill_process'];
140         }
142         if ($partner_id   < 0) {
143             $partner_id   = $row['x12_partner_id'];
144         }
146         if (!$process_file) {
147             $process_file = $row['process_file'];
148         }
150         if (!$target) {
151             $target       = $row['target'];
152         }
153     }
155     $claimset = "";
156     $sqlBindClaimset = array();
157     $billset = "";
158     $sqlBindBillset = array();
159     if (empty($payer_id) || $payer_id < 0) {
160         $payer_id = 0;
161     }
163     if ($status==7) {//$status==7 is the claim denial case.
164         $claimset .= ", status = ?";
165         $sqlBindClaimset[] = $status;
166     } elseif ($status >= 0) {
167         $claimset .= ", status = ?";
168         $sqlBindClaimset[] = $status;
169         if ($status > 1) {
170             $billset .= ", billed = 1";
171             if ($status == 2) {
172                 $billset  .= ", bill_date = NOW()";
173             }
174         } else {
175             $billset .= ", billed = 0";
176         }
177     }
179     if ($status==7) {//$status==7 is the claim denial case.
180         $billset  .= ", bill_process = ?";
181         $sqlBindBillset[] = $status;
182     } elseif ($bill_process >= 0) {
183         $claimset .= ", bill_process = ?";
184         $sqlBindClaimset[] = $bill_process;
185         $billset  .= ", bill_process = ?";
186         $sqlBindBillset[] = $bill_process;
187     }
189     if ($status==7) {//$status==7 is the claim denial case.
190         $claimset  .= ", process_file = ?";//Denial reason code is stored here
191         $sqlBindClaimset[] = $process_file;
192     } elseif ($process_file) {
193         $claimset .= ", process_file = ?, process_time = NOW()";
194         $sqlBindClaimset[] = $process_file;
195         $billset  .= ", process_file = ?, process_date = NOW()";
196         $sqlBindBillset[] = $process_file;
197     }
199     if ($target) {
200         $claimset .= ", target = ?";
201         $sqlBindClaimset[] = $target;
202         $billset  .= ", target = ?";
203         $sqlBindBillset[] = $target;
204     }
206     if ($payer_id >= 0) {
207         $claimset .= ", payer_id = ?, payer_type = ?";
208         $sqlBindClaimset[] = $payer_id;
209         $sqlBindClaimset[] = $payer_type;
210         $billset  .= ", payer_id = ?";
211         $sqlBindBillset[] = $payer_id;
212     }
214     if ($partner_id >= 0) {
215         $claimset .= ", x12_partner_id = ?";
216         $sqlBindClaimset[] = $partner_id;
217         $billset  .= ", x12_partner_id = ?";
218         $sqlBindBillset[] = $partner_id;
219     }
221     if ($billset) {
222         $billset = substr($billset, 2);
223         $sqlBindArray = $sqlBindBillset;
224         array_push($sqlBindArray, $encounter_id, $patient_id);
225         sqlStatement("UPDATE billing SET $billset WHERE " .
226         "encounter = ? AND pid= ? AND activity = 1", $sqlBindArray);
227     }
229     $claimset .= ", submitted_claim = ?";
230     $sqlBindClaimset[] = $submitted_claim;
231   // If a new claim version is requested, insert its row.
232   //
233     if ($newversion) {
234         /****
235         $payer_id = ($payer_id < 0) ? $row['payer_id'] : $payer_id;
236         $bill_process = ($bill_process < 0) ? $row['bill_process'] : $bill_process;
237         $process_file = ($process_file) ? $row['process_file'] : $process_file;
238         $target = ($target) ? $row['target'] : $target;
239         $partner_id = ($partner_id < 0) ? $row['x12_partner_id'] : $partner_id;
240         $sql = "INSERT INTO claims SET " .
241         "patient_id = '$patient_id', " .
242         "encounter_id = '$encounter_id', " .
243         "bill_time = UNIX_TIMESTAMP(NOW()), " .
244         "payer_id = '$payer_id', " .
245         "status = '$status', " .
246         "payer_type = '" . $row['payer_type'] . "', " .
247         "bill_process = '$bill_process', " .
248         "process_time = '" . $row['process_time'] . "', " .
249         "process_file = '$process_file', " .
250         "target = '$target', " .
251         "x12_partner_id = '$partner_id'";
252         ****/
253         sqlBeginTrans();
254         $version = sqlQuery("SELECT IFNULL(MAX(version),0) + 1 AS increment FROM claims WHERE patient_id = ? AND encounter_id = ?", array($patient_id, $encounter_id));
256         $sqlBindArray = array();
257         array_push($sqlBindArray, $patient_id, $encounter_id);
258         if ($crossover<>1) {
259             $sql .= "INSERT INTO claims SET " .
260               "patient_id = ?, " .
261               "encounter_id = ?, " .
262               "bill_time = NOW() $claimset ," .
263               "version = ?";
264             $sqlBindArray = array_merge($sqlBindArray, $sqlBindClaimset);
265             array_push($sqlBindArray, $version['increment']);
266         } else {//Claim automatic forward case.startTra
267             $sql= "INSERT INTO claims SET " .
268               "patient_id = ?, " .
269               "encounter_id = ?, " .
270               "bill_time = NOW(), status=? ," .
271               "version = ?";
272             array_push($sqlBindArray, $status, $version['increment']);
273         }
275         sqlStatement($sql, $sqlBindArray);
276         sqlCommitTrans();
277     } // Otherwise update the existing claim row.
278   //
279     else if ($claimset) {
280         $sqlBindArray = $sqlBindClaimset;
281         array_push($sqlBindArray, $patient_id, $encounter_id, $row['version']);
282         $claimset = substr($claimset, 2);
283         sqlStatement("UPDATE claims SET $claimset WHERE " .
284         "patient_id = ? AND encounter_id = ? AND " .
285         // "payer_id = '" . $row['payer_id'] . "' AND " .
286         "version = ?", $sqlBindArray);
287     }
289   // Whenever a claim is marked billed, update A/R accordingly.
290     if ($status == 2) {
291         if ($payer_type > 0) {
292             sqlStatement("UPDATE form_encounter SET " .
293             "last_level_billed = ? WHERE " .
294             "pid = ? AND encounter = ?", array($payer_type, $patient_id, $encounter_id));
295         }
296     }
298     return 1;
301 // Determine if the encounter is billed.  It is considered billed if it
302 // has at least one chargeable item, and all of them are billed.
304 function isEncounterBilled($pid, $encounter)
306     $billed = -1; // no chargeable services yet
308     $bres = sqlStatement(
309         "SELECT " .
310         "billing.billed FROM billing, code_types WHERE " .
311         "billing.pid = ? AND " .
312         "billing.encounter = ? AND " .
313         "billing.activity = 1 AND " .
314         "code_types.ct_key = billing.code_type AND " .
315         "code_types.ct_fee = 1 " .
316         "UNION " .
317         "SELECT billed FROM drug_sales WHERE " .
318         "pid = ? AND " .
319         "encounter = ?",
320         array($pid, $encounter, $pid, $encounter)
321     );
323     while ($brow = sqlFetchArray($bres)) {
324         if ($brow['billed'] == 0) {
325             $billed = 0;
326         } else {
327             if ($billed < 0) {
328                 $billed = 1;
329             }
330         }
331     }
333     return $billed > 0;
336 // Get the co-pay amount that is effective on the given date.
337 // Or if no insurance on that date, return -1.
339 function getCopay($patient_id, $encdate)
341     $tmp = sqlQuery("SELECT provider, copay FROM insurance_data " .
342     "WHERE pid = '$patient_id' AND type = 'primary' " .
343     "AND date <= '$encdate' ORDER BY date DESC LIMIT 1");
344     if ($tmp['provider']) {
345         return sprintf('%01.2f', 0 + $tmp['copay']);
346     }
348     return 0;
351 // Get the total co-pay amount paid by the patient for an encounter
352 function getPatientCopay($patient_id, $encounter)
354         $resMoneyGot = sqlStatement(
355             "SELECT sum(pay_amount) as PatientPay FROM ar_activity where ".
356             "pid = ? and encounter = ? and payer_type=0 and account_code='PCP'",
357             array($patient_id,$encounter)
358         );
359          //new fees screen copay gives account_code='PCP'
360         $rowMoneyGot = sqlFetchArray($resMoneyGot);
361         $Copay=$rowMoneyGot['PatientPay'];
362         return $Copay*-1;
365 // Get the "next invoice reference number" from this user's pool of reference numbers.
367 function getInvoiceRefNumber()
369     $trow = sqlQuery(
370         "SELECT lo.notes " .
371         "FROM users AS u, list_options AS lo " .
372         "WHERE u.username = ? AND " .
373         "lo.list_id = 'irnpool' AND lo.option_id = u.irnpool AND lo.activity = 1 LIMIT 1",
374         array($_SESSION['authUser'])
375     );
376     return empty($trow['notes']) ? '' : $trow['notes'];
379 // Increment the "next invoice reference number" of this user's pool.
380 // This identifies the "digits" portion of that number and adds 1 to it.
381 // If it contains more than one string of digits, the last is used.
383 function updateInvoiceRefNumber()
385     $irnumber = getInvoiceRefNumber();
386   // Here "?" specifies a minimal match, to get the most digits possible:
387     if (preg_match('/^(.*?)(\d+)(\D*)$/', $irnumber, $matches)) {
388         $newdigs = sprintf('%0' . strlen($matches[2]) . 'd', $matches[2] + 1);
389         $newnumber = $matches[1] . $newdigs . $matches[3];
390         sqlStatement(
391             "UPDATE users AS u, list_options AS lo " .
392             "SET lo.notes = ? WHERE " .
393             "u.username = ? AND " .
394             "lo.list_id = 'irnpool' AND lo.option_id = u.irnpool",
395             array($newnumber, $_SESSION['authUser'])
396         );
397     }
399     return $irnumber;
402 // Common function for voiding a receipt or checkout.  When voiding a checkout you can specify
403 // $time as a timestamp (yyyy-mm-dd hh:mm:ss) or 'all'; default is the last checkout.
405 function doVoid($patient_id, $encounter_id, $purge = false, $time = '')
407     $what_voided = $purge ? 'checkout' : 'receipt';
408     $date_original = '';
409     $adjustments = 0;
410     $payments = 0;
412     if (!$time) {
413         // Get last checkout timestamp.
414         $corow = sqlQuery(
415             "(SELECT bill_date FROM billing WHERE " .
416             "pid = ? AND encounter = ? AND activity = 1 AND bill_date IS NOT NULL) " .
417             "UNION " .
418             "(SELECT bill_date FROM drug_sales WHERE " .
419             "pid = ? AND encounter = ? AND bill_date IS NOT NULL) " .
420             "ORDER BY bill_date DESC LIMIT 1",
421             array($patient_id, $encounter_id, $patient_id, $encounter_id)
422         );
423         if (!empty($corow['bill_date'])) {
424               $date_original = $corow['bill_date'];
425         }
426     } else if ($time == 'all') {
427         $row = sqlQuery(
428             "SELECT SUM(pay_amount) AS payments, " .
429             "SUM(adj_amount) AS adjustments FROM ar_activity WHERE " .
430             "pid = ? AND encounter = ?",
431             array($patient_id, $encounter_id)
432         );
433         $adjustments = empty($row['adjustments']) ? 0 : $row['adjustments'];
434         $payments = empty($row['payments']) ? 0 : $row['payments'];
435     } else {
436         $date_original = $time;
437     }
439   // Get its charges and adjustments.
440     if ($date_original) {
441         $row = sqlQuery(
442             "SELECT SUM(pay_amount) AS payments, " .
443             "SUM(adj_amount) AS adjustments FROM ar_activity WHERE " .
444             "pid = ? AND encounter = ? AND post_time = ?",
445             array($patient_id, $encounter_id, $date_original)
446         );
447         $adjustments = empty($row['adjustments']) ? 0 : $row['adjustments'];
448         $payments = empty($row['payments']) ? 0 : $row['payments'];
449     }
451   // Get old invoice reference number.
452     $encrow = sqlQuery(
453         "SELECT invoice_refno FROM form_encounter WHERE " .
454         "pid = ? AND encounter = ? LIMIT 1",
455         array($patient_id, $encounter_id)
456     );
457     $old_invoice_refno = $encrow['invoice_refno'];
458   //
459     $usingirnpools = getInvoiceRefNumber();
460   // If not (undoing a checkout or using IRN pools), nothing is done.
461     if ($purge || $usingirnpools) {
462         $query = "INSERT INTO voids SET " .
463         "patient_id = ?, " .
464         "encounter_id = ?, " .
465         "what_voided = ?, " .
466         "date_voided = NOW(), " .
467         "user_id = ?, " .
468         "amount1 = ?, " .
469         "amount2 = ?, " .
470         "other_info = ?";
471         $sqlarr = array($patient_id, $encounter_id, $what_voided, $_SESSION['authUserID'], $adjustments,
472             $payments, $old_invoice_refno);
473         if ($date_original) {
474               $query .= ", date_original = ?";
475               $sqlarr[] = $date_original;
476         }
478         sqlStatement($query, $sqlarr);
479     }
481     if ($purge) {
482         // Purge means delete adjustments and payments from the last checkout
483         // and re-open the visit.
484         if ($date_original) {
485             sqlStatement(
486                 "DELETE FROM ar_activity WHERE " .
487                 "pid = ? AND encounter = ? AND post_time = ?",
488                 array($patient_id, $encounter_id, $date_original)
489             );
490             sqlStatement(
491                 "UPDATE billing SET billed = 0, bill_date = NULL WHERE " .
492                 "pid = ? AND encounter = ? AND activity = 1 AND " .
493                 "bill_date IS NOT NULL AND bill_date = ?",
494                 array($patient_id, $encounter_id, $date_original)
495             );
496             sqlStatement(
497                 "update drug_sales SET billed = 0, bill_date = NULL WHERE " .
498                 "pid = ? AND encounter = ? AND " .
499                 "bill_date IS NOT NULL AND bill_date = ?",
500                 array($patient_id, $encounter_id, $date_original)
501             );
502         } else {
503             if ($time == 'all') {
504                 sqlStatement(
505                     "DELETE FROM ar_activity WHERE " .
506                     "pid = ? AND encounter = ?",
507                     array($patient_id, $encounter_id)
508                 );
509             }
511             sqlStatement(
512                 "UPDATE billing SET billed = 0, bill_date = NULL WHERE " .
513                 "pid = ? AND encounter = ? AND activity = 1",
514                 array($patient_id, $encounter_id)
515             );
516             sqlStatement(
517                 "update drug_sales SET billed = 0, bill_date = NULL WHERE " .
518                 "pid = ? AND encounter = ?",
519                 array($patient_id, $encounter_id)
520             );
521         }
523         sqlStatement(
524             "UPDATE form_encounter SET last_level_billed = 0, " .
525             "last_level_closed = 0, stmt_count = 0, last_stmt_date = NULL " .
526             "WHERE pid = ? AND encounter = ?",
527             array($patient_id, $encounter_id)
528         );
529     } else if ($usingirnpools) {
530         // Non-purge means just assign a new invoice reference number.
531         $new_invoice_refno = add_escape_custom(updateInvoiceRefNumber());
532         sqlStatement(
533             "UPDATE form_encounter " .
534             "SET invoice_refno = ? " .
535             "WHERE pid = ? AND encounter = ?",
536             array($new_invoice_refno, $patient_id, $encounter_id)
537         );
538     }