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
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 .
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)
34 "SELECT title, notes FROM list_options WHERE " .
35 "list_id = 'adjreason' AND option_id = ? AND activity = 1",
38 if (empty($row['title'])) {
41 return xl_list_label($row['title']);
44 // Store a receipt line item.
46 function receiptArrayDetailLine(
60 // This supports Suriname insurers.
61 // It is set to true when the Customer is set and its notes include "=Ins".
65 "SELECT notes FROM list_options WHERE list_id = 'chargecats' AND " .
66 "option_id = ? AND activity = 1 AND notes LIKE '%=Ins%' LIMIT 1",
69 if (!empty($tmprow['notes'])) {
74 // If an invoice level adjustment, get it into the right column.
75 if ($code_type === '') {
76 $adjust = 0 - $charge;
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) {
84 $aReceipt['_adjusts'][$i]['code_type'] == $code_type &&
85 $aReceipt['_adjusts'][$i]['code'] == $code &&
86 $aReceipt['_adjusts'][$i]['post_time'] == $billtime
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)) {
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,
112 'description' => $description,
114 'quantity' => $quantity,
116 'adjustment' => sprintf('%01.2f', $adjust),
117 'adjreason' => $adjreason,
119 'insurer' => $insurer,
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(
138 'description' => $description,
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.
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 " .
156 $binds = array($patient_id);
158 $query .= "AND encounter = ?";
159 $binds[] = $encounter;
161 $query .= "ORDER BY id DESC LIMIT 1";
163 $ferow = sqlQuery($query, $binds);
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'];
172 if (!empty($ferow['fname'])) {
173 $docname = trim($ferow['fname']);
175 if (!empty($ferow['mname'])) {
179 $docname .= trim($ferow['fname']);
181 if (!empty($ferow['lname'])) {
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"];
197 "SELECT id, username, fname, mname, lname FROM users " .
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)) {
209 $username .= $userrow['mname'];
211 if (!empty($userrow['lname'])) {
212 if (!empty($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);
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);
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);
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);
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;
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,
299 'payments' => array(),
301 'total_quantity' => 0,
302 'total_charges' => 0,
303 'total_adjustments' => 0,
305 'total_payments' => 0,
306 'checkout_prvbal' => 0, // see calculation at end of this function
307 'encounter_due' => $encounter_due,
311 // Get timestamp of the previous checkout, if any.
312 $prevtime = '1900-01-01 00:00:00';
315 "(SELECT bill_date FROM billing WHERE pid = ? AND encounter = ? AND activity = 1 AND billed = 1 AND bill_date < ?) " .
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(
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) {
342 $aReceipt['_adjusts'][] = $arow;
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) {
358 $tmpname = $inrow['name'];
359 if ($tmpname !== $inrow['selector']) {
360 $tmpname .= ' / ' . $inrow['selector'];
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(
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 " .
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) " .
395 array($patient_id, $encounter)
397 while ($inrow = sqlFetchArray($inres)) {
398 if ($billtime && $inrow['bill_date'] != $billtime) {
401 $code_text = empty($inrow['code_text_short']) ?
$inrow['code_text'] : $inrow['code_text_short'];
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(
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) {
429 if ($billtime && $arow['post_time'] != $billtime) {
432 // $payer = empty($arow['payer_type']) ? 'Pt' : ('Ins' . $arow['payer_type']);
433 receiptArrayDetailLine(
437 getAdjustTitle($arow['memo']),
439 0 - $arow['adj_amount']
444 $inres = sqlStatement(
445 "SELECT * FROM billing WHERE " .
446 "pid = ? AND encounter = ? AND code_type = 'TAX' AND activity = 1 " .
448 array($patient_id, $encounter)
450 while ($inrow = sqlFetchArray($inres)) {
451 if ($billtime && $inrow['bill_date'] != $billtime) {
454 receiptArrayDetailLine(
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 " .
472 array($patient_id, $encounter)
474 while ($inrow = sqlFetchArray($inres)) {
475 if ($billtime && $inrow['bill_date'] != $billtime) {
478 $payments -= sprintf('%01.2f', $inrow['fee']);
479 receiptArrayPaymentLine(
488 // Get other payments.
489 $inres = sqlStatement(
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) {
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(
514 $inrow['pay_amount'],
515 $payer . ' ' . $inrow['reference'],
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" .
530 " a.deleted IS NULL AND a.pid = ? AND a.encounter = ?";
531 $binds = array($patient_id, $encounter);
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'];
545 // Get the array of checkout timestamps for the specified visit.
547 function craGetTimestamps($patient_id, $encounter_id)
551 "(SELECT bill_date FROM billing WHERE pid = ? AND encounter = ? AND activity = 1 AND billed = 1) " .
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'];