improved prior 8.2 fix
[openemr.git] / library / checkout_receipt_array.inc.php
blob656b65265930ac5cc52085f8486389e74d07bc89
1 <?php
3 /**
4 * checkout_receipt_array.inc.php
6 * This provides a function to build an array of data for printing on a checkout
7 * receipt. The idea is to support a receipt-printing script that is very easy
8 * to understand and customize and does not have to be concerned with where the
9 * data comes from.
11 * LICENSE: This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 3
14 * of the License, or (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see
21 * http://www.gnu.org/licenses/licenses.html#GPL .
23 * @package OpenEMR
24 * @license http://www.gnu.org/licenses/licenses.html#GPL GNU GPL V3+
25 * @author Rod Roark <rod@sunsetsystems.com>
26 * @copyright Copyright (c) 2013-2021 Rod Roark <rod@sunsetsystems.com>
27 * @link http://www.open-emr.org
30 // Get a list item's title, translated if appropriate.
31 function getAdjustTitle($option)
33 $row = sqlQuery(
34 "SELECT title, notes FROM list_options WHERE " .
35 "list_id = 'adjreason' AND option_id = ? AND activity = 1",
36 array($option)
38 if (empty($row['title'])) {
39 return $option;
41 return xl_list_label($row['title']);
44 // Store a receipt line item.
46 function receiptArrayDetailLine(
47 &$aReceipt,
48 $code_type,
49 $code,
50 $description,
51 $quantity,
52 $charge,
53 $billtime = '',
54 $tax = 0,
55 $chargecat = ''
56 ) {
57 $adjust = 0;
58 $adjreason = '';
60 // This supports Suriname insurers.
61 // It is set to true when the Customer is set and its notes include "=Ins".
62 $insurer = false;
63 if ($chargecat) {
64 $tmprow = sqlQuery(
65 "SELECT notes FROM list_options WHERE list_id = 'chargecats' AND " .
66 "option_id = ? AND activity = 1 AND notes LIKE '%=Ins%' LIMIT 1",
67 array($chargecat)
69 if (!empty($tmprow['notes'])) {
70 $insurer = true;
74 // If an invoice level adjustment, get it into the right column.
75 if ($code_type === '') {
76 $adjust = 0 - $charge;
77 $charge = 0;
78 } else {
79 // Otherwise pull out any adjustments matching this line item.
80 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
81 // Total and clear matching adjustments in $aReceipt['_adjusts'].
82 for ($i = 0; $i < count($aReceipt['_adjusts']); ++$i) {
83 if (
84 $aReceipt['_adjusts'][$i]['code_type'] == $code_type &&
85 $aReceipt['_adjusts'][$i]['code'] == $code &&
86 $aReceipt['_adjusts'][$i]['post_time'] == $billtime
87 ) {
88 $adjust += $aReceipt['_adjusts'][$i]['adj_amount'];
89 if ($aReceipt['_adjusts'][$i]['memo']) {
90 $adjreason = getAdjustTitle($aReceipt['_adjusts'][$i]['memo']);
92 $aReceipt['_adjusts'][$i]['adj_amount'] = 0;
98 $charge = sprintf('%01.2f', $charge);
99 $total = sprintf('%01.2f', $charge - $adjust);
100 if (empty($quantity)) {
101 $quantity = 1;
103 $price = sprintf('%01.4f', $charge / $quantity);
104 $tmp = sprintf('%01.2f', $price);
105 if ($price == $tmp) {
106 $price = $tmp; // converts xx.xx00 to xx.xx.
109 $aReceipt['items'][] = array(
110 'code_type' => $code_type,
111 'code' => $code,
112 'description' => $description,
113 'price' => $price,
114 'quantity' => $quantity,
115 'charge' => $charge,
116 'adjustment' => sprintf('%01.2f', $adjust),
117 'adjreason' => $adjreason,
118 'total' => $total,
119 'insurer' => $insurer,
120 'tax' => $tax,
123 $aReceipt['total_price'] = sprintf('%01.2f', $aReceipt['total_price' ] + $price);
124 $aReceipt['total_quantity'] = sprintf('%01.2f', $aReceipt['total_quantity' ] + $quantity);
125 $aReceipt['total_charges'] = sprintf('%01.2f', $aReceipt['total_charge' ] + $charge);
126 $aReceipt['total_adjustments'] = sprintf('%01.2f', $aReceipt['total_adjustments'] + $adjust);
127 $aReceipt['total_totals'] = sprintf('%01.2f', $aReceipt['total_totals' ] + $total);
130 // Store a receipt payment line.
132 function receiptArrayPaymentLine(&$aReceipt, $paydate, $amount, $description = '', $method = '')
134 $amount = sprintf('%01.2f', $amount);
135 $aReceipt['payments'][] = array(
136 'date' => $paydate,
137 'method' => $method,
138 'description' => $description,
139 'amount' => $amount,
141 $aReceipt['total_payments'] += $amount;
144 // Generate a receipt data array from the last-billed invoice for this patient,
145 // or from the specified encounter.
147 function generateReceiptArray($patient_id, $encounter = 0, $billtime = '')
149 // Get the most recent invoice data or that for the specified encounter.
150 $query = "SELECT " .
151 "fe.id, fe.date, fe.encounter, fe.facility_id, fe.invoice_refno, " .
152 "u.fname, u.mname, u.lname " .
153 "FROM form_encounter AS fe " .
154 "LEFT JOIN users AS u ON u.id = fe.provider_id " .
155 "WHERE fe.pid = ? ";
156 $binds = array($patient_id);
157 if ($encounter) {
158 $query .= "AND encounter = ?";
159 $binds[] = $encounter;
160 } else {
161 $query .= "ORDER BY id DESC LIMIT 1";
163 $ferow = sqlQuery($query, $binds);
164 if (empty($ferow)) {
165 die(xlt("This patient has no activity."));
167 $trans_id = $ferow['id'];
168 $encounter = $ferow['encounter'];
169 $svcdate = substr($ferow['date'], 0, 10);
170 $invoice_refno = $ferow['invoice_refno'];
171 $docname = '';
172 if (!empty($ferow['fname'])) {
173 $docname = trim($ferow['fname']);
175 if (!empty($ferow['mname'])) {
176 if ($docname) {
177 $docname .= ' ';
179 $docname .= trim($ferow['fname']);
181 if (!empty($ferow['lname'])) {
182 if ($docname) {
183 $docname .= ' ';
185 $docname .= trim($ferow['lname']);
188 // Get details for the visit's facility and organization facility.
189 $frow = getFacility($ferow['facility_id']);
190 $orgrow = getFacility(0);
192 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
194 // Get text for the logged-in user's name (first middle last).
195 $username = "UID: " . $_SESSION["authUserID"];
196 $userrow = sqlQuery(
197 "SELECT id, username, fname, mname, lname FROM users " .
198 "WHERE id = ?",
199 array($_SESSION["authUserID"])
201 if ($userrow['id']) {
202 if (!empty($userrow['fname'])) {
203 $username = $userrow['fname'];
205 if (!empty($userrow['mname'])) {
206 if (!empty($username)) {
207 $username .= ' ';
209 $username .= $userrow['mname'];
211 if (!empty($userrow['lname'])) {
212 if (!empty($username)) {
213 $username .= ' ';
215 $username .= $userrow['lname'];
219 // Compute numbers for summary on right side of page.
220 $head_begbal = get_patient_balance_excluding($patient_id, $encounter);
222 $query = "SELECT SUM(fee) AS amount FROM billing WHERE " .
223 "pid = ? AND encounter = ? AND activity = 1 AND " .
224 "code_type != 'COPAY'";
225 $binds = array($patient_id, $encounter);
226 if ($billtime) {
227 $query .= " AND billed = 1 AND bill_date <= ?";
228 $binds[] = $billtime;
230 $row = sqlQuery($query, $binds);
231 $head_charges = $row['amount'];
233 $query = "SELECT SUM(fee) AS amount FROM drug_sales WHERE " .
234 "pid = ? AND encounter = ?";
235 $binds = array($patient_id, $encounter);
236 if ($billtime) {
237 $query .= " AND billed = 1 AND bill_date <= ?";
238 $binds[] = $billtime;
240 $row = sqlQuery($query, $binds);
241 $head_charges += $row['amount'];
243 $query = "SELECT SUM(pay_amount) AS payments, " .
244 "SUM(adj_amount) AS adjustments FROM ar_activity WHERE " .
245 "deleted IS NULL AND pid = ? AND encounter = ?";
246 $binds = array($patient_id, $encounter);
247 if ($billtime) {
248 $query .= " AND post_time <= ?";
249 $binds[] = $billtime;
251 $row = sqlQuery($query, $binds);
252 $head_charges -= $row['adjustments'];
253 $head_payments = $row['payments'];
255 $query = "SELECT SUM(fee) AS amount FROM billing WHERE " .
256 "pid = ? AND encounter = ? AND activity = 1 AND " .
257 "code_type = 'COPAY'";
258 $binds = array($patient_id, $encounter);
259 if ($billtime) {
260 $query .= " AND billed = 1 AND bill_date <= ?";
261 $binds[] = $billtime;
263 $row = sqlQuery($query, $binds);
264 $head_payments -= $row['amount'];
266 // This is the amount due for the encounter as a whole up to this point.
267 $encounter_due = $head_charges - $head_payments;
268 $head_endbal = $head_begbal + $encounter_due;
270 $aReceipt = array(
271 'encounter_id' => $encounter,
272 'encounter_date' => $svcdate,
273 'invoice_refno' => $invoice_refno,
274 'patient_id' => $patient_id,
275 'patient_pubpid' => $patdata['pubpid'],
276 'patient_fname' => $patdata['fname'],
277 'patient_mname' => $patdata['mname'],
278 'patient_lname' => $patdata['lname'],
279 'patient_street' => $patdata['street'],
280 'patient_city' => $patdata['city'],
281 'patient_state' => $patdata['state'],
282 'patient_zip' => $patdata['postal_code'],
283 'facility_id' => $frow['id'],
284 'facility_name' => $frow['name'],
285 'facility_street' => $frow['street'],
286 'facility_city' => $frow['city'],
287 'facility_state' => $frow['state'],
288 'facility_zip' => $frow['postal_code'],
289 'facility_phone' => $frow['phone'],
290 'facility_fax' => $frow['fax'],
291 'facility_url' => empty($frow['website']) ? '' : $frow['website'],
292 'organization_name' => $orgrow['name'],
293 'docname' => $docname,
294 'username' => $username,
295 'userlogin' => $userrow['username'],
296 'starting_balance' => $head_begbal,
297 'ending_balance' => $head_endbal,
298 'items' => array(),
299 'payments' => array(),
300 'total_price' => 0,
301 'total_quantity' => 0,
302 'total_charges' => 0,
303 'total_adjustments' => 0,
304 'total_totals' => 0,
305 'total_payments' => 0,
306 'checkout_prvbal' => 0, // see calculation at end of this function
307 'encounter_due' => $encounter_due,
308 'cashier' => '',
311 // Get timestamp of the previous checkout, if any.
312 $prevtime = '1900-01-01 00:00:00';
313 if ($billtime) {
314 $tmp = sqlQuery(
315 "(SELECT bill_date FROM billing WHERE pid = ? AND encounter = ? AND activity = 1 AND billed = 1 AND bill_date < ?) " .
316 "UNION " .
317 "(SELECT bill_date FROM drug_sales WHERE pid = ? AND encounter = ? AND billed = 1 AND bill_date < ?) " .
318 "ORDER BY bill_date DESC LIMIT 1",
319 array($patient_id, $encounter, $billtime, $patient_id, $encounter, $billtime)
321 if (!empty($tmp['bill_date'])) {
322 $prevtime = $tmp['bill_date'];
326 // Create array aAdjusts from ar_activity rows for $inv_encounter.
327 $aReceipt['_adjusts'] = array();
328 $ares = sqlStatement(
329 "SELECT " .
330 "a.payer_type, a.adj_amount, a.memo, a.code_type, a.code, a.post_time, " .
331 "s.session_id, s.reference, s.check_date " .
332 "FROM ar_activity AS a " .
333 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
334 "a.deleted IS NULL AND a.pid = ? AND a.encounter = ? AND " .
335 "(a.adj_amount != 0 || a.pay_amount = 0)",
336 array($patient_id, $encounter)
338 while ($arow = sqlFetchArray($ares)) {
339 if ($billtime && $arow['post_time'] != $billtime) {
340 continue;
342 $aReceipt['_adjusts'][] = $arow;
345 // Product sales
346 $inres = sqlStatement(
347 "SELECT s.sale_id, s.sale_date, s.fee, s.billed, " .
348 "s.bill_date, s.quantity, s.drug_id, s.selector, s.user, s.chargecat, d.name " .
349 "FROM drug_sales AS s LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
350 "WHERE s.pid = ? AND s.encounter = ? " .
351 "ORDER BY s.sale_id",
352 array($patient_id, $encounter)
354 while ($inrow = sqlFetchArray($inres)) {
355 if ($billtime && $inrow['bill_date'] != $billtime) {
356 continue;
358 $tmpname = $inrow['name'];
359 if ($tmpname !== $inrow['selector']) {
360 $tmpname .= ' / ' . $inrow['selector'];
362 $taxrow = sqlQuery(
363 "SELECT SUM(fee) AS fee FROM billing WHERE " .
364 "pid = ? AND encounter = ? AND code_type = 'TAX' AND activity = 1 AND ndc_info = ?",
365 array($patient_id, $encounter, 'P:' . $inrow['sale_id'])
367 $tax = $taxrow['fee'];
368 receiptArrayDetailLine(
369 $aReceipt,
370 'PROD',
371 $inrow['drug_id'],
372 $tmpname,
373 $inrow['quantity'],
374 $inrow['fee'],
375 $inrow['bill_date'],
376 $tax,
377 $inrow['chargecat']
379 if (!empty($inrow['user'])) {
380 $aReceipt['cashier'] = $inrow['user'];
384 // Service items. Use short code description when available.
385 $inres = sqlStatement(
386 "SELECT b.*, c.code_text_short, u.username FROM billing AS b " .
387 "LEFT JOIN code_types AS ct ON ct.ct_key = b.code_type " .
388 "LEFT JOIN codes AS c ON c.code_type = ct.ct_id AND c.code = b.code AND c.modifier = b.modifier " .
389 "LEFT JOIN users AS u on u.id = b.user " .
390 "WHERE " .
391 "b.pid = ? AND b.encounter = ? AND " .
392 "b.code_type != 'COPAY' AND b.code_type != 'TAX' AND b.activity = 1 AND " .
393 "(ct.ct_id IS NULL OR ct.ct_fee = 1 OR ct.ct_diag = 0) " .
394 "ORDER BY b.id",
395 array($patient_id, $encounter)
397 while ($inrow = sqlFetchArray($inres)) {
398 if ($billtime && $inrow['bill_date'] != $billtime) {
399 continue;
401 $code_text = empty($inrow['code_text_short']) ? $inrow['code_text'] : $inrow['code_text_short'];
402 $taxrow = sqlQuery(
403 "SELECT SUM(fee) AS fee FROM billing WHERE " .
404 "pid = ? AND encounter = ? AND code_type = 'TAX' AND activity = 1 AND ndc_info = ?",
405 array($patient_id, $encounter, 'S:' . $inrow['id'])
407 $tax = $taxrow['fee'];
408 receiptArrayDetailLine(
409 $aReceipt,
410 $inrow['code_type'],
411 $inrow['code'],
412 $code_text,
413 $inrow['units'],
414 $inrow['fee'],
415 $inrow['bill_date'],
416 $tax,
417 $inrow['chargecat']
419 if (!empty($inrow['username'])) {
420 $aReceipt['cashier'] = $inrow['username'];
424 // Write any adjustments left in the aAdjusts array.
425 foreach ($aReceipt['_adjusts'] as $arow) {
426 if ($arow['adj_amount'] == 0) {
427 continue;
429 if ($billtime && $arow['post_time'] != $billtime) {
430 continue;
432 // $payer = empty($arow['payer_type']) ? 'Pt' : ('Ins' . $arow['payer_type']);
433 receiptArrayDetailLine(
434 $aReceipt,
436 xl('Adjustment'),
437 getAdjustTitle($arow['memo']),
439 0 - $arow['adj_amount']
443 // Tax items.
444 $inres = sqlStatement(
445 "SELECT * FROM billing WHERE " .
446 "pid = ? AND encounter = ? AND code_type = 'TAX' AND activity = 1 " .
447 "ORDER BY id",
448 array($patient_id, $encounter)
450 while ($inrow = sqlFetchArray($inres)) {
451 if ($billtime && $inrow['bill_date'] != $billtime) {
452 continue;
454 receiptArrayDetailLine(
455 $aReceipt,
456 $inrow['code_type'],
457 $inrow['code'],
458 $inrow['code_text'],
460 $inrow['fee']
464 $payments = 0;
466 // Get co-pays.
467 $inres = sqlStatement(
468 "SELECT fee, code_text, bill_date FROM billing WHERE " .
469 "pid = ? AND encounter = ? AND " .
470 "code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
471 "ORDER BY id",
472 array($patient_id, $encounter)
474 while ($inrow = sqlFetchArray($inres)) {
475 if ($billtime && $inrow['bill_date'] != $billtime) {
476 continue;
478 $payments -= sprintf('%01.2f', $inrow['fee']);
479 receiptArrayPaymentLine(
480 $aReceipt,
481 $svcdate,
482 0 - $inrow['fee'],
483 $inrow['code_text'],
484 'COPAY'
488 // Get other payments.
489 $inres = sqlStatement(
490 "SELECT " .
491 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, a.post_time, " .
492 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
493 "FROM ar_activity AS a " .
494 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
495 "a.deleted IS NULL AND a.pid = ? AND a.encounter = ? AND " .
496 "a.pay_amount != 0 " .
497 "ORDER BY a.post_time, s.check_date, a.sequence_no",
498 array($patient_id, $encounter)
500 $payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
501 while ($inrow = sqlFetchArray($inres)) {
502 $meth = $inrow['memo'];
503 if ($billtime && $inrow['post_time'] != $billtime) {
504 if ($inrow['post_time'] > $billtime || $inrow['post_time'] <= $prevtime) {
505 continue;
507 // This is the case of payments after the previous checkout and before this checkout.
508 $meth = xl('Previous');
510 $payments += sprintf('%01.2f', $inrow['pay_amount']);
511 receiptArrayPaymentLine(
512 $aReceipt,
513 $svcdate,
514 $inrow['pay_amount'],
515 $payer . ' ' . $inrow['reference'],
516 $meth
520 // Previous balance for this checkout is local to the encounter, and is the total balance
521 // for the encounter up to this time minus total balance for this checkout.
522 // If there is no $billtime then this should always be zero.
523 $aReceipt['checkout_prvbal'] = $aReceipt['encounter_due'] - $aReceipt['total_totals'] + $aReceipt['total_payments'];
525 // Get cashier as the person who did the checkout. If there was no checkout then it will
526 // remain as the person who entered the Fee Sheet.
527 $query = "SELECT u.username FROM ar_activity AS a" .
528 " JOIN users AS u ON u.id = a.post_user" .
529 " WHERE" .
530 " a.deleted IS NULL AND a.pid = ? AND a.encounter = ?";
531 $binds = array($patient_id, $encounter);
532 if ($billtime) {
533 $query .= " AND a.post_time = ?";
534 $binds[] = $billtime;
536 $query .= " ORDER BY a.sequence_no DESC LIMIT 1";
537 $inrow = sqlQuery($query, $binds);
538 if (!empty($inrow['username'])) {
539 $aReceipt['cashier'] = $inrow['username'];
542 return $aReceipt;
545 // Get the array of checkout timestamps for the specified visit.
547 function craGetTimestamps($patient_id, $encounter_id)
549 $ret = array();
550 $res = sqlStatement(
551 "(SELECT bill_date FROM billing WHERE pid = ? AND encounter = ? AND activity = 1 AND billed = 1) " .
552 "UNION " .
553 "(SELECT bill_date FROM drug_sales WHERE pid = ? AND encounter = ? AND billed = 1) " .
554 "ORDER BY bill_date",
555 array($patient_id, $encounter_id, $patient_id, $encounter_id)
557 while ($row = sqlFetchArray($res)) {
558 $ret[] = $row['bill_date'];
560 return $ret;