Added ar_activity.deleted to track voids of payments and adjustments. (#3743)
[openemr.git] / src / Billing / InvoiceSummary.php
blob3f19c454798cde0656ec3442902f46ae82421f3b
1 <?php
3 /**
4 * @package OpenEMR
5 * @author Rod Roark <rod@sunsetsystems.com>
6 * @author Stephen Waite <stephen.waite@cmsvt.com>
7 * @copyright Copyright (c) 2005-2020 Rod Roark <rod@sunsetsystems.com>
8 * @copyright Copyright (c) 2018-2019 Stephen Waite <stephen.waite@cmsvt.com>
9 * @link https://www.open-emr.org
10 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
13 // This returns an associative array keyed on procedure code, representing
14 // all charge items for one invoice. This array's values are themselves
15 // associative arrays having the following keys:
17 // chg - the sum of line items, including adjustments, for the code
18 // bal - the unpaid balance
19 // adj - the (positive) sum of inverted adjustments
20 // ins - the id of the insurance company that was billed (obsolete)
21 // dtl - associative array of details, if requested
23 // Where details are requested, each dtl array is keyed on a string
24 // beginning with a date in yyyy-mm-dd format, or blanks in the case
25 // of the original charge items. The value array is:
27 // pmt - payment amount as a positive number, only for payments
28 // src - check number or other source, only for payments
29 // chg - invoice line item amount amount, only for charges or
30 // adjustments (adjustments may be zero)
31 // rsn - adjustment reason, only for adjustments
32 // plv - provided for "integrated A/R" only: 0=pt, 1=Ins1, etc.
33 // dsc - for tax charges, a description of the tax
34 // arseq - ar_activity.sequence_no when it applies.
35 namespace OpenEMR\Billing;
37 require_once(dirname(__FILE__) . "/../../custom/code_types.inc.php");
39 use OpenEMR\Billing\SLEOB;
41 // for Integrated A/R.
43 class InvoiceSummary
45 public static function arGetInvoiceSummary($patient_id, $encounter_id, $with_detail = false)
47 $codes = array();
48 $keysuff1 = 1000;
49 $keysuff2 = 5000;
51 // Get charges from services.
52 $res = sqlStatement("SELECT " .
53 "date, code_type, code, modifier, code_text, fee " .
54 "FROM billing WHERE " .
55 "pid = ? AND encounter = ? AND " .
56 "activity = 1 AND fee != 0.00 ORDER BY id", array($patient_id, $encounter_id));
58 while ($row = sqlFetchArray($res)) {
59 $amount = sprintf('%01.2f', $row['fee']);
61 $code = $row['code'];
62 if (!$code) {
63 $code = "Unknown";
66 if ($row['modifier']) {
67 $code .= ':' . $row['modifier'];
70 $codes[$code]['chg'] += $amount;
71 $codes[$code]['bal'] += $amount;
73 // Pass the code type, code and code_text fields
74 // Although not all used yet, useful information
75 // to improve the statement reporting etc.
76 $codes[$code]['code_type'] = $row['code_type'];
77 $codes[$code]['code_value'] = $row['code'];
78 $codes[$code]['modifier'] = $row['modifier'];
79 $codes[$code]['code_text'] = $row['code_text'];
81 // Add the details if they want 'em.
82 if ($with_detail) {
83 if (!$codes[$code]['dtl']) {
84 $codes[$code]['dtl'] = array();
87 $tmp = array();
88 $tmp['chg'] = $amount;
89 $tmpkey = " " . $keysuff1++;
90 $codes[$code]['dtl'][$tmpkey] = $tmp;
94 // Get charges from product sales.
95 $query = "SELECT s.drug_id, s.sale_date, s.fee, s.quantity " .
96 "FROM drug_sales AS s " .
97 "WHERE " .
98 "s.pid = ? AND s.encounter = ? AND s.fee != 0 " .
99 "ORDER BY s.sale_id";
100 $res = sqlStatement($query, array($patient_id, $encounter_id));
101 while ($row = sqlFetchArray($res)) {
102 $amount = sprintf('%01.2f', $row['fee']);
103 $code = 'PROD:' . $row['drug_id'];
104 $codes[$code]['chg'] += $amount;
105 $codes[$code]['bal'] += $amount;
106 // Add the details if they want 'em.
107 if ($with_detail) {
108 if (!$codes[$code]['dtl']) {
109 $codes[$code]['dtl'] = array();
112 $tmp = array();
113 $tmp['chg'] = $amount;
114 $tmpkey = " " . $keysuff1++;
115 $codes[$code]['dtl'][$tmpkey] = $tmp;
118 // Get insurance data for stuff
119 $ins_data = array();
120 $res = sqlStatement("SELECT insurance_data.type as type, insurance_companies.name as name " .
121 "FROM insurance_data " .
122 "INNER JOIN insurance_companies ON insurance_data.provider = insurance_companies.id " .
123 "WHERE insurance_data.pid = ?", array($patient_id));
124 while ($row = sqlFetchArray($res)) {
125 $ins_data[$row['type']] = $row['name'];
127 // Get payments and adjustments. (includes copays)
128 $res = sqlStatement("SELECT " .
129 "a.code_type, a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, a.reason_code, " .
130 "a.post_time, a.session_id, a.sequence_no, a.account_code, a.follow_up_note, " .
131 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
132 ",i.name " .
133 "FROM ar_activity AS a " .
134 "LEFT OUTER JOIN ar_session AS s ON s.session_id = a.session_id " .
135 "LEFT OUTER JOIN insurance_companies AS i ON i.id = s.payer_id " .
136 "WHERE a.deleted IS NULL AND a.pid = ? AND a.encounter = ? " .
137 "ORDER BY s.check_date, a.sequence_no", array($patient_id, $encounter_id));
138 while ($row = sqlFetchArray($res)) {
139 $code = $row['code'];
140 if (!$code) {
141 $code = "Unknown";
144 if ($row['modifier']) {
145 $code .= ':' . $row['modifier'];
148 $ins_id = 0 + $row['payer_id'];
149 $codes[$code]['bal'] -= $row['pay_amount'];
150 $codes[$code]['bal'] -= $row['adj_amount'];
151 $codes[$code]['chg'] -= $row['adj_amount'];
152 $codes[$code]['adj'] += $row['adj_amount'];
153 if ($ins_id) {
154 $codes[$code]['ins'] = $ins_id;
157 // Add the details if they want 'em.
158 if ($with_detail) {
159 if (!$codes[$code]['dtl']) {
160 $codes[$code]['dtl'] = array();
163 $tmp = array();
164 $paydate = empty($row['deposit_date']) ? substr($row['post_time'], 0, 10) : $row['deposit_date'];
165 if ($row['pay_amount'] != 0) {
166 $tmp['pmt'] = $row['pay_amount'];
169 if (isset($row['reason_code'])) {
170 $tmp['msp'] = $row['reason_code'];
173 if ($row['adj_amount'] != 0 || $row['pay_amount'] == 0) {
174 $tmp['chg'] = 0 - $row['adj_amount'];
175 // $tmp['rsn'] = (empty($row['memo']) || empty($row['session_id'])) ? 'Unknown adjustment' : $row['memo'];
176 $tmp['rsn'] = empty($row['memo']) ? $row['follow_up_note'] : $row['memo'];
177 $tmp['rsn'] = str_replace("Ins1", $ins_data['primary'], $tmp['rsn']);
178 $tmp['rsn'] = str_replace("Ins2", $ins_data['secondary'], $tmp['rsn']);
179 $tmp['rsn'] = str_replace("Ins3", $ins_data['tertiary'], $tmp['rsn']);
180 $tmpkey = $paydate . $keysuff1++;
181 } else {
182 $tmpkey = $paydate . $keysuff2++;
185 if ($row['account_code'] == "PCP") {
186 //copay
187 $tmp['src'] = 'Pt Paid';
188 } else {
189 $tmp['src'] = empty($row['session_id']) ? $row['memo'] : $row['reference'];
192 $tmp['insurance_company'] = substr($row['name'], 0, 10);
193 if ($ins_id) {
194 $tmp['ins'] = $ins_id;
197 $tmp['plv'] = $row['payer_type'];
198 $tmp['arseq'] = $row['sequence_no'];
199 $codes[$code]['dtl'][$tmpkey] = $tmp;
203 return $codes;
206 // This determines the party from whom payment is currently expected.
207 // Returns: -1=Nobody, 0=Patient, 1=Ins1, 2=Ins2, 3=Ins3.
208 // for Integrated A/R.
210 public static function arResponsibleParty($patient_id, $encounter_id)
212 $row = sqlQuery("SELECT date, last_level_billed, last_level_closed " .
213 "FROM form_encounter WHERE " .
214 "pid = ? AND encounter = ? " .
215 "ORDER BY id DESC LIMIT 1", array($patient_id, $encounter_id));
216 if (empty($row)) {
217 return -1;
220 $next_level = $row['last_level_closed'] + 1;
221 if ($next_level <= $row['last_level_billed']) {
222 return $next_level;
225 if (SLEOB::arGetPayerID($patient_id, substr($row['date'], 0, 10), $next_level)) {
226 return $next_level;
229 // There is no unclosed insurance, so see if there is an unpaid balance.
230 // Currently hoping that form_encounter.balance_due can be discarded.
231 $balance = 0;
232 $codes = self::arGetInvoiceSummary($patient_id, $encounter_id);
233 foreach ($codes as $cdata) {
234 $balance += $cdata['bal'];
237 if ($balance > 0) {
238 return 0;
241 return -1;