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