2 // Copyright (C) 2005-2010 Rod Roark <rod@sunsetsystems.com>
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;
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.
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
57 $codes[$code]['ins'] = $ins_id;
59 // Add the details if they want 'em.
61 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
62 $tmpkey = $row['transdate'] . $keysuffix++
;
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
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.
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'];
86 if ($row['serialnumber'] == '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'])) {
99 $codes[$code]['chg'] +
= $amount;
100 $codes[$code]['bal'] +
= $amount;
101 if ($amount < 0) $codes[$code]['adj'] -= $amount;
104 $codes[$code]['ins'] = $ins_id;
106 // Add the details if they want 'em.
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++
;
112 $tmp['chg'] = $amount;
113 $tmp['rsn'] = $matches[2];
114 if ($ins_id) $tmp['ins'] = $ins_id;
115 $codes[$code]['dtl'][$tmpkey] = $tmp;
118 $tmpkey = " " . $keysuffix++
;
120 $tmp['chg'] = $amount;
121 if ($code == 'TAX') $tmp['dsc'] = substr($row['description'], 4);
122 $codes[$code]['dtl'][$tmpkey] = $tmp;
130 // Like the above, but for Integrated A/R.
132 function ar_get_invoice_summary($patient_id, $encounter_id, $with_detail = false) {
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') {
149 $codes[$code]['bal'] +
= $amount;
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.
160 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = array();
162 if ($row['code_type'] == 'COPAY') {
163 $tmp['pmt'] = 0 - $amount;
164 $tmp['src'] = 'Pt Paid';
166 $tmpkey = substr($row['date'], 0, 10) . $keysuff2++
;
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 " .
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.
190 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = 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, a.reason_code, " .
201 "a.post_time, a.session_id, a.sequence_no, " .
202 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
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.
221 if (! $codes[$code]['dtl']) $codes[$code]['dtl'] = 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 ( isset($row['reason_code'] ) ) {
226 $tmp['msp'] = $row['reason_code'];
228 if ($row['adj_amount'] != 0 ||
$row['pay_amount'] == 0) {
229 $tmp['chg'] = 0 - $row['adj_amount'];
230 // $tmp['rsn'] = (empty($row['memo']) || empty($row['session_id'])) ? 'Unknown adjustment' : $row['memo'];
231 $tmp['rsn'] = empty($row['memo']) ?
'Unknown adjustment' : $row['memo'];
232 $tmpkey = $paydate . $keysuff1++
;
235 $tmpkey = $paydate . $keysuff2++
;
237 $tmp['src'] = empty($row['session_id']) ?
$row['memo'] : $row['reference'];
238 $tmp['insurance_company'] = substr($row['name'], 0, 10);
239 if ($ins_id) $tmp['ins'] = $ins_id;
240 $tmp['plv'] = $row['payer_type'];
241 $tmp['arseq'] = $row['sequence_no'];
242 $codes[$code]['dtl'][$tmpkey] = $tmp;
248 // This determines the party from whom payment is currently expected.
249 // Returns: -1=Nobody, 0=Patient, 1=Ins1, 2=Ins2, 3=Ins3.
251 function responsible_party($trans_id) {
253 $arres = SLQuery("select * from ar where id = $trans_id");
254 if ($sl_err) die($sl_err);
255 $arrow = SLGetRow($arres, 0);
256 if (! $arrow) die(xl("There is no match for invoice id = ") . $trans_id);
257 if ($arrow['paid'] >= $arrow['netamount']) return -1;
258 $insgot = strtolower($arrow['notes']);
259 $insdone = strtolower($arrow['shipvia']);
260 for ($i = 1; $i <= 3; ++
$i) {
262 if (strpos($insgot, $lcvalue) !== false && strpos($insdone, $lcvalue) === false)
268 // As above but for Integrated A/R.
270 function ar_responsible_party($patient_id, $encounter_id) {
271 $row = sqlQuery("SELECT date, last_level_billed, last_level_closed " .
272 "FROM form_encounter WHERE " .
273 "pid = ? AND encounter = ? " .
274 "ORDER BY id DESC LIMIT 1", array($patient_id,$encounter_id) );
275 if (empty($row)) return -1;
276 $next_level = $row['last_level_closed'] +
1;
277 if ($next_level <= $row['last_level_billed'])
279 if (arGetPayerID($patient_id, substr($row['date'], 0, 10), $next_level))
281 // There is no unclosed insurance, so see if there is an unpaid balance.
282 // Currently hoping that form_encounter.balance_due can be discarded.
284 $codes = ar_get_invoice_summary($patient_id, $encounter_id);
285 foreach ($codes as $cdata) $balance +
= $cdata['bal'];
286 if ($balance > 0) return 0;