Fix for odd Adminsitration-=>globals bug
[openemr.git] / library / invoice_summary.inc.php
blob6812a0b315f721843bcf23d561ea5a9f71dc9720
1 <?php
2 // Copyright (C) 2005-2010 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
30 // arseq - ar_activity.sequence_no when it applies.
32 require_once("sl_eob.inc.php");
34 function get_invoice_summary($trans_id, $with_detail = false) {
35 global $sl_err, $sl_cash_acc;
37 $codes = array();
39 $chart_id_cash = SLQueryValue("select id from chart where accno = '$sl_cash_acc'");
40 if ($sl_err) die($sl_err);
41 if (! $chart_id_cash) die("There is no COA entry for cash account '$sl_cash_acc'");
43 // Request all cash entries belonging to the invoice.
44 $atres = SLQuery("select * from acc_trans where trans_id = $trans_id and chart_id = $chart_id_cash");
45 if ($sl_err) die($sl_err);
47 // Deduct payments for each procedure code from the respective balance owed.
48 $keysuffix = 5000;
49 for ($irow = 0; $irow < SLRowCount($atres); ++$irow) {
50 $row = SLGetRow($atres, $irow);
51 $code = strtoupper($row['memo']);
52 $ins_id = $row['project_id'];
53 if (! $code) $code = "Unknown";
54 $amount = $row['amount'];
55 $codes[$code]['bal'] += $amount; // amount is negative for a payment
56 if ($ins_id)
57 $codes[$code]['ins'] = $ins_id;
59 // Add the details if they want 'em.
60 if ($with_detail) {
61 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
62 $tmpkey = $row['transdate'] . $keysuffix++;
63 $tmp = array();
64 $tmp['pmt'] = 0 - $amount;
65 $tmp['src'] = $row['source'];
66 if ($ins_id) $tmp['ins'] = $ins_id;
67 $codes[$code]['dtl'][$tmpkey] = $tmp;
71 // Request all line items with money or adjustment reasons belonging
72 // to the invoice.
73 $inres = SLQuery("SELECT * FROM invoice WHERE trans_id = $trans_id AND " .
74 "( sellprice != 0 OR description LIKE 'Adjustment%' OR serialnumber = 'Claim' )");
75 if ($sl_err) die($sl_err);
77 // Add charges and adjustments for each procedure code into its total and balance.
78 $keysuffix = 1000;
79 for ($irow = 0; $irow < SLRowCount($inres); ++$irow) {
80 $row = SLGetRow($inres, $irow);
81 // $amount = $row['sellprice'];
82 $amount = sprintf('%01.2f', $row['sellprice'] * $row['qty']);
83 $ins_id = $row['project_id'];
85 $code = "Unknown";
86 if ($row['serialnumber'] == 'Claim') {
87 $code = 'Claim';
89 else if (preg_match("/([A-Za-z0-9]\d\d\S*)/", $row['serialnumber'], $matches)) {
90 $code = strtoupper($matches[1]);
92 else if (preg_match("/([A-Za-z0-9]\d\d\S*)/", $row['description'], $matches)) {
93 $code = strtoupper($matches[1]);
95 else if (preg_match("/^TAX:/", $row['description'])) {
96 $code = 'TAX';
99 $codes[$code]['chg'] += $amount;
100 $codes[$code]['bal'] += $amount;
101 if ($amount < 0) $codes[$code]['adj'] -= $amount;
103 if ($ins_id)
104 $codes[$code]['ins'] = $ins_id;
106 // Add the details if they want 'em.
107 if ($with_detail) {
108 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
109 if (preg_match("/^Adjustment\s*(\S*)\s*(.*)/", $row['description'], $matches)) {
110 $tmpkey = str_pad($matches[1], 10) . $keysuffix++;
111 $tmp = array();
112 $tmp['chg'] = $amount;
113 $tmp['rsn'] = $matches[2];
114 if ($ins_id) $tmp['ins'] = $ins_id;
115 $codes[$code]['dtl'][$tmpkey] = $tmp;
117 else {
118 $tmpkey = " " . $keysuffix++;
119 $tmp = array();
120 $tmp['chg'] = $amount;
121 if ($code == 'TAX') $tmp['dsc'] = substr($row['description'], 4);
122 $codes[$code]['dtl'][$tmpkey] = $tmp;
127 return $codes;
130 // Like the above, but for Integrated A/R.
132 function ar_get_invoice_summary($patient_id, $encounter_id, $with_detail = false) {
133 $codes = array();
134 $keysuff1 = 1000;
135 $keysuff2 = 5000;
137 // Get charges from services.
138 $res = sqlStatement("SELECT " .
139 "date, code_type, code, modifier, code_text, fee " .
140 "FROM billing WHERE " .
141 "pid = ? AND encounter = ? AND " .
142 "activity = 1 AND fee != 0.00 ORDER BY id", array($patient_id,$encounter_id) );
144 while ($row = sqlFetchArray($res)) {
145 $amount = sprintf('%01.2f', $row['fee']);
147 if ($row['code_type'] == 'COPAY') {
148 $code = 'CO-PAY';
149 $codes[$code]['bal'] += $amount;
151 else {
152 $code = strtoupper($row['code']);
153 if (! $code) $code = "Unknown";
154 if ($row['modifier']) $code .= ':' . strtoupper($row['modifier']);
155 $codes[$code]['chg'] += $amount;
156 $codes[$code]['bal'] += $amount;
158 // Add the details if they want 'em.
159 if ($with_detail) {
160 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
161 $tmp = array();
162 if ($row['code_type'] == 'COPAY') {
163 $tmp['pmt'] = 0 - $amount;
164 $tmp['src'] = 'Pt Paid';
165 $tmp['plv'] = 0;
166 $tmpkey = substr($row['date'], 0, 10) . $keysuff2++;
168 else {
169 $tmp['chg'] = $amount;
170 $tmpkey = " " . $keysuff1++;
172 $codes[$code]['dtl'][$tmpkey] = $tmp;
176 // Get charges from product sales.
177 $query = "SELECT s.drug_id, s.sale_date, s.fee, s.quantity " .
178 "FROM drug_sales AS s " .
179 "WHERE " .
180 "s.pid = ? AND s.encounter = ? AND s.fee != 0 " .
181 "ORDER BY s.sale_id";
182 $res = sqlStatement($query, array($patient_id,$encounter_id) );
183 while ($row = sqlFetchArray($res)) {
184 $amount = sprintf('%01.2f', $row['fee']);
185 $code = 'PROD:' . $row['drug_id'];
186 $codes[$code]['chg'] += $amount;
187 $codes[$code]['bal'] += $amount;
188 // Add the details if they want 'em.
189 if ($with_detail) {
190 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
191 $tmp = array();
192 $tmp['chg'] = $amount;
193 $tmpkey = " " . $keysuff1++;
194 $codes[$code]['dtl'][$tmpkey] = $tmp;
198 // Get payments and adjustments.
199 $res = sqlStatement("SELECT " .
200 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
201 "a.post_time, a.session_id, a.sequence_no, " .
202 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
203 ",i.name " .
204 "FROM ar_activity AS a " .
205 "LEFT OUTER JOIN ar_session AS s ON s.session_id = a.session_id " .
206 "LEFT OUTER JOIN insurance_companies AS i ON i.id = s.payer_id " .
207 "WHERE a.pid = ? AND a.encounter = ? " .
208 "ORDER BY s.check_date, a.sequence_no", array($patient_id,$encounter_id) );
209 while ($row = sqlFetchArray($res)) {
210 $code = strtoupper($row['code']);
211 if (! $code) $code = "Unknown";
212 if ($row['modifier']) $code .= ':' . strtoupper($row['modifier']);
213 $ins_id = 0 + $row['payer_id'];
214 $codes[$code]['bal'] -= $row['pay_amount'];
215 $codes[$code]['bal'] -= $row['adj_amount'];
216 $codes[$code]['chg'] -= $row['adj_amount'];
217 $codes[$code]['adj'] += $row['adj_amount'];
218 if ($ins_id) $codes[$code]['ins'] = $ins_id;
219 // Add the details if they want 'em.
220 if ($with_detail) {
221 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
222 $tmp = array();
223 $paydate = empty($row['deposit_date']) ? substr($row['post_time'], 0, 10) : $row['deposit_date'];
224 if ($row['pay_amount'] != 0) $tmp['pmt'] = $row['pay_amount'];
225 if ($row['adj_amount'] != 0 || $row['pay_amount'] == 0) {
226 $tmp['chg'] = 0 - $row['adj_amount'];
227 // $tmp['rsn'] = (empty($row['memo']) || empty($row['session_id'])) ? 'Unknown adjustment' : $row['memo'];
228 $tmp['rsn'] = empty($row['memo']) ? 'Unknown adjustment' : $row['memo'];
229 $tmpkey = $paydate . $keysuff1++;
231 else {
232 $tmpkey = $paydate . $keysuff2++;
234 $tmp['src'] = empty($row['session_id']) ? $row['memo'] : $row['reference'];
235 $tmp['insurance_company'] = substr($row['name'], 0, 10);
236 if ($ins_id) $tmp['ins'] = $ins_id;
237 $tmp['plv'] = $row['payer_type'];
238 $tmp['arseq'] = $row['sequence_no'];
239 $codes[$code]['dtl'][$tmpkey] = $tmp;
242 return $codes;
245 // This determines the party from whom payment is currently expected.
246 // Returns: -1=Nobody, 0=Patient, 1=Ins1, 2=Ins2, 3=Ins3.
248 function responsible_party($trans_id) {
249 global $sl_err;
250 $arres = SLQuery("select * from ar where id = $trans_id");
251 if ($sl_err) die($sl_err);
252 $arrow = SLGetRow($arres, 0);
253 if (! $arrow) die(xl("There is no match for invoice id = ") . $trans_id);
254 if ($arrow['paid'] >= $arrow['netamount']) return -1;
255 $insgot = strtolower($arrow['notes']);
256 $insdone = strtolower($arrow['shipvia']);
257 for ($i = 1; $i <= 3; ++$i) {
258 $lcvalue = "ins$i";
259 if (strpos($insgot, $lcvalue) !== false && strpos($insdone, $lcvalue) === false)
260 return $i;
262 return 0;
265 // As above but for Integrated A/R.
267 function ar_responsible_party($patient_id, $encounter_id) {
268 $row = sqlQuery("SELECT date, last_level_billed, last_level_closed " .
269 "FROM form_encounter WHERE " .
270 "pid = ? AND encounter = ? " .
271 "ORDER BY id DESC LIMIT 1", array($patient_id,$encounter_id) );
272 if (empty($row)) return -1;
273 $next_level = $row['last_level_closed'] + 1;
274 if ($next_level <= $row['last_level_billed'])
275 return $next_level;
276 if (arGetPayerID($patient_id, substr($row['date'], 0, 10), $next_level))
277 return $next_level;
278 // There is no unclosed insurance, so see if there is an unpaid balance.
279 // Currently hoping that form_encounter.balance_due can be discarded.
280 $balance = 0;
281 $codes = ar_get_invoice_summary($patient_id, $encounter_id);
282 foreach ($codes as $cdata) $balance += $cdata['bal'];
283 if ($balance > 0) return 0;
284 return -1;