Fix drug name search to work in a broader sense
[openemr.git] / library / invoice_summary.inc.php
blob4e93c9addc6976117fb6a406b9697ff6051e18ac
1 <?php
2 // Copyright (C) 2005-2008 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 // This returns an associative array keyed on procedure code, representing
10 // all charge items for one invoice. This array's values are themselves
11 // associative arrays having the following keys:
13 // chg - the sum of line items, including adjustments, for the code
14 // bal - the unpaid balance
15 // adj - the (positive) sum of inverted adjustments
16 // ins - the id of the insurance company that was billed (obsolete)
17 // dtl - associative array of details, if requested
19 // Where details are requested, each dtl array is keyed on a string
20 // beginning with a date in yyyy-mm-dd format, or blanks in the case
21 // of the original charge items. The value array is:
23 // pmt - payment amount as a positive number, only for payments
24 // src - check number or other source, only for payments
25 // chg - invoice line item amount amount, only for charges or
26 // adjustments (adjustments may be zero)
27 // rsn - adjustment reason, only for adjustments
28 // plv - provided for "integrated A/R" only: 0=pt, 1=Ins1, etc.
29 // dsc - for tax charges, a description of the tax
31 require_once("sl_eob.inc.php");
33 function get_invoice_summary($trans_id, $with_detail = false) {
34 global $sl_err, $sl_cash_acc;
36 $codes = array();
38 $chart_id_cash = SLQueryValue("select id from chart where accno = '$sl_cash_acc'");
39 if ($sl_err) die($sl_err);
40 if (! $chart_id_cash) die("There is no COA entry for cash account '$sl_cash_acc'");
42 // Request all cash entries belonging to the invoice.
43 $atres = SLQuery("select * from acc_trans where trans_id = $trans_id and chart_id = $chart_id_cash");
44 if ($sl_err) die($sl_err);
46 // Deduct payments for each procedure code from the respective balance owed.
47 $keysuffix = 5000;
48 for ($irow = 0; $irow < SLRowCount($atres); ++$irow) {
49 $row = SLGetRow($atres, $irow);
50 $code = strtoupper($row['memo']);
51 $ins_id = $row['project_id'];
52 if (! $code) $code = "Unknown";
53 $amount = $row['amount'];
54 $codes[$code]['bal'] += $amount; // amount is negative for a payment
55 if ($ins_id)
56 $codes[$code]['ins'] = $ins_id;
58 // Add the details if they want 'em.
59 if ($with_detail) {
60 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
61 $tmpkey = $row['transdate'] . $keysuffix++;
62 $tmp = array();
63 $tmp['pmt'] = 0 - $amount;
64 $tmp['src'] = $row['source'];
65 if ($ins_id) $tmp['ins'] = $ins_id;
66 $codes[$code]['dtl'][$tmpkey] = $tmp;
70 // Request all line items with money or adjustment reasons belonging
71 // to the invoice.
72 $inres = SLQuery("SELECT * FROM invoice WHERE trans_id = $trans_id AND " .
73 "( sellprice != 0 OR description LIKE 'Adjustment%' OR serialnumber = 'Claim' )");
74 if ($sl_err) die($sl_err);
76 // Add charges and adjustments for each procedure code into its total and balance.
77 $keysuffix = 1000;
78 for ($irow = 0; $irow < SLRowCount($inres); ++$irow) {
79 $row = SLGetRow($inres, $irow);
80 // $amount = $row['sellprice'];
81 $amount = sprintf('%01.2f', $row['sellprice'] * $row['qty']);
82 $ins_id = $row['project_id'];
84 $code = "Unknown";
85 if ($row['serialnumber'] == 'Claim') {
86 $code = 'Claim';
88 else if (preg_match("/([A-Za-z0-9]\d\d\S*)/", $row['serialnumber'], $matches)) {
89 $code = strtoupper($matches[1]);
91 else if (preg_match("/([A-Za-z0-9]\d\d\S*)/", $row['description'], $matches)) {
92 $code = strtoupper($matches[1]);
94 else if (preg_match("/^TAX:/", $row['description'])) {
95 $code = 'TAX';
98 $codes[$code]['chg'] += $amount;
99 $codes[$code]['bal'] += $amount;
100 if ($amount < 0) $codes[$code]['adj'] -= $amount;
102 if ($ins_id)
103 $codes[$code]['ins'] = $ins_id;
105 // Add the details if they want 'em.
106 if ($with_detail) {
107 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
108 if (preg_match("/^Adjustment\s*(\S*)\s*(.*)/", $row['description'], $matches)) {
109 $tmpkey = str_pad($matches[1], 10) . $keysuffix++;
110 $tmp = array();
111 $tmp['chg'] = $amount;
112 $tmp['rsn'] = $matches[2];
113 if ($ins_id) $tmp['ins'] = $ins_id;
114 $codes[$code]['dtl'][$tmpkey] = $tmp;
116 else {
117 $tmpkey = " " . $keysuffix++;
118 $tmp = array();
119 $tmp['chg'] = $amount;
120 if ($code == 'TAX') $tmp['dsc'] = substr($row['description'], 4);
121 $codes[$code]['dtl'][$tmpkey] = $tmp;
126 return $codes;
129 // Like the above, but for Integrated A/R.
131 function ar_get_invoice_summary($patient_id, $encounter_id, $with_detail = false) {
132 $codes = array();
133 $keysuff1 = 1000;
134 $keysuff2 = 5000;
136 // Get charges from services.
137 $res = sqlStatement("SELECT " .
138 "date, code_type, code, modifier, code_text, fee " .
139 "FROM billing WHERE " .
140 "pid = '$patient_id' AND encounter = '$encounter_id' AND " .
141 "activity = 1 AND fee != 0.00 ORDER BY id");
143 while ($row = sqlFetchArray($res)) {
144 $amount = sprintf('%01.2f', $row['fee']);
146 if ($row['code_type'] == 'COPAY') {
147 $code = 'CO-PAY';
148 $codes[$code]['bal'] += $amount;
150 else {
151 $code = strtoupper($row['code']);
152 if (! $code) $code = "Unknown";
153 if ($row['modifier']) $code .= ':' . strtoupper($row['modifier']);
154 $codes[$code]['chg'] += $amount;
155 $codes[$code]['bal'] += $amount;
157 // Add the details if they want 'em.
158 if ($with_detail) {
159 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
160 $tmp = array();
161 if ($row['code_type'] == 'COPAY') {
162 $tmp['pmt'] = 0 - $amount;
163 $tmp['src'] = 'Pt Paid';
164 $tmp['plv'] = 0;
165 $tmpkey = substr($row['date'], 0, 10) . $keysuff2++;
167 else {
168 $tmp['chg'] = $amount;
169 $tmpkey = " " . $keysuff1++;
171 $codes[$code]['dtl'][$tmpkey] = $tmp;
175 // Get charges from product sales.
176 $query = "SELECT s.drug_id, s.sale_date, s.fee, s.quantity " .
177 "FROM drug_sales AS s " .
178 "WHERE " .
179 "s.pid = '$patient_id' AND s.encounter = '$encounter_id' AND s.fee != 0 " .
180 "ORDER BY s.sale_id";
181 $res = sqlStatement($query);
182 while ($row = sqlFetchArray($res)) {
183 $amount = sprintf('%01.2f', $row['fee']);
184 $code = 'PROD:' . $row['drug_id'];
185 $codes[$code]['chg'] += $amount;
186 $codes[$code]['bal'] += $amount;
187 // Add the details if they want 'em.
188 if ($with_detail) {
189 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
190 $tmp = array();
191 $tmp['chg'] = $amount;
192 $tmpkey = " " . $keysuff1++;
193 $codes[$code]['dtl'][$tmpkey] = $tmp;
197 // Get payments and adjustments.
198 $res = sqlStatement("SELECT " .
199 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
200 "a.post_time, a.session_id, " .
201 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
202 "FROM ar_activity AS a " .
203 "LEFT OUTER JOIN ar_session AS s ON s.session_id = a.session_id " .
204 "WHERE a.pid = '$patient_id' AND a.encounter = '$encounter_id' " .
205 "ORDER BY s.check_date, a.sequence_no");
206 while ($row = sqlFetchArray($res)) {
207 $code = strtoupper($row['code']);
208 if (! $code) $code = "Unknown";
209 if ($row['modifier']) $code .= ':' . strtoupper($row['modifier']);
210 $ins_id = 0 + $row['payer_id'];
211 $codes[$code]['bal'] -= $row['pay_amount'];
212 $codes[$code]['bal'] -= $row['adj_amount'];
213 $codes[$code]['chg'] -= $row['adj_amount'];
214 $codes[$code]['adj'] += $row['adj_amount'];
215 if ($ins_id) $codes[$code]['ins'] = $ins_id;
216 // Add the details if they want 'em.
217 if ($with_detail) {
218 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
219 $tmp = array();
220 $paydate = empty($row['deposit_date']) ? substr($row['post_time'], 0, 10) : $row['deposit_date'];
221 if ($row['pay_amount'] != 0) $tmp['pmt'] = $row['pay_amount'];
222 if ($row['adj_amount'] != 0 || $row['pay_amount'] == 0) {
223 $tmp['chg'] = 0 - $row['adj_amount'];
224 // $tmp['rsn'] = (empty($row['memo']) || empty($row['session_id'])) ? 'Unknown adjustment' : $row['memo'];
225 $tmp['rsn'] = empty($row['memo']) ? 'Unknown adjustment' : $row['memo'];
226 $tmpkey = $paydate . $keysuff1++;
228 else {
229 $tmpkey = $paydate . $keysuff2++;
231 $tmp['src'] = empty($row['session_id']) ? $row['memo'] : $row['reference'];
232 if ($ins_id) $tmp['ins'] = $ins_id;
233 $tmp['plv'] = $row['payer_type'];
234 $codes[$code]['dtl'][$tmpkey] = $tmp;
237 return $codes;
240 // This determines the party from whom payment is currently expected.
241 // Returns: -1=Nobody, 0=Patient, 1=Ins1, 2=Ins2, 3=Ins3.
243 function responsible_party($trans_id) {
244 global $sl_err;
245 $arres = SLQuery("select * from ar where id = $trans_id");
246 if ($sl_err) die($sl_err);
247 $arrow = SLGetRow($arres, 0);
248 if (! $arrow) die(xl("There is no match for invoice id = ") . $trans_id);
249 if ($arrow['paid'] >= $arrow['netamount']) return -1;
250 $insgot = strtolower($arrow['notes']);
251 $insdone = strtolower($arrow['shipvia']);
252 for ($i = 1; $i <= 3; ++$i) {
253 $lcvalue = "ins$i";
254 if (strpos($insgot, $lcvalue) !== false && strpos($insdone, $lcvalue) === false)
255 return $i;
257 return 0;
260 // As above but for Integrated A/R.
262 function ar_responsible_party($patient_id, $encounter_id) {
263 $row = sqlQuery("SELECT date, last_level_billed, last_level_closed " .
264 "FROM form_encounter WHERE " .
265 "pid = '$patient_id' AND encounter = '$encounter_id' " .
266 "ORDER BY id DESC LIMIT 1");
267 if (empty($row)) return -1;
268 $next_level = $row['last_level_closed'] + 1;
269 if ($next_level <= $row['last_level_billed'])
270 return $next_level;
271 if (arGetPayerID($patient_id, substr($row['date'], 0, 10), $payer_type))
272 return $next_level;
273 // There is no unclosed insurance, so see if there is an unpaid balance.
274 // Currently hoping that form_encounter.balance_due can be discarded.
275 $balance = 0;
276 $codes = ar_get_invoice_summary($patient_id, $encounter_id);
277 foreach ($codes as $cdata) $balance += $cdata['bal'];
278 if ($balance > 0) return 0;
279 return -1;