Practices gui - bootstrap light, no pure php in smarty, html escaping in smarty ...
[openemr.git] / library / billing.inc
blobbf49a45de4726b723569a20b5550e78b08a294b7
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     {
21         $all[$iter] = $row;
22     }
23     return $all;
26 function addBilling($encounter_id, $code_type, $code, $code_text, $pid,
27   $authorized="0", $provider, $modifier="", $units="", $fee="0.00",
28   $ndc_info='', $justify='', $billed=0, $notecodes='', $pricelevel='')
30   $sql = "insert into billing (date, encounter, code_type, code, code_text, " .
31     "pid, authorized, user, groupname, activity, billed, provider_id, " .
32     "modifier, units, fee, ndc_info, justify, notecodes, pricelevel) values (" .
33     "NOW(), ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
34   return sqlInsert($sql, array($encounter_id,$code_type,$code,$code_text,$pid,$authorized,
35     $_SESSION['authId'],$_SESSION['authProvider'],$billed,$provider,$modifier,$units,$fee,
36     $ndc_info,$justify,$notecodes,$pricelevel));
39 function authorizeBilling($id, $authorized = "1")
41     sqlQuery("update billing set authorized = '$authorized' where id = '$id'");
44 function deleteBilling($id)
46     sqlStatement("update billing set activity = 0 where id = '$id'");
49 function clearBilling($id)
51     sqlStatement("update billing set justify = '' where id = '$id'");
54 // This function supports the Billing page (billing_process.php),
55 // and initiation of secondary processing (sl_eob.inc.php). 
56 // It is called in the following situations:
58 // * billing_process.php sets bill_time, bill_process, payer and target on
59 //   queueing a claim for processing.  Create claims row.
60 // * billing_process.php sets claim status to 2, and payer, on marking a
61 //   claim as billed without actually generating any billing.  Create a
62 //   claims row.  In this case bill_process will remain at 0 and process_time
63 //   and process_file will not be set.
64 // * billing_process.php sets bill_process, payer, target and x12 partner
65 //   before calling gen_x12_837.  Create a claims row.
66 // * billing_process.php sets claim status to 2 (billed), bill_process to 2,
67 //   process_time and process_file after calling gen_x12_837.  Claims row
68 //   already exists.
69 // * billing_process.php sets claim status to 2 (billed) after creating
70 //   an electronic batch (hcfa-only with recent changes).  Claims
71 //   row already exists.
72 // * EOB posting updates claim status to mark a payer as done.  Claims row
73 //   already exists.
74 // * EOB posting reopens an encounter for billing a secondary payer.  Create
75 //   a claims row.
77 // $newversion should be passed to us to indicate if a new claims row
78 // is to be generated, otherwise one must already exist.  The payer, if
79 // passed in for the latter case, must match the existing claim.
81 // Currently on the billing page the user can select any of the patient's
82 // payers.  That logic will tailor the payer choices to the encounter date.
84 function updateClaim($newversion, $patient_id, $encounter_id, $payer_id=-1, $payer_type=-1,
85   $status=-1, $bill_process=-1, $process_file='', $target='', $partner_id=-1,$crossover=0)
87   if (!$newversion) {
88     $sql = "SELECT * FROM claims WHERE patient_id = '$patient_id' AND " .
89       "encounter_id = '$encounter_id' AND status > 0 AND status < 4 ";
90     if ($payer_id >= 0) $sql .= "AND payer_id = '$payer_id' ";
91     $sql .= "ORDER BY version DESC LIMIT 1";
92     $row = sqlQuery($sql);
93     if (!$row) return 0;
94     if ($payer_id     < 0) $payer_id     = $row['payer_id'];
95     if ($status       < 0) $status       = $row['status'];
96     if ($bill_process < 0) $bill_process = $row['bill_process'];
97     if ($partner_id   < 0) $partner_id   = $row['x12_partner_id'];
98     if (!$process_file   ) $process_file = $row['process_file'];
99     if (!$target         ) $target       = $row['target'];
100   }
102   $claimset = "";
103   $billset = "";
104   if (empty($payer_id) || $payer_id < 0) $payer_id = 0;
106   if ($status==7) {//$status==7 is the claim denial case.
107     $claimset .= ", status = '$status'";
108   }
109   elseif ($status >= 0) {
110     $claimset .= ", status = '$status'";
111     if ($status > 1) {
112       $billset .= ", billed = 1";
113       if ($status == 2) $billset  .= ", bill_date = NOW()";
114     } else {
115       $billset .= ", billed = 0";
116     }
117   }
118   if ($status==7) {//$status==7 is the claim denial case.
119     $billset  .= ", bill_process = '$status'";
120   }
121   elseif ($bill_process >= 0) {
122     $claimset .= ", bill_process = '$bill_process'";
123     $billset  .= ", bill_process = '$bill_process'";
124   }
125   if ($status==7) {//$status==7 is the claim denial case.
126     $claimset  .= ", process_file = '$process_file'";//Denial reason code is stored here
127   }
128   elseif ($process_file) {
129     $claimset .= ", process_file = '$process_file', process_time = NOW()";
130     $billset  .= ", process_file = '$process_file', process_date = NOW()";
131   }
132   if ($target) {
133     $claimset .= ", target = '$target'";
134     $billset  .= ", target = '$target'";
135   }
136   if ($payer_id >= 0) {
137     $claimset .= ", payer_id = '$payer_id', payer_type = '$payer_type'";
138     $billset  .= ", payer_id = '$payer_id'";
139   }
140   if ($partner_id >= 0) {
141     $claimset .= ", x12_partner_id = '$partner_id'";
142     $billset  .= ", x12_partner_id = '$partner_id'";
143   }
145   if ($billset) {
146     $billset = substr($billset, 2);
147     sqlStatement("UPDATE billing SET $billset WHERE " .
148       "encounter = '$encounter_id' AND pid='$patient_id' AND activity = 1");
149   }
151   // If a new claim version is requested, insert its row.
152   //
153   if ($newversion) {
154     /****
155     $payer_id = ($payer_id < 0) ? $row['payer_id'] : $payer_id;
156     $bill_process = ($bill_process < 0) ? $row['bill_process'] : $bill_process;
157     $process_file = ($process_file) ? $row['process_file'] : $process_file;
158     $target = ($target) ? $row['target'] : $target;
159     $partner_id = ($partner_id < 0) ? $row['x12_partner_id'] : $partner_id;
160     $sql = "INSERT INTO claims SET " .
161       "patient_id = '$patient_id', " .
162       "encounter_id = '$encounter_id', " .
163       "bill_time = UNIX_TIMESTAMP(NOW()), " .
164       "payer_id = '$payer_id', " .
165       "status = '$status', " .
166       "payer_type = '" . $row['payer_type'] . "', " .
167       "bill_process = '$bill_process', " .
168       "process_time = '" . $row['process_time'] . "', " .
169       "process_file = '$process_file', " .
170       "target = '$target', " .
171       "x12_partner_id = '$partner_id'";
172     ****/
173     sqlBeginTrans();
174     $version = sqlQuery( "SELECT IFNULL(MAX(version),0) + 1 AS increment FROM claims WHERE patient_id = ? AND encounter_id = ?", array( $patient_id, $encounter_id ) );
175     if($crossover<>1)
176     {
177       $sql .= "INSERT INTO claims SET " .
178           "patient_id = $patient_id, " .
179           "encounter_id = $encounter_id, " .
180           "bill_time = NOW() $claimset ," .
181           "version = " . $version['increment'];
182     }
183     else
184     {//Claim automatic forward case.startTra
185       $sql= "INSERT INTO claims SET " .
186           "patient_id = $patient_id, " .
187           "encounter_id = $encounter_id, " .
188           "bill_time = NOW(), status=$status ," .
189           "version = " . $version['increment'];
190     }
191     sqlStatement($sql);
192     sqlCommitTrans();
193   }
195   // Otherwise update the existing claim row.
196   //
197   else if ($claimset) {
198     $claimset = substr($claimset, 2);
199     sqlStatement("UPDATE claims SET $claimset WHERE " .
200       "patient_id = '$patient_id' AND encounter_id = '$encounter_id' AND " .
201       // "payer_id = '" . $row['payer_id'] . "' AND " .
202       "version = '" . $row['version'] . "'");
203   }
205   // Whenever a claim is marked billed, update A/R accordingly.
206   //
207   if ($status == 2) {
208       if ($payer_type > 0) {
209         sqlStatement("UPDATE form_encounter SET " .
210           "last_level_billed = '$payer_type' WHERE " .
211           "pid = '$patient_id' AND encounter = '$encounter_id'");
212       }
213   }
215   return 1;
218 // Determine if the encounter is billed.  It is considered billed if it
219 // has at least one chargeable item, and all of them are billed.
221 function isEncounterBilled($pid, $encounter) {
222   $billed = -1; // no chargeable services yet
224   $bres = sqlStatement("SELECT " .
225     "billing.billed FROM billing, code_types WHERE " .
226     "billing.pid = ? AND " .
227     "billing.encounter = ? AND " .
228     "billing.activity = 1 AND " .
229     "code_types.ct_key = billing.code_type AND " .
230     "code_types.ct_fee = 1 " .
231     "UNION " .
232     "SELECT billed FROM drug_sales WHERE " .
233     "pid = ? AND " .
234     "encounter = ?",
235     array($pid, $encounter, $pid, $encounter));
237   while ($brow = sqlFetchArray($bres)) {
238     if ($brow['billed'] == 0) {
239       $billed = 0;
240     }
241     else {
242       if ($billed < 0) $billed = 1;
243     }
244   }
246   return $billed > 0;
249 // Get the co-pay amount that is effective on the given date.
250 // Or if no insurance on that date, return -1.
252 function getCopay($patient_id, $encdate) {
253  $tmp = sqlQuery("SELECT provider, copay FROM insurance_data " .
254    "WHERE pid = '$patient_id' AND type = 'primary' " .
255    "AND date <= '$encdate' ORDER BY date DESC LIMIT 1");
256  if ($tmp['provider']) return sprintf('%01.2f', 0 + $tmp['copay']);
257  return 0;
260 // Get the total co-pay amount paid by the patient for an encounter
261 function getPatientCopay($patient_id, $encounter) {
262         $resMoneyGot = sqlStatement("SELECT sum(pay_amount) as PatientPay FROM ar_activity where ".
263           "pid = ? and encounter = ? and payer_type=0 and account_code='PCP'",
264           array($patient_id,$encounter));
265          //new fees screen copay gives account_code='PCP'
266         $rowMoneyGot = sqlFetchArray($resMoneyGot);
267         $Copay=$rowMoneyGot['PatientPay'];
268         return $Copay*-1;
271 // Get the "next invoice reference number" from this user's pool of reference numbers.
273 function getInvoiceRefNumber() {
274   $trow = sqlQuery("SELECT lo.notes " .
275     "FROM users AS u, list_options AS lo " .
276     "WHERE u.username = ? AND " .
277     "lo.list_id = 'irnpool' AND lo.option_id = u.irnpool AND lo.activity = 1 LIMIT 1",
278     array($_SESSION['authUser']));
279   return empty($trow['notes']) ? '' : $trow['notes'];
282 // Increment the "next invoice reference number" of this user's pool.
283 // This identifies the "digits" portion of that number and adds 1 to it.
284 // If it contains more than one string of digits, the last is used.
286 function updateInvoiceRefNumber() {
287   $irnumber = getInvoiceRefNumber();
288   // Here "?" specifies a minimal match, to get the most digits possible:
289   if (preg_match('/^(.*?)(\d+)(\D*)$/', $irnumber, $matches)) {
290     $newdigs = sprintf('%0' . strlen($matches[2]) . 'd', $matches[2] + 1);
291     $newnumber = $matches[1] . $newdigs . $matches[3];
292     sqlStatement("UPDATE users AS u, list_options AS lo " .
293       "SET lo.notes = ? WHERE " .
294       "u.username = ? AND " .
295       "lo.list_id = 'irnpool' AND lo.option_id = u.irnpool",
296       array($newnumber, $_SESSION['authUser']));
297   }
298   return $irnumber;
301 // Common function for voiding a receipt or checkout.  When voiding a checkout you can specify
302 // $time as a timestamp (yyyy-mm-dd hh:mm:ss) or 'all'; default is the last checkout.
304 function doVoid($patient_id, $encounter_id, $purge=false, $time='') {
305   $what_voided = $purge ? 'checkout' : 'receipt';
306   $date_original = '';
307   $adjustments = 0;
308   $payments = 0;
310   if (!$time) {
311     // Get last checkout timestamp.
312     $corow = sqlQuery("(SELECT bill_date FROM billing WHERE " .
313       "pid = ? AND encounter = ? AND activity = 1 AND bill_date IS NOT NULL) " .
314       "UNION " .
315       "(SELECT bill_date FROM drug_sales WHERE " .
316       "pid = ? AND encounter = ? AND bill_date IS NOT NULL) " .
317       "ORDER BY bill_date DESC LIMIT 1",
318       array($patient_id, $encounter_id, $patient_id, $encounter_id));
319     if (!empty($corow['bill_date'])) {
320       $date_original = $corow['bill_date'];
321     }
322   }
323   else if ($time == 'all') {
324     $row = sqlQuery("SELECT SUM(pay_amount) AS payments, " .
325       "SUM(adj_amount) AS adjustments FROM ar_activity WHERE " .
326       "pid = ? AND encounter = ?",
327       array($patient_id, $encounter_id));
328     $adjustments = $row['adjustments'];
329     $payments = $row['payments'];
330   }
331   else {
332     $date_original = $time;
333   }
334   // Get its charges and adjustments.
335   if ($date_original) {
336     $row = sqlQuery("SELECT SUM(pay_amount) AS payments, " .
337       "SUM(adj_amount) AS adjustments FROM ar_activity WHERE " .
338       "pid = ? AND encounter = ? AND post_time = ?",
339       array($patient_id, $encounter_id, $date_original));
340     $adjustments = $row['adjustments'];
341     $payments = $row['payments'];
342   }
344   // Get old invoice reference number.
345   $encrow = sqlQuery("SELECT invoice_refno FROM form_encounter WHERE " .
346     "pid = ? AND encounter = ? LIMIT 1",
347     array($patient_id, $encounter_id));
348   $old_invoice_refno = $encrow['invoice_refno'];
349   //
350   $usingirnpools = getInvoiceRefNumber();
351   // If not (undoing a checkout or using IRN pools), nothing is done.
352   if ($purge || $usingirnpools) {
353     $query = "INSERT INTO voids SET " .
354       "patient_id = ?, " .
355       "encounter_id = ?, " .
356       "what_voided = ?, " .
357       "date_voided = NOW(), " .
358       "user_id = ?, " .
359       "amount1 = ?, " .
360       "amount2 = ?, " .
361       "other_info = ?";
362     $sqlarr = array($patient_id, $encounter_id, $what_voided, $_SESSION['authUserID'], $row['adjustments'],
363       $row['payments'], $old_invoice_refno);
364     if ($date_original) {
365       $query .= ", date_original = ?";
366       $sqlarr[] = $date_original;
367     }
368     sqlStatement($query, $sqlarr);
369   }
370   if ($purge) {
371     // Purge means delete adjustments and payments from the last checkout
372     // and re-open the visit.
373     if ($date_original) {
374       sqlStatement("DELETE FROM ar_activity WHERE " .
375         "pid = ? AND encounter = ? AND post_time = ?",
376         array($patient_id, $encounter_id, $date_original));
377       sqlStatement("UPDATE billing SET billed = 0, bill_date = NULL WHERE " .
378         "pid = ? AND encounter = ? AND activity = 1 AND " .
379         "bill_date IS NOT NULL AND bill_date = ?",
380         array($patient_id, $encounter_id, $date_original));
381       sqlStatement("update drug_sales SET billed = 0, bill_date = NULL WHERE " .
382         "pid = ? AND encounter = ? AND " .
383         "bill_date IS NOT NULL AND bill_date = ?",
384         array($patient_id, $encounter_id, $date_original));
385     }
386     else {
387       if ($time == 'all') {
388         sqlStatement("DELETE FROM ar_activity WHERE " .
389           "pid = ? AND encounter = ?",
390           array($patient_id, $encounter_id));
391       }
392       sqlStatement("UPDATE billing SET billed = 0, bill_date = NULL WHERE " .
393         "pid = ? AND encounter = ? AND activity = 1",
394           array($patient_id, $encounter_id));
395       sqlStatement("update drug_sales SET billed = 0, bill_date = NULL WHERE " .
396         "pid = ? AND encounter = ?",
397           array($patient_id, $encounter_id));
398     }
399     sqlStatement("UPDATE form_encounter SET last_level_billed = 0, " .
400       "last_level_closed = 0, stmt_count = 0, last_stmt_date = NULL " .
401       "WHERE pid = ? AND encounter = ?",
402       array($patient_id, $encounter_id));
403   }
404   else if ($usingirnpools) {
405     // Non-purge means just assign a new invoice reference number.
406     $new_invoice_refno = add_escape_custom(updateInvoiceRefNumber());
407     sqlStatement("UPDATE form_encounter " .
408       "SET invoice_refno = ? " .
409       "WHERE pid = ? AND encounter = ?",
410       array($new_invoice_refno, $patient_id, $encounter_id));
411   }