Various changes and fixes (#7424)
[openemr.git] / interface / patient_file / pos_checkout_ippf.php
blobe730e8490e0ea3093e22a745c19a5292c814e223
1 <?php
3 /**
4 * Checkout Module.
6 * This module supports a popup window to handle patient checkout
7 * as a point-of-sale transaction. Support for in-house drug sales
8 * is included.
10 * <pre>
11 * Important notes about system design:
12 * (1) Drug sales may or may not be associated with an encounter;
13 * they are if they are paid for concurrently with an encounter, or
14 * if they are "product" (non-prescription) sales via the Fee Sheet.
15 * UPDATE: ENCOUNTER IS NOW ALWAYS REQUIRED.
16 * (2) Drug sales without an encounter will have 20YYMMDD, possibly
17 * with a suffix, as the encounter-number portion of their invoice
18 * number.
19 * (3) Payments are saved as AR only, don't mess with the billing table.
20 * See library/classes/WSClaim.class.php for posting code.
21 * (4) On checkout, the billing and drug_sales table entries are marked
22 * as billed and so become unavailable for further billing.
23 * (5) Receipt printing must be a separate operation from payment,
24 * and repeatable.
26 * TBD:
27 * If this user has 'irnpool' set
28 * on display of checkout form
29 * show pending next invoice number
30 * on applying checkout
31 * save next invoice number to form_encounter
32 * compute new next invoice number
33 * on receipt display
34 * show invoice number
35 * </pre>
37 * @package OpenEMR
38 * @link http://www.open-emr.org
39 * @author Rod Roark <rod@sunsetsystems.com>
40 * @author Brady Miller <brady.g.miller@gmail.com>
41 * @author Ranganath Pathak <pathak@scrs1.org>
42 * @author Jerry Padgett <sjpadgett@gmail.com>
43 * @author Stephen Waite <stephen.waite@cmsvt.com>
44 * @copyright Copyright (c) 2006-2021 Rod Roark <rod@sunsetsystems.com>
45 * @copyright Copyright (c) 2017-2019 Brady Miller <brady.g.miller@gmail.com>
46 * @copyright Copyright (c) 2018 Ranganath Pathak <pathak@scrs1.org>
47 * @copyright Copyright (c) 2019 Jerry Padgett <sjpadgett@gmail.com>
48 * @copyright Copyright (c) 2019 Stephen Waite <stephen.waite@cmsvt.com>
49 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
52 require_once("$srcdir/patient.inc.php");
53 require_once("$srcdir/options.inc.php");
54 require_once("../../custom/code_types.inc.php");
55 require_once("$srcdir/checkout_receipt_array.inc.php");
56 require_once("$srcdir/appointment_status.inc.php");
58 use OpenEMR\Billing\BillingUtilities;
59 use OpenEMR\Billing\SLEOB;
60 use OpenEMR\Common\Acl\AclMain;
61 use OpenEMR\Common\Csrf\CsrfUtils;
62 use OpenEMR\Common\Twig\TwigContainer;
63 use OpenEMR\Core\Header;
64 use OpenEMR\OeUI\OemrUI;
65 use OpenEMR\Services\FacilityService;
67 $facilityService = new FacilityService();
69 // Change this to get the old appearance.
70 $TAXES_AFTER_ADJUSTMENT = true;
72 $currdecimals = intval($GLOBALS['currency_decimals'] ?? 2);
74 // Details default to yes now.
75 $details = (!isset($_GET['details']) || !empty($_GET['details'])) ? 1 : 0;
77 $patient_id = empty($_GET['ptid']) ? $pid : intval($_GET['ptid']);
78 $encounter_id = empty($_GET['enid']) ? 0 : intval($_GET['enid']);
79 $checkout_id = empty($_GET['coid']) ? '' : $_GET['coid']; // timestamp of checkout
81 // This flag comes from the Fee Sheet form and perhaps later others.
82 $rapid_data_entry = empty($_GET['rde']) ? 0 : 1;
84 if (
85 !AclMain::aclCheckCore('admin', 'super') &&
86 !AclMain::aclCheckCore('acct', 'bill') &&
87 !AclMain::aclCheckCore('acct', 'disc')
88 ) {
89 echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Client Receipt")]);
90 exit;
93 // This will be used for SQL timestamps that we write.
94 $this_bill_date = date('Y-m-d H:i:s');
96 // Get the patient's name and chart number.
97 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
99 // Adjustments from the ar_activity table.
100 $aAdjusts = array();
102 // Holds possible javascript error messages.
103 $alertmsg = '';
105 // Format a money amount with decimals but no other decoration.
106 // Second argument is used when extra precision is required.
107 function formatMoneyNumber($value, $extradecimals = 0)
109 return sprintf('%01.' . ($GLOBALS['currency_decimals'] + $extradecimals) . 'f', $value);
112 // Get a list item's title, translated if appropriate.
114 function getListTitle($list, $option)
116 $row = sqlQuery(
117 "SELECT title FROM list_options WHERE list_id = ? AND option_id = ? AND activity = 1",
118 array($list, $option)
120 if (empty($row['title'])) {
121 return $option;
123 return xl_list_label($row['title']);
126 function generate_layout_display_field($formid, $fieldid, $currvalue)
128 $frow = sqlQuery(
129 "SELECT * FROM layout_options WHERE form_id = ? AND field_id = ? LIMIT 1",
130 array($formid, $fieldid)
132 if (empty($frow)) {
133 return $currvalue;
135 return generate_display_field($frow, $currvalue);
138 // This creates and loads the array $aAdjusts of adjustment data for this encounter.
140 function load_adjustments($patient_id, $encounter_id)
142 global $aAdjusts;
143 // Create array aAdjusts from ar_activity rows for $encounter_id.
144 $aAdjusts = array();
145 $ares = sqlStatement(
146 "SELECT " .
147 "a.payer_type, a.adj_amount, a.memo, a.code_type, a.code, a.post_time, a.post_date, " .
148 "s.session_id, s.reference, s.check_date, lo.title AS memotitle " .
149 "FROM ar_activity AS a " .
150 "LEFT JOIN list_options AS lo ON lo.list_id = 'adjreason' AND lo.option_id = a.memo AND " .
151 "lo.activity = 1 " .
152 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
153 "a.pid = ? AND a.encounter = ? AND a.deleted IS NULL AND " .
154 "( a.adj_amount != 0 OR a.pay_amount = 0 ) " .
155 "ORDER BY s.check_date, a.sequence_no",
156 array($patient_id, $encounter_id)
158 while ($arow = sqlFetchArray($ares)) {
159 if (empty($arow['memotitle'])) {
160 $arow['memotitle'] = $arow['memo'];
162 $aAdjusts[] = $arow;
166 // Total and clear adjustments in $aAdjusts matching this line item. Should only
167 // happen for billed items, and matching includes the billing timestamp in order
168 // to handle the case of multiple checkouts.
169 function pull_adjustment($code_type, $code, $billtime, &$memo)
171 global $aAdjusts;
172 $adjust = 0;
173 $memo = '';
174 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
175 for ($i = 0; $i < count($aAdjusts); ++$i) {
176 if (
177 $aAdjusts[$i]['code_type'] == $code_type && $aAdjusts[$i]['code'] == $code &&
178 $aAdjusts[$i]['post_time'] == $billtime &&
179 ($aAdjusts[$i]['adj_amount'] != 0 || $aAdjusts[$i]['memotitle'] !== '')
181 $adjust += $aAdjusts[$i]['adj_amount'];
182 if ($memo && $aAdjusts[$i]['memotitle']) {
183 $memo .= ', ';
185 $memo .= $aAdjusts[$i]['memotitle'];
186 $aAdjusts[$i]['adj_amount'] = 0;
187 $aAdjusts[$i]['memotitle'] = '';
191 return $adjust;
194 // Generate $aTaxNames = array of tax names, and $aInvTaxes = array of taxes for this invoice.
195 // For a given tax ID and line ID, $aInvTaxes[$taxID][$lineID] = tax amount.
196 // $lineID identifies the invoice line item (product or service) that was taxed, and is of the
197 // form S:<billing.id> or P:<drug_sales.sale_id>
198 // Taxes may change from time to time and $aTaxNames reflects only the taxes that were present
199 // for this invoice.
201 function load_taxes($patient_id, $encounter)
203 global $aTaxNames, $aInvTaxes, $taxes;
204 global $num_optional_columns, $rcpt_num_method_columns, $rcpt_num_ref_columns, $rcpt_num_amount_columns;
205 global $form_num_type_columns, $form_num_method_columns, $form_num_ref_columns, $form_num_amount_columns;
207 $aTaxNames = array();
208 $aInvTaxes = array();
209 foreach ($taxes as $taxid => $taxarr) {
210 $aTaxNames[$taxid] = $taxarr[0];
211 $aInvTaxes[$taxid] = array();
214 $taxres = sqlStatement(
215 "SELECT code, fee, ndc_info FROM billing WHERE " .
216 "pid = ? AND encounter = ? AND code_type = 'TAX' AND activity = 1 " .
217 "ORDER BY id",
218 array($patient_id, $encounter)
220 while ($taxrow = sqlFetchArray($taxres)) {
221 $aInvTaxes[$taxrow['code']][$taxrow['ndc_info']] = $taxrow['fee'];
224 // Knowing the number of tax columns we can now compute the total number of optional
225 // columns and from that the colspan values for various things.
226 $num_optional_columns = (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1) +
227 (empty($GLOBALS['gbl_charge_categories']) ? 0 : 1) +
228 (empty($GLOBALS['gbl_checkout_line_adjustments']) ? 0 : 2) +
229 count($aTaxNames);
230 // Compute colspans for receipt payment rows.
231 // What's in play here are columns for Qty, Price, the optionals, and Total.
232 $rcpt_num_method_columns = 1;
233 $rcpt_num_ref_columns = 1;
234 if ($num_optional_columns == 1) {
235 $rcpt_num_method_columns = 2;
236 } else if ($num_optional_columns > 1) {
237 $rcpt_num_method_columns = 3;
238 $rcpt_num_ref_columns = $num_optional_columns - 1;
240 $rcpt_num_amount_columns = 3 + $num_optional_columns - $rcpt_num_method_columns - $rcpt_num_ref_columns;
241 // Compute colspans for form payment rows.
242 $form_num_type_columns = 2;
243 $form_num_method_columns = 1;
244 $form_num_ref_columns = 1;
245 if ($num_optional_columns > 0) {
246 $form_num_method_columns = 2;
248 if ($num_optional_columns > 1) {
249 $form_num_type_columns = 3;
251 $form_num_amount_columns = 5 + $num_optional_columns - $form_num_type_columns - $form_num_method_columns - $form_num_ref_columns;
254 // Use $lineid to match up (and delete) entries in $aInvTaxes with the line.
255 // $lineid looks like: S:<billing.id> or P:<drug_sales.sale_id>.
256 // This writes to the $aTaxes argument and returns the total tax for the line.
257 function pull_tax($lineid, &$aTaxes)
259 global $aInvTaxes;
260 $totlinetax = 0;
261 foreach ($aInvTaxes as $taxid => $taxarr) {
262 $aTaxes[$taxid] = 0;
263 if ($lineid !== '') {
264 foreach ($taxarr as $taxlineid => $tax) {
265 if ($taxlineid === $lineid) {
266 $aTaxes[$taxid] += $tax;
267 $totlinetax += $tax;
268 $aInvTaxes[$taxid][$taxlineid] = 0;
273 // $aTaxes now contains the total of each tax type (keyed on tax ID) for this line item,
274 // and those matched amounts are removed from $aInvTaxes.
275 return $totlinetax;
278 // Output HTML for a receipt line item.
280 function receiptDetailLine(
281 $code_type,
282 $code,
283 $description,
284 $quantity,
285 $charge,
286 &$aTotals = '',
287 $lineid = '',
288 $billtime = '',
289 $postdate = '',
290 $chargecat = ''
292 global $details, $TAXES_AFTER_ADJUSTMENT;
294 // Use $lineid to match up (and delete) entries in $aInvTaxes with the line.
295 $aTaxes = array();
296 $totlinetax = pull_tax($lineid, $aTaxes);
297 // $aTaxes now contains the total of each tax type for this line item, and those matched
298 // amounts are removed from $aInvTaxes.
300 $adjust = 0;
301 $memo = '';
302 $isadjust = false;
304 // If an adjustment, do appropriate interpretation.
305 if ($code_type === '') {
306 $isadjust = true;
307 $adjust = 0 - $charge;
308 $charge = 0;
309 list($payer, $code_type, $code) = explode('|', $code);
310 $memo = $description;
311 $description = $GLOBALS['simplified_demographics'] ? '' : "$payer ";
312 $description .= $code ? xl('Item Adjustment') : xl('Invoice Adjustment');
313 $quantity = '';
314 } else {
315 // Total and clear adjustments in $aAdjusts matching this line item.
316 $adjust += pull_adjustment($code_type, $code, $billtime, $memo);
319 $charge = formatMoneyNumber($charge);
320 $total = formatMoneyNumber($charge + $totlinetax - $adjust);
321 if (empty($quantity)) {
322 $quantity = 1;
324 $price = formatMoneyNumber($charge / $quantity, 2);
325 $tmp = formatMoneyNumber($price);
326 if ($price == $tmp) {
327 $price = $tmp;
329 if (is_array($aTotals)) {
330 $aTotals[0] += $quantity;
331 $aTotals[1] += $price;
332 $aTotals[2] += $charge;
333 $aTotals[3] += $adjust;
334 $aTotals[4] += $total;
335 // Accumulate columns 5 and beyond for taxes.
336 $i = 5;
337 foreach ($aTaxes as $tax) {
338 $aTotals[$i++] += $tax;
342 if (!$details) {
343 return;
345 if (empty($postdate) || substr($postdate, 0, 4) == '0000') {
346 $postdate = $billtime;
348 echo " <tr>\n";
349 echo " <td title='" . xla('Entered') . ' ' .
350 text(oeFormatShortDate($billtime)) . attr(substr($billtime, 10)) . "'>" .
351 text(oeFormatShortDate($postdate)) . "</td>\n";
352 echo " <td>" . text($code) . "</td>\n";
353 echo " <td>" . text($description) . "</td>\n";
354 echo " <td class='text-center'>" . ($isadjust ? '' : $quantity) . "</td>\n";
355 echo " <td class='text-right'>" . text(oeFormatMoney($price, false, true)) . "</td>\n";
357 if (!empty($GLOBALS['gbl_checkout_charges'])) {
358 echo " <td class='text-right'>" . text(oeFormatMoney($charge, false, true)) . "</td>\n";
361 if (!$TAXES_AFTER_ADJUSTMENT) {
362 // Write tax amounts.
363 foreach ($aTaxes as $tax) {
364 echo " <td class='text-right'>" . text(oeFormatMoney($tax, false, true)) . "</td>\n";
368 // Charge Category
369 if (!empty($GLOBALS['gbl_charge_categories'])) {
370 echo " <td class='text-right'>" . text($chargecat) . "</td>\n";
373 // Adjustment and its description.
374 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
375 echo " <td class='text-right'>" . text($memo) . "</td>\n";
376 echo " <td class='text-right'>" . text(oeFormatMoney($adjust, false, true)) . "</td>\n";
379 if ($TAXES_AFTER_ADJUSTMENT) {
380 // Write tax amounts.
381 foreach ($aTaxes as $tax) {
382 echo " <td class='text-right'>" . text(oeFormatMoney($tax, false, true)) . "</td>\n";
386 echo " <td class='text-right'>" . text(oeFormatMoney($total)) . "</td>\n";
387 echo " </tr>\n";
390 // Output HTML for a receipt payment line.
392 function receiptPaymentLine($paydate, $amount, $description = '', $method = '', $refno = '', $billtime = '')
394 global $aTaxNames, $num_optional_columns;
395 global $rcpt_num_method_columns, $rcpt_num_ref_columns, $rcpt_num_amount_columns;
396 $amount = formatMoneyNumber($amount); // make it negative
397 if ($description == 'Pt') {
398 $description = '';
400 // Resolve the payment method portion of the memo to display properly.
401 if (!empty($method)) {
402 $tmp = explode(' ', $method, 2);
403 $method = getListTitle('paymethod', $tmp[0]);
404 if (isset($tmp[1])) {
405 // If the description is not interesting then let it hold the check number
406 // or similar, otherwise append that to the payment method.
407 if ($description == '') {
408 $description = $tmp[1];
409 } else {
410 $method .= ' ' . $tmp[1];
414 echo " <tr>\n";
415 echo " <td";
416 if (!empty($billtime) && substr($billtime, 0, 4) != '0000') {
417 echo " title='" . xla('Entered') . ' ' .
418 text(oeFormatShortDate($billtime)) . attr(substr($billtime, 10)) . "'";
420 echo ">" . text(oeFormatShortDate($paydate)) . "</td>\n";
421 echo " <td colspan='2'>" . text($refno) . "</td>\n";
422 echo " <td colspan='$rcpt_num_method_columns' class='text-left'>" . text($method) . "</td>\n";
423 echo " <td colspan='$rcpt_num_ref_columns' class='text-left'>" . text($description) . "</td>\n";
424 echo " <td colspan='$rcpt_num_amount_columns' class='text-right'>" . text(oeFormatMoney($amount)) . "</td>\n";
425 echo " </tr>\n";
428 // Compute a current checksum of this encounter's invoice-related data from the database.
430 function invoiceChecksum($pid, $encounter)
432 $row1 = sqlQuery(
433 "SELECT BIT_XOR(CRC32(CONCAT_WS(',', " .
434 "id, code, modifier, units, fee, authorized, provider_id, ndc_info, justify, billed, user, bill_date" .
435 "))) AS checksum FROM billing WHERE " .
436 "pid = ? AND encounter = ? AND activity = 1",
437 array($pid, $encounter)
439 $row2 = sqlQuery(
440 "SELECT BIT_XOR(CRC32(CONCAT_WS(',', " .
441 "sale_id, inventory_id, prescription_id, quantity, fee, sale_date, billed, bill_date" .
442 "))) AS checksum FROM drug_sales WHERE " .
443 "pid = ? AND encounter = ?",
444 array($pid, $encounter)
446 $row3 = sqlQuery(
447 "SELECT BIT_XOR(CRC32(CONCAT_WS(',', " .
448 "sequence_no, code, modifier, payer_type, post_time, post_user, memo, pay_amount, adj_amount, post_date" .
449 "))) AS checksum FROM ar_activity WHERE " .
450 "pid = ? AND encounter = ?",
451 array($pid, $encounter)
453 $row4 = sqlQuery(
454 "SELECT BIT_XOR(CRC32(CONCAT_WS(',', " .
455 "id, date, reason, facility_id, provider_id, supervisor_id, invoice_refno" .
456 "))) AS checksum FROM form_encounter WHERE " .
457 "pid = ? AND encounter = ?",
458 array($pid, $encounter)
460 return (0 + $row1['checksum']) ^ (0 + $row2['checksum']) ^ (0 + $row3['checksum']) ^ (0 + $row4['checksum']);
463 //////////////////////////////////////////////////////////////////////
465 // Generate a receipt from the last-billed invoice for this patient,
466 // or for the encounter specified as a GET parameter.
468 function generate_receipt($patient_id, $encounter = 0)
470 global $details, $rapid_data_entry, $aAdjusts;
471 global $web_root, $webserver_root, $code_types;
472 global $aTaxNames, $aInvTaxes, $checkout_times, $current_checksum;
473 global $num_optional_columns, $rcpt_num_method_columns, $rcpt_num_ref_columns, $rcpt_num_amount_columns;
474 global $TAXES_AFTER_ADJUSTMENT;
475 global $facilityService, $alertmsg;
477 // Get the most recent invoice data or that for the specified encounter.
478 if ($encounter) {
479 $ferow = sqlQuery(
480 "SELECT id, date, encounter, facility_id, invoice_refno " .
481 "FROM form_encounter WHERE pid = ? AND encounter = ?",
482 array($patient_id, $encounter)
484 } else {
485 $ferow = sqlQuery(
486 "SELECT id, date, encounter, facility_id, invoice_refno " .
487 "FROM form_encounter WHERE pid = ? ORDER BY id DESC LIMIT 1",
488 array($patient_id)
491 if (empty($ferow)) {
492 die(xlt("This patient has no activity."));
494 $trans_id = $ferow['id'];
495 $encounter = $ferow['encounter'];
496 $svcdate = substr($ferow['date'], 0, 10);
497 $invoice_refno = $ferow['invoice_refno'];
499 // Generate checksum.
500 $current_checksum = invoiceChecksum($patient_id, $encounter);
502 // Get details for the visit's facility.
503 $frow = $facilityService->getById($ferow['facility_id']);
505 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
507 // Get array of checkout timestamps.
508 $checkout_times = craGetTimestamps($patient_id, $encounter);
510 // Generate $aTaxNames = array of tax names, and $aInvTaxes = array of taxes for this invoice.
511 load_taxes($patient_id, $encounter);
513 <!-- The following is from the php function generate_receipt. -->
514 <!DOCTYPE html>
515 <html>
516 <head>
517 <?php Header::setupHeader(['datetime-picker']);?>
518 <title><?php echo xlt('Client Receipt'); ?></title>
520 <script>
522 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
524 $(function () {
525 var win = top.printLogSetup ? top : opener.top;
526 win.printLogSetup(document.getElementById('printbutton'));
529 // Process click on Print button.
530 function printme(checkout_id) {
531 <?php if (!empty($GLOBALS['gbl_custom_receipt'])) { ?>
532 // Custom checkout receipt needs to be sent as a PDF in a new window or tab.
533 window.open('pos_checkout.php?ptid=' + <?php echo js_url($patient_id); ?>
534 + '&enc=' + <?php echo js_url($encounter); ?>
535 + '&pdf=1&coid=' + encodeURIComponent(checkout_id),
536 '_blank', 'width=750,height=550,resizable=1,scrollbars=1');
537 <?php } else { ?>
538 var divstyle = document.getElementById('hideonprint').style;
539 divstyle.display = 'none';
540 if (checkout_id != '*') {
541 window.print();
543 <?php } ?>
544 return false;
547 // Process click on Print button before printing.
548 function printlog_before_print() {
549 // * means do not call window.print().
550 printme('*');
553 // Process click on Delete button.
554 function deleteme() {
555 dlgopen('deleter.php?billing=' + <?php echo js_url($patient_id . "." . $encounter); ?> + '&csrf_token_form=' + <?php echo js_url(CsrfUtils::collectCsrfToken()); ?>, '_blank', 500, 450);
556 return false;
559 // Called by the deleteme.php window on a successful delete.
560 function imdeleted() {
561 window.close();
564 var voidaction = ''; // saves action argument from voidme()
566 // Submit the form to complete a void operation.
567 function voidwrap(form_reason, form_notes) {
568 top.restoreSession();
569 document.location.href = 'pos_checkout.php?ptid=' + <?php echo js_url($patient_id); ?> +
570 '&' + encodeURIComponent(voidaction) + '=' + <?php echo js_url($encounter); ?> +
571 '&form_checksum=' + <?php echo js_url($current_checksum); ?> +
572 '&form_reason=' + encodeURIComponent(form_reason) +
573 '&form_notes=' + encodeURIComponent(form_notes) +
574 '<?php if (!empty($_GET['framed'])) {
575 echo '&framed=1';} ?>';
576 return false;
579 // Process click on a void option.
580 // action can be 'regen', 'void' or 'voidall'.
581 function voidme(action) {
582 voidaction = action;
583 if (action == 'void' || action == 'voidall') {
584 if (!confirm(<?php echo xlj('This will advance the receipt number. Please print the receipt if you have not already done so.'); ?>)) {
585 return false;
587 dlgopen('void_dialog.php', '_blank', 500, 450);
588 return false;
590 // TBD: Better defaults for void reason and notes.
591 voidwrap('', '');
592 return false;
595 </script>
597 <style>
598 @media (min-width: 992px){
599 .modal-lg {
600 width: 1000px !Important;
603 </style>
604 <title><?php echo xlt('Patient Checkout'); ?></title>
605 </head>
607 <body>
608 <div class='container mt-3'>
609 <div class='row'>
610 <div class='col text-center'>
611 <table class='table' width='95%'>
612 <tr>
613 <td width='25%' align='left' valign='top'>
614 <?php
615 // TBD: Maybe make a global for this file name.
616 if ($tmp = UrlIfImageExists('ma_logo.png')) {
617 echo "<img src='$tmp' />";
618 } else {
619 echo "&nbsp;";
622 </td>
623 <td width='50%' align='center' valign='top' class='font-weight-bold'>
624 <?php echo text($frow['name']); ?>
625 <br><?php echo text($frow['street']); ?>
626 <br><?php
627 echo text($frow['city']) . ", ";
628 echo text($frow['state']) . " ";
629 echo text($frow['postal_code']); ?>
630 <br><?php echo text($frow['phone']); ?>
631 </td>
632 <td width='25%' align='right' valign='top'>
633 <!-- This space available. -->
634 &nbsp;
635 </td>
636 </tr>
637 </table>
638 <p class='font-weight-bold'>
639 <?php
640 echo xlt("Client Receipt");
641 if ($invoice_refno) {
642 echo " " . xlt("for Invoice") . text(" $invoice_refno");
645 <br />&nbsp;
646 </p>
647 <?php
648 // Compute numbers for summary on right side of page.
649 $head_begbal = get_patient_balance_excluding($patient_id, $encounter);
650 $row = sqlQuery(
651 "SELECT SUM(fee) AS amount FROM billing WHERE " .
652 "pid = ? AND encounter = ? AND activity = 1 AND " .
653 "code_type != 'COPAY'",
654 array($patient_id, $encounter)
656 $head_charges = $row['amount'];
657 $row = sqlQuery(
658 "SELECT SUM(fee) AS amount FROM drug_sales WHERE pid = ? AND encounter = ?",
659 array($patient_id, $encounter)
661 $head_charges += $row['amount'];
662 $row = sqlQuery(
663 "SELECT SUM(pay_amount) AS payments, " .
664 "SUM(adj_amount) AS adjustments FROM ar_activity WHERE " .
665 "pid = ? AND encounter = ? AND deleted IS NULL",
666 array($patient_id, $encounter)
668 $head_adjustments = $row['adjustments'];
669 $head_payments = $row['payments'];
670 $row = sqlQuery(
671 "SELECT SUM(fee) AS amount FROM billing WHERE " .
672 "pid = ? AND encounter = ? AND activity = 1 AND " .
673 "code_type = 'COPAY'",
674 array($patient_id, $encounter)
676 $head_payments -= $row['amount'];
677 $head_endbal = $head_begbal + $head_charges - $head_adjustments - $head_payments;
679 <table class='table' width='95%'>
680 <tr>
681 <td width='50%' class='text-left' valign='top'>
682 <?php echo text($patdata['fname'] . ' ' . $patdata['mname'] . ' ' . $patdata['lname']); ?>
683 <br /><?php echo text($patdata['street']); ?>
684 <br /><?php
685 echo generate_layout_display_field('DEM', 'city', $patdata['city']) . ", ";
686 echo generate_layout_display_field('DEM', 'state', $patdata['state']) . " ";
687 echo text($patdata['postal_code']); ?>
688 </td>
689 <td width='50%' class='text-right' valign='top'>
690 <table>
691 <tr>
692 <td><?php echo xlt('Beginning Account Balance'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
693 <td class='text-right'><?php echo text(oeFormatMoney($head_begbal)); ?></td>
694 </tr>
695 <tr>
696 <td><?php echo xlt('Total Visit Charges'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
697 <td class='text-right'><?php echo text(oeFormatMoney($head_charges)); ?></td>
698 </tr>
699 <tr>
700 <td><?php echo xlt('Adjustments'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
701 <td class='text-right'><?php echo text(oeFormatMoney($head_adjustments)); ?></td>
702 </tr>
703 <tr>
704 <td><?php echo xlt('Payments'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
705 <td class='text-right'><?php echo text(oeFormatMoney($head_payments)); ?></td>
706 </tr>
707 <tr>
708 <td><?php echo xlt('Ending Account Balance'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
709 <td class='text-right'><?php echo text(oeFormatMoney($head_endbal)); ?></td>
710 </tr>
711 </table>
712 </td>
713 </tr>
714 </table>
716 <table class='table' width='95%'>
717 <?php if ($details) { ?>
718 <tr>
719 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
720 style='padding-top:5pt;' class='font-weight-bold'>
721 <?php echo xlt('Charges for') . ' ' . text(oeFormatShortDate($svcdate)); ?>
722 </td>
723 </tr>
725 <tr>
726 <td class='font-weight-bold'><?php echo xlt('Date'); ?></td>
727 <td class='font-weight-bold'><?php echo xlt('Code'); ?></td>
728 <td class='font-weight-bold'><?php echo xlt('Description'); ?></td>
729 <td class='font-weight-bold text-center'><?php echo $details ? xlt('Qty') : '&nbsp;'; ?></td>
730 <td class='font-weight-bold text-right'><?php echo $details ? xlt('Price') : '&nbsp;'; ?></td>
731 <?php if (!empty($GLOBALS['gbl_checkout_charges'])) { ?>
732 <td class='font-weight-bold text-right'><?php echo xlt('Charge'); ?></td>
733 <?php } ?>
734 <?php
735 if (!$TAXES_AFTER_ADJUSTMENT) {
736 foreach ($aTaxNames as $taxname) {
737 echo " <td class='font-weight-bold text-right'>" . text($taxname) . "</td>\n";
741 <?php if (!empty($GLOBALS['gbl_charge_categories'])) { ?>
742 <td class='font-weight-bold text-right'><?php echo xlt('Customer'); ?></td>
743 <?php } ?>
744 <?php if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) { ?>
745 <td class='font-weight-bold text-right'><?php echo xlt('Adj Type'); ?></td>
746 <td class='font-weight-bold text-right'><?php echo xlt('Adj Amt'); ?></td>
747 <?php } ?>
748 <?php
749 if ($TAXES_AFTER_ADJUSTMENT) {
750 foreach ($aTaxNames as $taxname) {
751 echo " <td class='font-weight-bold text-right'>" . text($taxname) . "</td>\n";
755 <td class='font-weight-bold text-right'><?php echo xlt('Total'); ?></td>
756 </tr>
758 <?php } // end if details ?>
760 <tr>
761 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
762 style='border-top:1px solid black; font-size:1px; padding:0;'>
763 &nbsp;
764 </td>
765 </tr>
766 <?php
767 // Create array aAdjusts from ar_activity rows for $encounter.
768 load_adjustments($patient_id, $encounter);
770 $aTotals = array(0, 0, 0, 0, 0);
771 for ($i = 0; $i < count($aTaxNames); ++$i) {
772 $aTotals[5 + $i] = 0;
775 // Product sales
776 $inres = sqlStatement(
777 "SELECT s.sale_id, s.sale_date, s.fee, " .
778 "s.quantity, s.drug_id, s.billed, s.bill_date, s.selector, d.name, lo.title " .
779 "FROM drug_sales AS s " .
780 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
781 "LEFT JOIN list_options AS lo ON lo.list_id = 'chargecats' and lo.option_id = s.chargecat AND lo.activity = 1 " .
782 "WHERE s.pid = ? AND s.encounter = ? " .
783 "ORDER BY s.sale_id",
784 array($patient_id, $encounter)
786 while ($inrow = sqlFetchArray($inres)) {
787 $billtime = $inrow['billed'] ? $inrow['bill_date'] : '';
788 $tmpname = $inrow['name'];
789 if ($tmpname !== $inrow['selector']) {
790 $tmpname .= ' / ' . $inrow['selector'];
792 $units = $inrow['quantity'] / FeeSheet::getBasicUnits($inrow['drug_id'], $inrow['selector']);
793 receiptDetailLine(
794 'PROD',
795 $inrow['drug_id'],
796 $tmpname,
797 $units,
798 $inrow['fee'],
799 $aTotals,
800 'P:' . $inrow['sale_id'],
801 $billtime,
802 $svcdate,
803 $inrow['title']
807 // Service items.
808 $inres = sqlStatement(
809 "SELECT * FROM billing AS b " .
810 "LEFT JOIN list_options AS lo ON lo.list_id = 'chargecats' and lo.option_id = b.chargecat AND lo.activity = 1 " .
811 "WHERE b.pid = ? AND b.encounter = ? AND " .
812 "b.code_type != 'COPAY' AND b.code_type != 'TAX' AND b.activity = 1 " .
813 "ORDER BY b.id",
814 array($patient_id, $encounter)
816 while ($inrow = sqlFetchArray($inres)) {
817 // Write the line item if it allows fees or is not a diagnosis.
818 if (!empty($code_types[$inrow['code_type']]['fee']) || empty($code_types[$inrow['code_type']]['diag'])) {
819 $billtime = $inrow['billed'] ? $inrow['bill_date'] : '';
820 receiptDetailLine(
821 $inrow['code_type'],
822 $inrow['code'],
823 $inrow['code_text'],
824 $inrow['units'],
825 $inrow['fee'],
826 $aTotals,
827 'S:' . $inrow['id'],
828 $billtime,
829 $svcdate,
830 $inrow['title']
835 // Write any adjustments left in the aAdjusts array.
836 foreach ($aAdjusts as $arow) {
837 if ($arow['adj_amount'] == 0 && $arow['memotitle'] == '') {
838 continue;
840 $payer = empty($arow['payer_type']) ? 'Pt' : ('Ins' . $arow['payer_type']);
841 receiptDetailLine(
843 "$payer|" . $arow['code_type'] . "|" . $arow['code'],
844 $arow['memotitle'],
846 0 - $arow['adj_amount'],
847 $aTotals,
849 $arow['post_time'],
850 $arow['post_date']
854 <tr>
855 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
856 style='border-top:1px solid black; font-size:1px; padding:0;'>
857 &nbsp;
858 </td>
859 </tr>
861 <?php
862 // Sub-Total line with totals of all numeric columns.
863 if ($details) {
864 echo " <tr>\n";
865 echo " <td colspan='3' align='right'><b>" . xlt('Sub-Total') . "</b></td>\n";
866 echo " <td align='center'>" . text($aTotals[0]) . "</td>\n";
867 echo " <td align='right'>" . text(oeFormatMoney($aTotals[1])) . "</td>\n";
868 // Optional charge amount.
869 if (!empty($GLOBALS['gbl_checkout_charges'])) {
870 echo " <td align='right'>" . text(oeFormatMoney($aTotals[2])) . "</td>\n";
872 if (!$TAXES_AFTER_ADJUSTMENT) {
873 // Put tax columns, if any, in the subtotals.
874 for ($i = 0; $i < count($aTaxNames); ++$i) {
875 echo " <td align='right'>" . text(oeFormatMoney($aTotals[5 + $i])) . "</td>\n";
878 // Optional charge category empty column.
879 if (!empty($GLOBALS['gbl_charge_categories'])) {
880 echo " <td align='right'>&nbsp;</td>\n";
882 // Optional adjustment columns.
883 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
884 echo " <td align='right'>&nbsp;</td>\n";
885 echo " <td align='right'>" . text(oeFormatMoney($aTotals[3])) . "</td>\n";
887 if ($TAXES_AFTER_ADJUSTMENT) {
888 // Put tax columns, if any, in the subtotals.
889 for ($i = 0; $i < count($aTaxNames); ++$i) {
890 echo " <td align='right'>" . text(oeFormatMoney($aTotals[5 + $i])) . "</td>\n";
893 echo " <td align='right'>" . text(oeFormatMoney($aTotals[4])) . "</td>\n";
894 echo " </tr>\n";
897 // Write a line for each tax item that did not match.
898 // Should only happen for old invoices before taxes were assigned to line items.
899 foreach ($aInvTaxes as $taxid => $taxarr) {
900 foreach ($taxarr as $taxlineid => $tax) {
901 if ($tax) {
902 receiptDetailLine('TAX', $taxid, $aTaxNames[$taxid], 1, $tax, $aTotals);
903 $aInvTaxes[$taxid][$taxlineid] = 0;
908 // Total Charges line.
909 echo " <tr>\n";
910 echo " <td colspan='" . (3 + $num_optional_columns) . "'>&nbsp;</td>\n";
911 echo " <td colspan='" . 2 .
912 "' align='right'><b>" . xlt('Total Charges') . "</b></td>\n";
913 echo " <td align='right'>" . text(oeFormatMoney($aTotals[4])) . "</td>\n";
914 echo " </tr>\n";
917 <tr>
918 <td colspan='<?php echo 6 + $num_optional_columns; ?>' style='padding-top:5pt;'>
919 <b><?php echo xlt('Payments'); ?></b>
920 </td>
921 </tr>
923 <tr>
924 <td><b><?php echo xlt('Date'); ?></b></td>
925 <td colspan='2'><b><?php echo xlt('Checkout Receipt Ref'); ?></b></td>
926 <td colspan="<?php echo text($rcpt_num_method_columns); ?>"
927 align='left'><b><?php echo xlt('Payment Method'); ?></b></td>
928 <td colspan="<?php echo text($rcpt_num_ref_columns); ?>"
929 align='left'><b><?php echo xlt('Ref No'); ?></b></td>
930 <td colspan='<?php echo text($rcpt_num_amount_columns); ?>'
931 align='right'><b><?php echo xlt('Amount'); ?></b></td>
932 </tr>
934 <tr>
935 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
936 style='border-top:1px solid black; font-size:1px; padding:0;'>
937 &nbsp;
938 </td>
939 </tr>
941 <?php
942 $payments = 0;
944 // Get co-pays.
945 $inres = sqlStatement(
946 "SELECT fee, code_text FROM billing WHERE " .
947 "pid = ? AND encounter = ? AND " .
948 "code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
949 "ORDER BY id",
950 array($patient_id, $encounter)
952 while ($inrow = sqlFetchArray($inres)) {
953 $payments -= formatMoneyNumber($inrow['fee']);
954 receiptPaymentLine($svcdate, 0 - $inrow['fee'], $inrow['code_text'], 'COPAY');
957 // Get other payments.
958 $inres = sqlStatement(
959 "SELECT " .
960 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
961 "a.post_time, IFNULL(a.post_date, a.post_time) AS post_date, " .
962 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
963 "FROM ar_activity AS a " .
964 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
965 "a.pid = ? AND a.encounter = ? AND a.deleted IS NULL AND " .
966 "a.pay_amount != 0 " .
967 "ORDER BY s.check_date, a.sequence_no",
968 array($patient_id, $encounter)
970 $payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
971 while ($inrow = sqlFetchArray($inres)) {
972 $payments += formatMoneyNumber($inrow['pay_amount']);
973 // Compute invoice number with payment suffix.
974 $tmp = array_search($inrow['post_time'], $checkout_times);
975 $tmp = $tmp === false ? 0 : ($tmp + 1);
976 $refno = $invoice_refno ? "$invoice_refno-$tmp" : "$encounter-$tmp";
977 receiptPaymentLine(
978 $inrow['post_date'],
979 $inrow['pay_amount'],
980 trim($payer . ' ' . $inrow['reference']),
981 $inrow['memo'],
982 $refno,
983 $inrow['post_time']
988 <tr>
989 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
990 style='border-top:1px solid black; font-size:1px; padding:0;'>
991 &nbsp;
992 </td>
993 </tr>
995 <tr>
996 <td colspan='<?php echo 3 + $num_optional_columns; ?>'>&nbsp;</td>
997 <td colspan='2' align='right'><b><?php echo xlt('Total Payments'); ?></b></td>
998 <td align='right'><?php echo str_replace(' ', '&nbsp;', text(oeFormatMoney($payments, true))); ?></td>
999 </tr>
1001 </table>
1002 </div>
1003 </div>
1005 <div class='row'>
1006 <div class='col'>
1008 <?php
1009 // The user-customizable note.
1010 if (!empty($GLOBALS['gbl_checkout_receipt_note'])) {
1011 echo "<p>";
1012 echo str_repeat('*', 80) . '<br />';
1013 echo '&nbsp;&nbsp;' . text($GLOBALS['gbl_checkout_receipt_note']) . '<br />';
1014 echo str_repeat('*', 80) . '<br />';
1015 echo "</p>";
1020 <b><?php echo xlt("Printed on") . ' ' . text(dateformat()); ?></b>
1021 </p>
1023 <div id='hideonprint'>
1025 &nbsp;
1027 <?php
1028 if (count($checkout_times) > 1 && !empty($GLOBALS['gbl_custom_receipt'])) {
1029 // Multiple checkouts so allow selection of the one to print.
1030 // This is only applicable for custom checkout receipts.
1031 echo "<select onchange='printme(this.value)' >\n";
1032 echo " <option value=''>" . xlt('Print Checkout') . "</option>\n";
1033 $i = 0;
1034 foreach ($checkout_times as $tmp) {
1035 ++$i;
1036 echo " <option value='" . attr($tmp) . "'>" . text("$i: $tmp") . "</option>\n";
1038 echo "</select>\n";
1039 } else {
1040 echo "<a href='#' onclick='return printme(\"\");'>" . xlt('Print') . "</a>\n";
1044 <?php if (AclMain::aclCheckCore('acct', 'disc')) { ?>
1045 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1046 <a href='#' onclick='return voidme("regen");'><?php echo xlt('Generate New Receipt Number'); ?></a>
1047 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1048 <a href='#' onclick='return voidme("void");' title='<?php echo xla('Applies to this visit only'); ?>'>
1049 <?php echo xlt('Void Last Checkout'); ?></a>
1050 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1051 <a href='#' onclick='return voidme("voidall");' title='<?php echo xla('Applies to this visit only'); ?>'>
1052 <?php echo xlt('Void All Checkouts'); ?></a>
1053 <?php } ?>
1055 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1057 <?php if ($details) { ?>
1058 <a href='pos_checkout.php?details=0&ptid=<?php echo attr_url($patient_id); ?>&enc=<?php echo attr_url($encounter); ?>'
1059 onclick='top.restoreSession()'><?php echo xlt('Hide Details'); ?></a>
1060 <?php } else { ?>
1061 <a href='pos_checkout.php?details=1&ptid=<?php echo attr_url($patient_id); ?>&enc=<?php echo attr_url($encounter); ?>'
1062 onclick='top.restoreSession()'><?php echo xlt('Show Details'); ?></a>
1063 <?php } ?>
1064 </p>
1065 </div><!-- end hideonprint -->
1066 </div><!-- end col -->
1067 </div><!-- end row -->
1068 </div><!-- end container -->
1069 <script>
1070 <?php
1071 if ($alertmsg) {
1072 echo " alert(" . js_escape($alertmsg) . ");\n";
1075 </script>
1076 </body>
1077 </html>
1078 <?php
1081 // end function generate_receipt()
1083 //////////////////////////////////////////////////////////////////////
1085 // Function to write the heading lines for the data entry form.
1086 // This is deferred because we need to know which encounter was chosen.
1088 $form_headers_written = false;
1089 function write_form_headers()
1091 global $form_headers_written, $patdata, $patient_id, $encounter_id, $aAdjusts;
1092 global $taxes, $encounter_date, $num_optional_columns, $TAXES_AFTER_ADJUSTMENT;
1094 if ($form_headers_written) {
1095 return;
1097 $form_headers_written = true;
1099 // Create arrays $aAdjusts, $aTaxNames and $aInvTaxes for this encounter.
1100 load_adjustments($patient_id, $encounter_id);
1101 // This also initializes $num_optional_columns and related colspan values.
1102 load_taxes($patient_id, $encounter_id);
1104 $ferow = sqlQuery(
1105 "SELECT date FROM form_encounter WHERE pid = ? AND encounter = ?",
1106 array($patient_id, $encounter_id)
1108 $encounter_date = substr($ferow['date'], 0, 10);
1110 <tr>
1111 <td colspan='<?php echo 5 + $num_optional_columns; ?>' align='center' class='title'>
1112 <?php echo xlt('Patient Checkout for '); ?><?php echo text($patdata['fname']) . " " .
1113 text($patdata['lname']) . " (" . text($patdata['pubpid']) . ")" ?>
1114 <br />&nbsp;
1115 <p class='bold'>
1116 <?php
1117 $prvbal = get_patient_balance_excluding($patient_id, $encounter_id);
1118 echo xlt('Previous Balance') . '&nbsp;&nbsp;&nbsp;&nbsp;';
1119 echo "<input type='text' value='" . attr(oeFormatMoney($prvbal)) . "' size='6' ";
1120 echo "style='text-align:right;background-color:transparent' readonly />\n";
1121 if ($prvbal > 0) {
1122 echo "&nbsp;<input type='button' value='" . xla('Pay Previous Balance') .
1123 "' onclick='payprevious()' />\n";
1126 <br />&nbsp;
1127 </p>
1128 </td>
1129 </tr>
1131 <tr>
1132 <?php if (!$TAXES_AFTER_ADJUSTMENT) { ?>
1133 <td colspan='<?php echo 4 + (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1) + count($taxes); ?>' class='bold'>
1134 <?php } else { ?>
1135 <td colspan='<?php echo 4 + (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1); ?>' class='bold'>
1136 <?php } ?>
1137 &nbsp;
1138 </td>
1139 <?php if (!empty($GLOBALS['gbl_charge_categories'])) { ?>
1140 <td align='right' class='bold' nowrap>
1141 <?php echo xlt('Default Customer'); ?>
1142 </td>
1143 <?php } ?>
1144 <?php if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) { ?>
1145 <td align='right' class='bold' nowrap>
1146 <?php echo xlt('Default Adjust Type'); ?>
1147 </td>
1148 <td class='bold'>
1149 &nbsp;
1150 </td>
1151 <?php } ?>
1152 <?php if (!$TAXES_AFTER_ADJUSTMENT) { ?>
1153 <td class='bold'>
1154 <?php } else { ?>
1155 <td colspan='<?php echo 1 + count($taxes); ?>' class='bold'>
1156 <?php } ?>
1157 &nbsp;
1158 </td>
1159 </tr>
1161 <tr>
1162 <?php if (!$TAXES_AFTER_ADJUSTMENT) { ?>
1163 <td colspan='<?php echo 4 + (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1) + count($taxes); ?>' class='title'>
1164 <?php } else { ?>
1165 <td colspan='<?php echo 4 + (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1); ?>' class='title'>
1166 <?php } ?>
1167 <?php echo xlt('Current Charges'); ?>
1168 </td>
1169 <?php if (!empty($GLOBALS['gbl_charge_categories'])) { // charge category default ?>
1170 <td align='right' class='bold'>
1171 <?php echo generate_select_list('form_charge_category', 'chargecats', '', '', ' ', '', 'chargeCategoryChanged();'); ?>
1172 </td>
1173 <?php } ?>
1174 <?php if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) { // adjustmenty reason default ?>
1175 <td align='right' class='bold'>
1176 <?php echo generate_select_list('form_discount_type', 'adjreason', '', '', ' ', '', 'discountTypeChanged();billingChanged();'); ?>
1177 </td>
1178 <td class='bold'>
1179 &nbsp;
1180 </td>
1181 <?php } ?>
1182 <?php if (!$TAXES_AFTER_ADJUSTMENT) { ?>
1183 <td class='bold'>
1184 <?php } else { ?>
1185 <td colspan='<?php echo 1 + count($taxes); ?>' class='bold'>
1186 <?php } ?>
1187 &nbsp;
1188 </td>
1189 </tr>
1191 <tr>
1192 <td class='bold'><?php echo xlt('Date'); ?></td>
1193 <td class='bold'><?php echo xlt('Description'); ?></td>
1194 <td align='right' class='bold'><?php echo xlt('Quantity'); ?></td>
1195 <?php if (empty($GLOBALS['gbl_checkout_charges'])) { // if no charges column ?>
1196 <td align='right' class='bold'><?php echo xlt('Charge'); ?></td>
1197 <?php } else { // charges column needed ?>
1198 <td align='right' class='bold'><?php echo xlt('Price'); ?></td>
1199 <td align='right' class='bold'><?php echo xlt('Charge'); ?></td>
1200 <?php } ?>
1201 <?php
1202 if (!$TAXES_AFTER_ADJUSTMENT) {
1203 foreach ($taxes as $taxarr) {
1204 echo " <td align='right' class='bold'>" . text($taxarr[0]) . "</td>";
1208 <?php if (!empty($GLOBALS['gbl_charge_categories'])) { // charge category ?>
1209 <td align='right' class='bold'><?php echo xlt('Customer'); ?></td>
1210 <?php } ?>
1211 <?php if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) { ?>
1212 <td align='right' class='bold'><?php echo xlt('Adjust Type'); ?></td>
1213 <td align='right' class='bold'><?php echo xlt('Adj'); ?></td>
1214 <?php } ?>
1215 <?php
1216 if ($TAXES_AFTER_ADJUSTMENT) {
1217 foreach ($taxes as $taxarr) {
1218 echo " <td align='right' class='bold'>" . text($taxarr[0]) . "</td>";
1222 <td align='right' class='bold'><?php echo xlt('Total'); ?></td>
1223 </tr>
1224 <?php
1227 // Function to output a line item for the input form.
1229 $totalchg = 0; // totals charges after adjustments
1230 function write_form_line(
1231 $code_type,
1232 $code,
1233 $id,
1234 $date,
1235 $description,
1236 $amount,
1237 $units,
1238 $taxrates,
1239 $billtime = '',
1240 $chargecat = ''
1242 global $lino, $totalchg, $aAdjusts, $taxes, $encounter_date, $TAXES_AFTER_ADJUSTMENT;
1244 // Write heading rows if that is not already done.
1245 write_form_headers();
1246 $amount = formatMoneyNumber($amount);
1247 if (empty($units)) {
1248 $units = 1;
1250 $price = formatMoneyNumber($amount / $units, 2); // should be even cents, but...
1251 if (substr($price, -2) === '00') {
1252 $price = formatMoneyNumber($price);
1255 // Total and clear adjustments in aAdjusts matching this line item. Should only
1256 // happen for billed items, and matching includes the billing timestamp in order
1257 // to handle the case of multiple checkouts.
1258 $memo = '';
1259 $adjust = pull_adjustment($code_type, $code, $billtime, $memo);
1260 $total = formatMoneyNumber($amount - $adjust);
1261 if (empty($GLOBALS['discount_by_money'])) {
1262 // Convert $adjust to a percentage of the amount, up to 4 decimal places.
1263 $adjust = round(100 * $adjust / $amount, 4);
1266 // Compute the string of numeric tax rates to store with the charge line.
1267 $taxnumrates = '';
1268 $arates = explode(':', $taxrates);
1269 foreach ($taxes as $taxid => $taxarr) {
1270 $rate = $taxarr[1];
1271 if (empty($arates) || !in_array($taxid, $arates)) {
1272 $rate = 0;
1274 $taxnumrates .= $rate . ':';
1277 echo " <tr>\n";
1278 echo " <td class='text'>" . text(oeFormatShortDate($encounter_date));
1279 echo "<input type='hidden' name='line[$lino][code_type]' value='" . attr($code_type) . "'>";
1280 echo "<input type='hidden' name='line[$lino][code]' value='" . attr($code) . "'>";
1281 echo "<input type='hidden' name='line[$lino][id]' value='" . attr($id) . "'>";
1282 echo "<input type='hidden' name='line[$lino][description]' value='" . attr($description) . "'>";
1283 // String of numeric tax rates is written here as a form field only for JavaScript tax computations.
1284 echo "<input type='hidden' name='line[$lino][taxnumrates]' value='" . attr($taxnumrates) . "'>";
1285 echo "<input type='hidden' name='line[$lino][units]' value='" . attr($units) . "'>";
1286 // Indicator of whether and when this line item was previously billed:
1287 echo "<input type='hidden' name='line[$lino][billtime]' value='" . attr($billtime) . "'>";
1288 echo "</td>\n";
1289 echo " <td class='text'>" . text($description) . "</td>";
1290 echo " <td class='text' align='right'>" . text($units) . "</td>";
1292 if (empty($GLOBALS['gbl_checkout_charges'])) {
1293 // We show only total charges here.
1294 echo " <td class='text' align='right'>";
1295 echo "<input type='hidden' name='line[$lino][price]' value='" . attr($price) . "'>";
1296 echo "<input type='text' name='line[$lino][charge]' value='" . attr($amount) . "' size='6'";
1297 echo " style='text-align:right;background-color:transparent' readonly />";
1298 echo "</td>\n";
1299 } else {
1300 // In this case show price and extended charge amount.
1301 echo " <td class='text' align='right'>";
1302 echo "<input type='text' name='line[$lino][price]' value='" . attr($price) . "' size='6'";
1303 echo " style='text-align:right;background-color:transparent' readonly />";
1304 echo "</td>\n";
1305 echo " <td class='text' align='right'>";
1306 echo "<input type='text' name='line[$lino][charge]' value='" . attr($amount) . "' size='6'";
1307 echo " style='text-align:right;background-color:transparent' readonly />";
1308 echo "</td>\n";
1311 // Match up (and delete) entries in $aInvTaxes with the line.
1312 $lineid = $code_type == 'PROD' ? "P:$id" : "S:$id";
1313 $aTaxes = array();
1314 pull_tax($lineid, $aTaxes); // fills in $aTaxes
1316 if (!$TAXES_AFTER_ADJUSTMENT) {
1317 // A tax column for each tax. JavaScript will compute the amounts and
1318 // account for how the discount affects them.
1319 $i = 0;
1320 foreach ($taxes as $taxid => $dummy) {
1321 echo " <td class='text' align='right'>";
1322 echo "<input type='text' name='line[$lino][tax][$i]' size='6'";
1323 // Set tax amounts for existing billed items. JS must not recompute those.
1324 echo " value='" . attr(formatMoneyNumber($aTaxes[$taxid])) . "'";
1325 echo " style='text-align:right;background-color:transparent' readonly />";
1326 echo "</td>\n";
1327 ++$i;
1331 // Optional Charge Category.
1332 if (!empty($GLOBALS['gbl_charge_categories'])) {
1333 echo " <td class='text' align='right'>";
1334 echo generate_select_list(
1335 "line[$lino][chargecat]",
1336 'chargecats',
1337 $chargecat,
1339 ' ',
1343 $billtime ? array('disabled' => 'disabled') : null
1345 echo "</td>\n";
1348 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
1349 echo " <td class='text' align='right'>";
1350 echo generate_select_list(
1351 "line[$lino][memo]",
1352 'adjreason',
1353 $memo,
1355 ' ',
1357 'billingChanged()',
1359 $billtime ? array('disabled' => 'disabled') : null
1361 echo "</td>\n";
1362 echo " <td class='text' align='right' nowrap>";
1363 echo empty($GLOBALS['discount_by_money']) ? '' : text($GLOBALS['gbl_currency_symbol']);
1364 echo "<input type='text' name='line[$lino][adjust]' size='6'";
1365 echo " value='" . attr(formatMoneyNumber($adjust)) . "'";
1366 // Modifying discount requires the acct/disc permission.
1367 if ($billtime || $code_type == 'TAX' || $code_type == 'COPAY' || !AclMain::aclCheckCore('acct', 'disc')) {
1368 echo " style='text-align:right;background-color:transparent' readonly";
1369 } else {
1370 echo " style='text-align:right' maxlength='8' onkeyup='lineDiscountChanged($lino)'";
1372 echo " /> ";
1373 echo empty($GLOBALS['discount_by_money']) ? '%' : '';
1374 echo "</td>\n";
1377 if ($TAXES_AFTER_ADJUSTMENT) {
1378 // A tax column for each tax. JavaScript will compute the amounts and
1379 // account for how the discount affects them.
1380 $i = 0;
1381 foreach ($taxes as $taxid => $dummy) {
1382 echo " <td class='text' align='right'>";
1383 echo "<input type='text' name='line[$lino][tax][$i]' size='6'";
1384 // Set tax amounts for existing billed items. JS must not recompute those.
1385 echo " value='" . attr(formatMoneyNumber($aTaxes[$taxid])) . "'";
1386 echo " style='text-align:right;background-color:transparent' readonly />";
1387 echo "</td>\n";
1388 ++$i;
1392 // Extended amount after adjustments and taxes.
1393 echo " <td class='text' align='right'>";
1394 echo "<input type='text' name='line[$lino][amount]' value='" . attr($total) . "' size='6'";
1395 echo " style='text-align:right;background-color:transparent' readonly />";
1396 echo "</td>\n";
1398 echo " </tr>\n";
1399 ++$lino;
1400 $totalchg += $amount;
1403 // Function to output a past payment/adjustment line to the form.
1405 function write_old_payment_line($pay_type, $date, $method, $reference, $amount)
1407 global $lino, $taxes, $num_optional_columns;
1408 global $form_num_type_columns, $form_num_method_columns, $form_num_ref_columns, $form_num_amount_columns;
1409 // Write heading rows if that is not already done.
1410 write_form_headers();
1411 $amount = formatMoneyNumber($amount);
1412 echo " <tr>\n";
1413 echo " <td class='text' colspan='$form_num_type_columns'>" . text($pay_type) . "</td>\n";
1414 echo " <td class='text' colspan='$form_num_method_columns'>" . text($method) . "</td>\n";
1415 echo " <td class='text' colspan='$form_num_ref_columns'>" . text($reference) . "</td>\n";
1416 echo " <td class='text' align='right' colspan='$form_num_amount_columns'><input type='text' name='oldpay[$lino][amount]' " .
1417 "value='$amount' size='6' maxlength='8'";
1418 echo " style='text-align:right;background-color:transparent' readonly";
1419 echo "></td>\n";
1420 echo " </tr>\n";
1421 ++$lino;
1424 // Mark the tax rates that are referenced in this invoice.
1425 function markTaxes($taxrates)
1427 global $taxes;
1428 $arates = explode(':', $taxrates);
1429 if (empty($arates)) {
1430 return;
1432 foreach ($arates as $value) {
1433 if (!empty($taxes[$value])) {
1434 $taxes[$value][2] = '1';
1439 // Create the taxes array. Key is tax id, value is
1440 // (description, rate, indicator). Indicator seems to be unused.
1441 $taxes = array();
1442 $pres = sqlStatement(
1443 "SELECT option_id, title, option_value " .
1444 "FROM list_options WHERE list_id = 'taxrate' AND activity = 1 ORDER BY seq, title, option_id"
1446 while ($prow = sqlFetchArray($pres)) {
1447 $taxes[$prow['option_id']] = array($prow['title'], $prow['option_value'], 0);
1450 // Array of HTML for the 4 or 5 cells of an input payment row.
1451 // "%d" will be replaced by a payment line number on the client side.
1453 $aCellHTML = array();
1454 $aCellHTML[] = "<span id='paytitle_%d'>" . text(xl('New Payment')) . "</span>";
1455 $aCellHTML[] = strtr(generate_select_list('payment[%d][method]', 'paymethod', '', '', ''), array("\n" => ""));
1456 $aCellHTML[] = "<input type='text' name='payment[%d][refno]' size='10' />";
1457 $aCellHTML[] = "<input type='text' name='payment[%d][amount]' size='6' style='text-align:right' onkeyup='setComputedValues()' />";
1459 $alertmsg = ''; // anything here pops up in an alert box
1461 // Make sure we have the encounter ID applicable to this request.
1462 if (!empty($_POST['form_save'])) {
1463 $patient_id = (int) $_POST['form_pid'];
1464 $encounter_id = (int) $_POST['form_encounter'];
1465 } else {
1466 foreach (array('regen', 'enc', 'void', 'voidall') as $key) {
1467 if (!empty($_GET[$key])) {
1468 $encounter_id = (int) $_GET[$key];
1469 break;
1474 // Compute and validate the checksum.
1475 $current_checksum = 0;
1476 if ($patient_id && $encounter_id) {
1477 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1478 if (!empty($_REQUEST['form_checksum'])) {
1479 if ($_REQUEST['form_checksum'] != $current_checksum) {
1480 $alertmsg = xl('Someone else has just changed this visit. Please cancel this page and try again.');
1485 // If the Save button was clicked...
1487 if (!empty($_POST['form_save']) && !$alertmsg) {
1488 if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) {
1489 CsrfUtils::csrfNotVerified();
1492 // On a save, do the following:
1493 // Flag this form's drug_sales and billing items as billed.
1494 // Post line-level adjustments, replacing any existing ones for the same charges.
1495 // Post any invoice-level adjustment.
1496 // Post payments and be careful to use a unique invoice number.
1497 // Call the generate-receipt function.
1498 // Exit.
1500 // A current invoice reference number may be present if there was a previous checkout.
1501 $tmprow = sqlQuery(
1502 "SELECT invoice_refno FROM form_encounter WHERE " .
1503 "pid = ? AND encounter = ?",
1504 array($patient_id, $encounter_id)
1506 $current_irnumber = $tmprow['invoice_refno'];
1508 // Get the posting date from the form as yyyy-mm-dd.
1509 $postdate = substr($this_bill_date, 0, 10);
1510 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $_POST['form_date'], $matches)) {
1511 $postdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
1513 $dosdate = $postdate; // not sure if this is appropriate
1515 if (! $encounter_id) {
1516 die("Internal error: Encounter ID is missing!");
1519 // Delete unbilled TAX rows from billing because they will be recalculated.
1520 // Do not delete already-billed taxes; we must not touch billed stuff.
1521 sqlStatement(
1522 "UPDATE billing SET activity = 0 WHERE " .
1523 "pid = ? AND encounter = ? AND " .
1524 "code_type = 'TAX' AND billed = 0 AND activity = 1",
1525 array($patient_id, $encounter_id)
1528 $form_amount = $_POST['form_totalpay'];
1529 $lines = $_POST['line'];
1531 for ($lino = 0; !empty($lines[$lino]['code_type']); ++$lino) {
1532 $line = $lines[$lino];
1533 $code_type = $line['code_type'];
1534 $code = $line['code'];
1535 $id = $line['id'];
1536 $chargecat = $line['chargecat'] ?? '';
1537 $amount = formatMoneyNumber(trim($line['amount']));
1538 $linetax = 0;
1540 // Skip saving taxes and adjustments for billed items.
1541 if (!empty($line['billtime'])) {
1542 continue;
1545 // Insert any taxes for this line.
1546 // There's a chance of input data and the $taxes array being out of sync if someone
1547 // updates the taxrate list during data entry... we oughta do something about that.
1548 if (is_array($line['tax'])) {
1549 // For tax rows the ndc_info field is used to identify the charge item that is taxed.
1550 // P indicates drug_sales.sale_id, S indicates billing.id.
1551 $ndc_info = $code_type == 'PROD' ? "P:$id" : "S:$id";
1552 $i = 0;
1553 foreach ($taxes as $taxid => $taxarr) {
1554 $taxamount = $line['tax'][$i++] + 0;
1555 if ($taxamount != 0) {
1556 BillingUtilities::addBilling(
1557 $encounter_id,
1558 'TAX',
1559 $taxid,
1560 $taxarr[0],
1561 $patient_id,
1566 $taxamount,
1567 $ndc_info,
1571 // billed=0 because we will set billed and bill_date for unbilled items below.
1572 $linetax += $taxamount;
1577 // If there is an adjustment for this line, insert it.
1578 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
1579 $adjust = 0.00 + trim($line['adjust']);
1580 $memo = formDataCore($line['memo']);
1581 if ($adjust != 0 || $memo !== '') {
1582 // $memo = xl('Discount');
1583 if ($memo === '') {
1584 $memo = formData('form_discount_type');
1586 sqlBeginTrans();
1587 $sequence_no = sqlQuery(
1588 "SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE " .
1589 "pid = ? AND encounter = ?",
1590 array($patient_id, $encounter_id)
1592 $query = "INSERT INTO ar_activity ( " .
1593 "pid, encounter, sequence_no, code_type, code, modifier, payer_type, " .
1594 "post_user, post_time, post_date, session_id, memo, adj_amount " .
1595 ") VALUES ( " .
1596 "?, ?, ?, ?, ?, '', '0', ?, ?, ?, '0', ?, ? " .
1597 ")";
1598 sqlStatement($query, array(
1599 $patient_id,
1600 $encounter_id,
1601 $sequence_no['increment'],
1602 $code_type,
1603 $code,
1604 $_SESSION['authUserID'],
1605 $this_bill_date,
1606 $postdate,
1607 $memo,
1608 $adjust
1610 sqlCommitTrans();
1614 if (!empty($GLOBALS['gbl_charge_categories'])) {
1615 // Update charge category for this line item.
1616 if ($code_type == 'PROD') {
1617 $query = "UPDATE drug_sales SET chargecat = ? WHERE sale_id = ?";
1618 sqlQuery($query, array($chargecat, $id));
1619 } else {
1620 $query = "UPDATE billing SET chargecat = ? WHERE id = ?";
1621 sqlQuery($query, array($chargecat, $id));
1626 // Flag the encounter as billed.
1627 $query = "UPDATE billing SET billed = 1, bill_date = ? WHERE " .
1628 "pid = ? AND encounter = ? AND activity = 1 AND billed = 0";
1629 sqlQuery($query, array($this_bill_date, $patient_id, $encounter_id));
1630 $query = "update drug_sales SET billed = 1, bill_date = ? WHERE " .
1631 "pid = ? AND encounter = ? AND billed = 0";
1632 sqlQuery($query, array($this_bill_date, $patient_id, $encounter_id));
1634 // Post discount.
1635 if ($_POST['form_discount'] != 0) {
1636 if ($GLOBALS['discount_by_money']) {
1637 $amount = formatMoneyNumber(trim($_POST['form_discount']));
1638 } else {
1639 $amount = formatMoneyNumber(trim($_POST['form_discount']) * $form_amount / 100);
1641 $memo = formData('form_discount_type');
1642 sqlBeginTrans();
1643 $sequence_no = sqlQuery(
1644 "SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE " .
1645 "pid = ? AND encounter = ?",
1646 array($patient_id, $encounter_id)
1648 $query = "INSERT INTO ar_activity ( " .
1649 "pid, encounter, sequence_no, code, modifier, payer_type, post_user, post_time, " .
1650 "post_date, session_id, memo, adj_amount " .
1651 ") VALUES ( " .
1652 "?, ?, ?, '', '', '0', ?, ?, ?, '0', ?, ? " .
1653 ")";
1654 sqlStatement($query, array(
1655 $patient_id,
1656 $encounter_id,
1657 $sequence_no['increment'],
1658 $_SESSION['authUserID'],
1659 $this_bill_date,
1660 $postdate,
1661 $memo,
1662 $amount
1664 sqlCommitTrans();
1667 // Post the payments.
1668 if (is_array($_POST['payment'])) {
1669 $lines = $_POST['payment'];
1670 for ($lino = 0; isset($lines[$lino]['amount']); ++$lino) {
1671 $line = $lines[$lino];
1672 $amount = formatMoneyNumber(trim($line['amount']));
1673 if ($amount != 0.00) {
1674 $method = $line['method'];
1675 $refno = $line['refno'];
1676 if ($method !== '' && $refno !== '') {
1677 $method .= " $refno";
1679 $session_id = 0; // Is this OK?
1680 SLEOB::arPostPayment(
1681 $patient_id,
1682 $encounter_id,
1683 $session_id,
1684 $amount,
1687 $method,
1689 $this_bill_date,
1691 $postdate
1697 // If applicable, set the invoice reference number.
1698 if (!$current_irnumber) {
1699 $invoice_refno = '';
1700 if (isset($_POST['form_irnumber'])) {
1701 $invoice_refno = formData('form_irnumber', 'P', true);
1702 } else {
1703 $invoice_refno = add_escape_custom(BillingUtilities::updateInvoiceRefNumber());
1705 if ($invoice_refno) {
1706 sqlStatement(
1707 "UPDATE form_encounter SET invoice_refno = ? WHERE pid = ? AND encounter = ?",
1708 array($invoice_refno, $patient_id, $encounter_id)
1713 // If appropriate, update the status of the related appointment to
1714 // "Checked out".
1715 updateAppointmentStatus($patient_id, $dosdate, '>');
1717 generate_receipt($patient_id, $encounter_id);
1718 exit();
1721 // Void attributes.
1722 $form_reason = empty($_GET['form_reason']) ? '' : $_GET['form_reason'];
1723 $form_notes = empty($_GET['form_notes' ]) ? '' : $_GET['form_notes'];
1725 // If "regen" encounter ID was given, then we must generate a new receipt ID.
1727 if (!$alertmsg && $patient_id && !empty($_GET['regen'])) {
1728 BillingUtilities::doVoid(
1729 $patient_id,
1730 $encounter_id,
1731 false,
1733 $form_reason,
1734 $form_notes
1736 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1737 $_GET['enc'] = $encounter_id;
1740 // If "enc" encounter ID was given, then we must generate a receipt and exit.
1742 if ($patient_id && !empty($_GET['enc'])) {
1743 if (empty($_GET['pdf'])) {
1744 generate_receipt($patient_id, $_GET['enc']);
1745 } else {
1746 // PDF receipt is requested. In this case we are probably in a new window.
1747 require_once($GLOBALS['OE_SITE_DIR'] . "/" . $GLOBALS['gbl_custom_receipt']);
1748 // $checkout_id is an optional specified checkout timestamp.
1749 $billtime = $checkout_id;
1750 if (!$billtime) {
1751 // No timestamp specified so use the last one.
1752 $checkout_times = craGetTimestamps($patient_id, $_GET['enc']);
1753 $billtime = empty($checkout_times) ? '' : $checkout_times[count($checkout_times) - 1];
1755 generateCheckoutReceipt($patient_id, $_GET['enc'], $billtime);
1757 exit();
1760 // If "void" encounter ID was given, then we must undo the last checkout.
1761 // Or for "voidall" undo all checkouts for the encounter.
1763 if (!$alertmsg && $patient_id && !empty($_GET['void'])) {
1764 BillingUtilities::doVoid($patient_id, $encounter_id, true, '', $form_reason, $form_notes);
1765 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1766 } else if (!$alertmsg && $patient_id && !empty($_GET['voidall'])) {
1767 BillingUtilities::doVoid($patient_id, $encounter_id, true, 'all', $form_reason, $form_notes);
1768 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1771 // Get the specified or first unbilled encounter ID for this patient.
1773 if (!$encounter_id) {
1774 $query = "SELECT encounter FROM billing WHERE " .
1775 "pid = ? AND activity = 1 AND billed = 0 AND code_type != 'TAX' " .
1776 "ORDER BY encounter DESC LIMIT 1";
1777 $brow = sqlQuery($query, array($patient_id));
1778 $query = "SELECT encounter FROM drug_sales WHERE " .
1779 "pid = ? AND billed = 0 " .
1780 "ORDER BY encounter DESC LIMIT 1";
1781 $drow = sqlQuery($query, array($patient_id));
1782 if (!empty($brow['encounter'])) {
1783 if (!empty($drow['encounter'])) {
1784 $encounter_id = min(intval($brow['encounter']), intval($drow['encounter']));
1785 } else {
1786 $encounter_id = $brow['encounter'];
1788 } else if (!empty($drow['encounter'])) {
1789 $encounter_id = $drow['encounter'];
1793 // If there are none, just redisplay the last receipt and exit.
1795 if (!$encounter_id) {
1796 generate_receipt($patient_id);
1797 exit();
1800 // Form requires billing permission.
1801 if (!AclMain::aclCheckCore('admin', 'super') && !AclMain::aclCheckCore('acct', 'bill')) {
1802 echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Patient Checkout")]);
1803 exit;
1806 // We have $patient_id and $encounter_id. Generate checksum if not already done.
1807 if (!$current_checksum) {
1808 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1811 // Get the valid practitioners, including those not active.
1812 $arr_users = array();
1813 $ures = sqlStatement(
1814 "SELECT id, username FROM users WHERE " .
1815 "( authorized = 1 OR info LIKE '%provider%' ) AND username != ''"
1817 while ($urow = sqlFetchArray($ures)) {
1818 $arr_users[$urow['id']] = '1';
1821 // Now write a data entry form:
1822 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
1823 // List unbilled product sales for the patient.
1824 // Present an editable dollar amount for each line item, a total
1825 // which is also the default value of the input payment amount,
1826 // and OK and Cancel buttons.
1828 <!DOCTYPE html>
1829 <html>
1830 <head>
1831 <?php Header::setupHeader(['datetime-picker']);?>
1833 <title><?php echo xlt('Patient Checkout'); ?></title>
1835 <style>
1836 @media (min-width: 992px){
1837 .modal-lg {
1838 width: 1000px !Important;
1841 </style>
1843 <script>
1844 var mypcc = <?php echo js_escape($GLOBALS['phone_country_code']); ?>;
1846 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
1848 // This clears tax amounts in preparation for recomputing taxes.
1849 // TBD: Probably don't need this at all.
1850 function clearTax(visible) {
1851 var f = document.forms[0];
1852 for (var i = 0; f['totaltax[' + i + ']']; ++i) {
1853 f['totaltax[' + i + ']'].value = '0.00';
1857 // This computes taxes and extended amount for the specified line, and returns
1858 // the extended amount.
1859 function calcTax(lino) {
1860 var f = document.forms[0];
1861 var pfx = 'line[' + lino + ']';
1862 var taxable = parseFloat(f[pfx + '[charge]'].value);
1863 var adjust = 0.00;
1864 if (f[pfx + '[adjust]']) {
1865 adjust = parseFloat(f[pfx + '[adjust]'].value);
1867 var adjreason = '';
1868 if (f[pfx + '[memo]']) {
1869 adjreason = f[pfx + '[memo]'].value;
1871 var extended = taxable - adjust;
1872 if (true
1873 <?php
1874 // Generate JavaScript that checks if the chosen adjustment type is to be
1875 // applied before taxes are computed. option_value 1 indicates that the
1876 // "After Taxes" checkbox is checked for an adjustment type.
1877 $tmpres = sqlStatement(
1878 "SELECT option_id FROM list_options WHERE " .
1879 "list_id = 'adjreason' AND option_value = 1 AND activity = 1"
1881 while ($tmprow = sqlFetchArray($tmpres)) {
1882 echo " && adjreason != " . js_escape($tmprow['option_id']) . "\n";
1886 taxable -= adjust;
1888 var taxnumrates = f[pfx + '[taxnumrates]'].value;
1889 var rates = taxnumrates.split(':');
1890 for (var i = 0; i < rates.length; ++i) {
1891 if (! f[pfx + '[tax][' + i + ']']) {
1892 break;
1894 var tax = 0;
1895 if (f[pfx + '[billtime]'].value) {
1896 // Line item is billed, use the tax amounts that were previously set for it.
1897 tax = parseFloat(f[pfx + '[tax][' + i + ']'].value);
1899 else {
1900 tax = taxable * parseFloat(rates[i]);
1901 tax = parseFloat(tax.toFixed(<?php echo $currdecimals ?>));
1902 if (isNaN(tax)) {
1903 alert('Tax rate not numeric at line ' + lino);
1905 f[pfx + '[tax][' + i + ']'].value = tax.toFixed(<?php echo $currdecimals ?>);
1907 extended += tax;
1908 var totaltax = parseFloat(f['totaltax[' + i + ']'].value) + tax;
1909 f['totaltax[' + i + ']'].value = totaltax.toFixed(<?php echo $currdecimals ?>);
1911 f[pfx + '[amount]'].value = extended.toFixed(<?php echo $currdecimals ?>);
1912 return extended;
1915 // This mess recomputes total charges and optionally applies a discount.
1916 // As part of this, taxes and extended amount are recomputed for each line.
1917 function computeDiscountedTotals(discount, visible) {
1918 clearTax(visible);
1919 var f = document.forms[0];
1920 var total = 0.00;
1921 for (var lino = 0; f['line[' + lino + '][code_type]']; ++lino) {
1922 var code_type = f['line[' + lino + '][code_type]'].value;
1923 // price is price per unit when the form was originally generated.
1924 // By contrast, amount is the dynamically-generated discounted line total.
1925 var price = parseFloat(f['line[' + lino + '][price]'].value);
1926 if (isNaN(price)) {
1927 alert('Price not numeric at line ' + lino);
1929 if (code_type == 'COPAY' || code_type == 'TAX') {
1930 // I think this case is obsolete now.
1931 total += parseFloat(price.toFixed(<?php echo $currdecimals ?>));
1932 continue;
1934 // Compute and set taxes and extended amount for the given line.
1935 // This also returns the extended amount.
1936 total += calcTax(lino);
1938 if (visible) {
1939 f.totalchg.value = total.toFixed(<?php echo $currdecimals ?>);
1941 return total - discount;
1944 // This computes and returns the total of payments.
1945 function computePaymentTotal() {
1946 var f = document.forms[0];
1947 var total = 0.00;
1948 for (var lino = 0; ('oldpay[' + lino + '][amount]') in f; ++lino) {
1949 var amount = parseFloat(f['oldpay[' + lino + '][amount]'].value);
1950 if (isNaN(amount)) {
1951 continue;
1953 amount = parseFloat(amount.toFixed(<?php echo $currdecimals ?>));
1954 total += amount;
1956 for (var lino = 0; ('payment[' + lino + '][amount]') in f; ++lino) {
1957 var amount = parseFloat(f['payment[' + lino + '][amount]'].value);
1958 if (isNaN(amount)) {
1959 amount = parseFloat(0);
1961 amount = parseFloat(amount.toFixed(<?php echo $currdecimals ?>));
1962 total += amount;
1963 // Set payment row's description to Refund if the amount is negative.
1964 var title = amount < 0 ? <?php echo xlj('Refund'); ?> : <?php echo xlj('New payment'); ?>;
1965 var span = document.getElementById('paytitle_' + lino);
1966 span.innerHTML = title;
1968 return total;
1971 // Recompute default payment amount with any discount applied, but
1972 // not if there is more than one input payment line.
1973 // This is called when the discount amount is changed, and initially.
1974 // As a side effect the tax line items are recomputed and
1975 // setComputedValues() is called.
1976 function billingChanged() {
1977 var f = document.forms[0];
1978 var discount = parseFloat(f.form_discount.value);
1979 if (isNaN(discount)) {
1980 discount = 0;
1982 <?php if (!$GLOBALS['discount_by_money']) { ?>
1983 // This site discounts by percentage, so convert it to a money amount.
1984 if (discount > 100) {
1985 discount = 100;
1987 if (discount < 0 ) {
1988 discount = 0;
1990 discount = 0.01 * discount * computeDiscountedTotals(0, false);
1991 <?php } ?>
1992 var total = computeDiscountedTotals(discount, true);
1993 // Get out if there is more than one input payment line.
1994 if (!('payment[1][amount]' in f)) {
1995 f['payment[0][amount]'].value = 0;
1996 total -= computePaymentTotal();
1997 f['payment[0][amount]'].value = total.toFixed(<?php echo $currdecimals ?>);
1999 setComputedValues();
2000 return true;
2003 // Function to return the adjustment type, if any, identified in a customer's Notes field.
2004 function adjTypeFromCustomer(customer) {
2005 var ret = '';
2006 <?php
2007 $tmpres = sqlStatement(
2008 "SELECT option_id, notes FROM list_options WHERE " .
2009 "list_id = 'chargecats' AND activity = 1"
2011 while ($tmprow = sqlFetchArray($tmpres)) {
2012 if (
2013 preg_match('/ADJ=(\w+)/', $tmprow['notes'], $matches) ||
2014 preg_match('/ADJ="(.*?)"/', $tmprow['notes'], $matches)
2016 echo " if (customer == " . js_escape($tmprow['option_id']) . ") ret = " . js_escape($matches[1]) . ";\n";
2020 return ret;
2023 // A line item adjustment was changed, so recompute stuff.
2024 function lineDiscountChanged(lino) {
2025 var f = document.forms[0];
2026 var discount = parseFloat(f['line[' + lino + '][adjust]'].value);
2027 if (isNaN(discount)) {
2028 discount = 0;
2030 var charge = parseFloat(f['line[' + lino + '][charge]'].value);
2031 if (isNaN(charge)) {
2032 charge = 0;
2034 <?php if (!$GLOBALS['discount_by_money']) { ?>
2035 // This site discounts by percentage, so convert it to a money amount.
2036 if (discount > 100) {
2037 discount = 100;
2039 if (discount < 0 ) {
2040 discount = 0;
2042 discount = 0.01 * discount * charge;
2043 <?php } ?>
2044 var amount = charge - discount;
2045 f['line[' + lino + '][amount]'].value = amount.toFixed(<?php echo $currdecimals ?>);
2046 // alert(f['line[' + lino + '][amount]'].value); // debugging
2047 if (discount) {
2048 // Apply default adjustment type if one is specified in the customer (charge category).
2049 var custElem = f['line[' + lino + '][chargecat]'];
2050 var adjtElem = f['line[' + lino + '][memo]'];
2051 if (custElem && custElem.value && adjtElem && !adjtElem.value) {
2052 var ccAdjType = adjTypeFromCustomer(custElem.value);
2053 if (ccAdjType) {
2054 adjtElem.value = ccAdjType;
2058 return billingChanged();
2061 // Set Total Payments, Difference and Balance Due when any amount changes.
2062 function setComputedValues() {
2063 var f = document.forms[0];
2064 var payment = computePaymentTotal();
2065 var difference = computeDiscountedTotals(0, false) - payment;
2066 var discount = parseFloat(f.form_discount.value);
2067 if (isNaN(discount)) {
2068 discount = 0;
2070 <?php if (!$GLOBALS['discount_by_money']) { ?>
2071 // This site discounts by percentage, so convert it to a money amount.
2072 if (discount > 100) {
2073 discount = 100;
2075 if (discount < 0 ) {
2076 discount = 0;
2078 discount = 0.01 * discount * computeDiscountedTotals(0, false);
2079 <?php } ?>
2080 var balance = difference - discount;
2081 f.form_totalpay.value = payment.toFixed(<?php echo $currdecimals ?>);
2082 f.form_difference.value = difference.toFixed(<?php echo $currdecimals ?>);
2083 f.form_balancedue.value = balance.toFixed(<?php echo $currdecimals ?>);
2084 return true;
2087 // This is called when [Compute] is clicked by the user.
2088 // Computes and sets the discount value from total charges less payment.
2089 // This also calls setComputedValues() so the balance due will be correct.
2090 function computeDiscount() {
2091 var f = document.forms[0];
2092 var charges = computeDiscountedTotals(0, false);
2093 var payment = computePaymentTotal();
2094 var discount = charges - payment;
2095 <?php if (!$GLOBALS['discount_by_money']) { ?>
2096 // This site discounts by percentage, so convert to that.
2097 discount = charges ? (100 * discount / charges) : 0;
2098 f.form_discount.value = discount.toFixed(4);
2099 <?php } else { ?>
2100 f.form_discount.value = discount.toFixed(<?php echo $currdecimals ?>);
2101 <?php } ?>
2102 setComputedValues();
2103 return false;
2106 // When the main adjustment reason changes, duplicate it to all per-line reasons.
2107 function discountTypeChanged() {
2108 var f = document.forms[0];
2109 if (f.form_discount_type && f.form_discount_type.selectedIndex) {
2110 for (lino = 0; f['line[' + lino + '][memo]']; ++lino) {
2111 // But do not change adjustment reason for billed items.
2112 if (f['line[' + lino + '][billtime]'].value) {
2113 continue;
2115 f['line[' + lino + '][memo]'].selectedIndex = f.form_discount_type.selectedIndex;
2120 // When the main charge category changes, duplicate it to all per-line categories.
2121 function chargeCategoryChanged() {
2122 var f = document.forms[0];
2123 if (f.form_charge_category && f.form_charge_category.selectedIndex) {
2124 for (lino = 0; f['line[' + lino + '][chargecat]']; ++lino) {
2125 // But do not change categories for billed items.
2126 if (f['line[' + lino + '][billtime]'].value) {
2127 continue;
2129 f['line[' + lino + '][chargecat]'].selectedIndex = f.form_charge_category.selectedIndex;
2134 // This is specific to IPPF and Suriname.
2135 function check_referrals() {
2136 <?php if (!empty($GLOBALS['gbl_menu_surinam_insurance'])) { ?>
2137 var msg = '';
2138 var f = document.forms[0];
2139 var services_needing_referral = '';
2140 for (var lino = 0; f['line[' + lino + '][chargecat]']; ++lino) {
2141 var pfx = 'line[' + lino + ']';
2142 var cust = f[pfx+'[chargecat]'].value;
2143 var price = parseFloat(f[pfx + '[price]'].value);
2144 if (isNaN(price)) {
2145 price = 0;
2147 if ((cust == 'SZF' || cust == 'KL-0098') && f[pfx+'[code_type]'].value != 'PROD' && price != 0) {
2148 services_needing_referral += f[pfx+'[code_type]'].value + ':' + f[pfx+'[code]'].value + ';';
2151 if (services_needing_referral) {
2152 top.restoreSession();
2153 $.ajax({
2154 dataType: "json",
2155 async: false, // We cannot continue without an answer.
2156 url: "<?php echo $GLOBALS['webroot']; ?>/library/ajax/check_szf_referrals_ajax.php",
2157 data: {
2158 "pid": <?php echo intval($patient_id); ?>,
2159 "encounter": <?php echo intval($encounter_id); ?>,
2160 "services": services_needing_referral
2162 success: function (jsondata, textstatus) {
2163 msg = jsondata['message'];
2167 if (msg && !confirm(msg)) {
2168 return false;
2170 <?php } ?>
2171 return true;
2174 // This is specific to IPPF and the NetSuite project.
2175 function check_giftcards() {
2176 <?php if (empty($GLOBALS['gbl_menu_netsuite'])) { ?>
2177 return true;
2178 <?php } else { ?>
2179 var f = document.forms[0];
2180 // If there is no gift card customer return true.
2181 var gc_customer_exists = false;
2182 for (lino = 0; f['line[' + lino + '][chargecat]']; ++lino) {
2183 var chargecat = f['line[' + lino + '][chargecat]'].value;
2184 if (
2185 1 == 2
2186 <?php
2187 $lres = sqlStatement(
2188 "SELECT option_id FROM list_options WHERE " .
2189 "list_id = 'chargecats' AND activity = 1 AND notes LIKE '%GIFTCARD=Y%' ORDER BY seq, title"
2191 while ($lrow = sqlFetchArray($lres)) {
2192 echo " || chargecat == " . js_escape($lrow['option_id']) . "\n";
2196 gc_customer_exists = true;
2199 if (!gc_customer_exists) {
2200 return true;
2202 // If there is a gift card payment method in the form return true.
2203 for (lino = 0; f['payment[' + lino + '][method]']; ++lino) {
2204 var method = f['payment[' + lino + '][method]'].value;
2205 if (
2206 1 == 2
2207 <?php
2208 $lres = sqlStatement(
2209 "SELECT option_id FROM list_options WHERE " .
2210 "list_id = 'paymethod' AND activity = 1 AND notes LIKE '%GIFTCARD=Y%' ORDER BY seq, title"
2212 while ($lrow = sqlFetchArray($lres)) {
2213 echo " || method == " . js_escape($lrow['option_id']) . "\n";
2217 if (!f['payment[' + lino + '][refno]'].value) {
2218 // There is a gift card payment method but no gift card ID is entered.
2219 alert(<?php echo xlj("Enter Gift Card number in the Reference field"); ?>);
2220 return false;
2222 return true; // There is a gift card payment method or no such methods exist.
2225 // There is a gift card customer but no gift card payment method.
2226 alert(<?php echo xlj("Gift Card Customer requires at least one Gift Card Payment Method"); ?>);
2227 return false;
2228 <?php } ?>
2231 function validate() {
2232 var f = document.forms[0];
2233 var missingtypeamt = false;
2234 var missingtypeany = false;
2235 for (lino = 0; f['line[' + lino + '][memo]']; ++lino) {
2236 if (f['line[' + lino + '][memo]'].selectedIndex == 0 && f['line[' + lino + '][billtime]'].value == '') {
2237 missingtypeany = true;
2238 if (parseFloat(f['line[' + lino + '][adjust]'].value) != 0) {
2239 missingtypeamt = true;
2243 <?php if (false /* adjustments_indicate_insurance */) { ?>
2244 if (missingtypeany) {
2245 alert(<?php echo xlj('Adjustment type is required for every line item.') ?>);
2246 return false;
2248 <?php } else { ?>
2249 if (missingtypeamt) {
2250 alert(<?php echo xlj('Adjustment type is required for each line with an adjustment.') ?>);
2251 return false;
2253 <?php } ?>
2254 if (!check_referrals()) {
2255 return false;
2257 if (!check_giftcards()) {
2258 return false;
2260 top.restoreSession();
2261 return true;
2264 </script>
2265 <?php
2266 // TBD: Not sure this will be used here.
2267 $arrOeUiSettings = array(
2268 'heading_title' => xl('Patient Checkout'),
2269 'include_patient_name' => true,// use only in appropriate pages
2270 'expandable' => false,
2271 'expandable_files' => array(),//all file names need suffix _xpd
2272 'action' => "",//conceal, reveal, search, reset, link or back
2273 'action_title' => "",
2274 'action_href' => "",//only for actions - reset, link or back
2275 'show_help_icon' => false,
2276 'help_file_name' => ""
2278 $oemr_ui = new OemrUI($arrOeUiSettings);
2280 </head>
2282 <body>
2284 <?php
2285 echo "<form method='post' action='pos_checkout.php?rde=" . attr_url($rapid_data_entry);
2286 if ($encounter_id) {
2287 echo "&enid=" . attr_url($encounter_id);
2289 if (!empty($_GET['framed'])) {
2290 echo '&framed=1';
2292 echo "' onsubmit='return validate()'>\n";
2293 echo "<input type='hidden' name='form_pid' value='" . attr($patient_id) . "' />\n";
2295 <input type="hidden" name="csrf_token_form" value="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" />
2297 <center>
2300 <table cellspacing='5' id='paytable' width='85%'>
2301 <?php
2302 $inv_date = '';
2303 $inv_provider = 0;
2304 $inv_payer = 0;
2305 $gcac_related_visit = false;
2306 $gcac_service_provided = false;
2308 // This is set by write_form_headers() when the encounter is known.
2309 $encounter_date = '';
2311 // This to save copays from the billing table.
2312 $aCopays = array();
2314 $lino = 0;
2316 $query = "SELECT id, date, code_type, code, modifier, code_text, " .
2317 "provider_id, payer_id, units, fee, encounter, billed, bill_date, chargecat " .
2318 "FROM billing WHERE pid = ? AND encounter = ? AND activity = 1 AND " .
2319 "code_type != 'TAX' ORDER BY id ASC";
2320 $bres = sqlStatement($query, array($patient_id, $encounter_id));
2322 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, s.quantity, " .
2323 "s.encounter, s.drug_id, s.billed, s.bill_date, s.selector, s.chargecat, d.name, r.provider_id " .
2324 "FROM drug_sales AS s " .
2325 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
2326 "LEFT OUTER JOIN prescriptions AS r ON r.id = s.prescription_id " .
2327 "WHERE s.pid = ? AND s.encounter = ? " .
2328 "ORDER BY s.sale_id ASC";
2329 $dres = sqlStatement($query, array($patient_id, $encounter_id));
2331 // Process billing table items. Note this includes co-pays.
2332 // Items that are not allowed to have a fee are skipped.
2334 while ($brow = sqlFetchArray($bres)) {
2335 $thisdate = substr($brow['date'], 0, 10);
2336 $code_type = $brow['code_type'];
2337 $inv_payer = $brow['payer_id'];
2338 if (!$inv_date || $inv_date < $thisdate) {
2339 $inv_date = $thisdate;
2341 // Co-pays are saved for later.
2342 if ($code_type == 'COPAY') {
2343 $aCopays[] = $brow;
2344 continue;
2347 $billtime = $brow['billed'] ? $brow['bill_date'] : '';
2349 // Collect tax rates, related code and provider ID.
2350 $taxrates = '';
2351 $related_code = '';
2352 if (!empty($code_types[$code_type]['fee'])) {
2353 $query = "SELECT taxrates, related_code FROM codes WHERE code_type = ? AND " .
2354 "code = ? AND ";
2355 $binds = array($code_types[$code_type]['id'], $brow['code']);
2356 if ($brow['modifier']) {
2357 $query .= "modifier = ?";
2358 $binds[] = $brow['modifier'];
2359 } else {
2360 $query .= "(modifier IS NULL OR modifier = '')";
2362 $query .= " LIMIT 1";
2363 $tmp = sqlQuery($query, $binds);
2364 $taxrates = $tmp['taxrates'];
2365 $related_code = $tmp['related_code'];
2366 markTaxes($taxrates);
2369 // Write the line item if it allows fees or is not a diagnosis.
2370 if (!empty($code_types[$code_type]['fee']) || empty($code_types[$code_type]['diag'])) {
2371 write_form_line(
2372 $code_type,
2373 $brow['code'],
2374 $brow['id'],
2375 $thisdate,
2376 ucfirst(strtolower($brow['code_text'])),
2377 $brow['fee'],
2378 $brow['units'],
2379 $taxrates,
2380 $billtime,
2381 $brow['chargecat']
2385 // Custom logic for IPPF to determine if a GCAC issue applies.
2386 if ($GLOBALS['ippf_specific'] && $related_code) {
2387 $relcodes = explode(';', $related_code);
2388 foreach ($relcodes as $codestring) {
2389 if ($codestring === '') {
2390 continue;
2392 list($codetype, $code) = explode(':', $codestring);
2393 if ($codetype !== 'IPPF2') {
2394 continue;
2396 if (preg_match('/^211/', $code)) {
2397 $gcac_related_visit = true;
2398 if (
2399 preg_match('/^211313030110/', $code) // Medical
2400 || preg_match('/^211323030230/', $code) // Surgical
2401 || preg_match('/^211403030110/', $code) // Incomplete Medical
2402 || preg_match('/^211403030230/', $code) // Incomplete Surgical
2404 $gcac_service_provided = true;
2411 // Process drug sales / products.
2413 while ($drow = sqlFetchArray($dres)) {
2414 if ($encounter_id && $drow['encounter'] != $encounter_id) {
2415 continue;
2417 $thisdate = $drow['sale_date'];
2418 if (!$encounter_id) {
2419 $encounter_id = $drow['encounter'];
2421 if (!$inv_provider && !empty($arr_users[$drow['provider_id']])) {
2422 $inv_provider = $drow['provider_id'] + 0;
2424 if (!$inv_date || $inv_date < $thisdate) {
2425 $inv_date = $thisdate;
2427 $billtime = $drow['billed'] ? $drow['bill_date'] : '';
2429 // Accumulate taxes for this product.
2430 $tmp = sqlQuery(
2431 "SELECT taxrates FROM drug_templates WHERE drug_id = ? ORDER BY selector LIMIT 1",
2432 array($drow['drug_id'])
2434 $taxrates = $tmp['taxrates'];
2435 markTaxes($taxrates);
2437 $tmpname = $drow['name'];
2438 if ($tmpname !== $drow['selector']) {
2439 $tmpname .= ' / ' . $drow['selector'];
2441 $units = $drow['quantity'] / FeeSheet::getBasicUnits($drow['drug_id'], $drow['selector']);
2443 write_form_line(
2444 'PROD',
2445 $drow['drug_id'],
2446 $drow['sale_id'],
2447 $thisdate,
2448 $tmpname,
2449 $drow['fee'],
2450 $units,
2451 $taxrates,
2452 $billtime,
2453 $drow['chargecat']
2457 // Line for total charges.
2458 $totalchg = formatMoneyNumber($totalchg);
2459 echo " <tr>\n";
2460 echo " <td class='bold' colspan='" . (!empty($GLOBALS['gbl_checkout_charges']) ? 4 : 3) .
2461 "' align='right'>" . xlt('Total Charges This Visit') . "</td>\n";
2462 echo " <td class='text' align='right'><input type='text' name='totalcba' " .
2463 "value='" . attr($totalchg) . "' size='6' maxlength='8' " .
2464 "style='text-align:right;background-color:transparent' readonly";
2465 echo "></td>\n";
2466 if (!$TAXES_AFTER_ADJUSTMENT) {
2467 for ($i = 0; $i < count($taxes); ++$i) {
2468 echo " <td class='text' align='right'><input type='text' name='totaltax[$i]' " .
2469 "value='0.00' size='6' maxlength='8' " .
2470 "style='text-align:right;background-color:transparent' readonly";
2471 echo "></td>\n";
2474 if (!empty($GLOBALS['gbl_charge_categories'])) {
2475 echo " <td class='text' align='right'>&nbsp;</td>\n"; // Empty space in charge category column.
2477 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
2478 // Note $totalchg is the total of charges before adjustments, and the following
2479 // field will be recomputed at onload time and as adjustments are entered.
2480 echo " <td class='text' align='right'>&nbsp;</td>\n"; // Empty space in adjustment type column.
2481 echo " <td class='text' align='right'>&nbsp;</td>\n"; // TBD: Total adjustments can go here.
2483 if ($TAXES_AFTER_ADJUSTMENT) {
2484 for ($i = 0; $i < count($taxes); ++$i) {
2485 echo " <td class='text' align='right'><input type='text' name='totaltax[$i]' " .
2486 "value='0.00' size='6' maxlength='8' " .
2487 "style='text-align:right;background-color:transparent' readonly";
2488 echo "></td>\n";
2491 echo " <td class='text' align='right'><input type='text' name='totalchg' " .
2492 "value='" . attr($totalchg) . "' size='6' maxlength='8' " .
2493 "style='text-align:right;background-color:transparent' readonly";
2494 echo "></td>\n";
2495 echo " </tr>\n";
2498 <tr>
2499 <td class='title' colspan='<?php echo 5 + $num_optional_columns; ?>'
2500 style='border-top:1px solid black; padding-top:5pt;'>
2501 <b><?php echo xlt('Payments'); ?></b>
2502 </td>
2503 </tr>
2505 <?php
2506 // Start new section for payments.
2507 echo " <td class='bold' colspan='$form_num_type_columns'>" . xlt('Type') . "</td>\n";
2508 echo " <td class='bold' colspan='$form_num_method_columns'>" . xlt('Payment Method') . "</td>\n";
2509 echo " <td class='bold' colspan='$form_num_ref_columns'>" . xlt('Reference') . "</td>\n";
2510 echo " <td class='bold' colspan='$form_num_amount_columns' align='right' nowrap>" . xlt('Payment Amount') . "</td>\n";
2511 echo " </tr>\n";
2513 $lino = 0;
2515 // Write co-pays.
2516 foreach ($aCopays as $brow) {
2517 $thisdate = substr($brow['date'], 0, 10);
2518 write_old_payment_line(
2519 xl('Prepayment'),
2520 $thisdate,
2521 $brow['code_text'],
2523 0 - $brow['fee']
2527 // Write any adjustments left in the aAdjusts array. This should only happen if
2528 // there was an invoice-level discount in a prior checkout of this encounter.
2529 foreach ($aAdjusts as $arow) {
2530 $memo = $arow['memotitle'];
2531 if ($arow['adj_amount'] == 0 && $memo === '') {
2532 continue;
2534 $reference = $arow['reference'];
2535 write_old_payment_line(
2536 xl('Adjustment'),
2537 $thisdate,
2538 $memo,
2539 $reference,
2540 $arow['adj_amount']
2544 // Write ar_activity payments.
2545 $ares = sqlStatement(
2546 "SELECT " .
2547 "a.payer_type, a.pay_amount, a.memo, s.session_id, s.reference, s.check_date " .
2548 "FROM ar_activity AS a " .
2549 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
2550 "a.pid = ? AND a.encounter = ? AND a.deleted IS NULL AND a.pay_amount != 0 " .
2551 "ORDER BY s.check_date, a.sequence_no",
2552 array($patient_id, $encounter_id)
2554 while ($arow = sqlFetchArray($ares)) {
2555 $memo = $arow['memo'];
2556 $reference = $arow['reference'];
2557 if (empty($arow['session_id'])) {
2558 $atmp = explode(' ', $memo, 2);
2559 $memo = $atmp[0];
2560 $reference = $atmp[1];
2562 $rowtype = $arow['payer_type'] ? xl('Insurance payment') : xl('Prepayment');
2563 write_old_payment_line(
2564 $rowtype,
2565 $thisdate,
2566 $memo,
2567 $reference,
2568 $arow['pay_amount']
2572 // Line for total payments.
2573 echo " <tr id='totalpay'>\n";
2574 echo " <td class='bold' colspan='$form_num_type_columns'><a href='#' onclick='return addPayLine()'>[" . xlt('Add Row') . "]</a></td>\n";
2575 echo " <td class='bold' colspan='" . ($form_num_method_columns + $form_num_ref_columns) .
2576 "' align='right'>" . xlt('Total Payments This Visit') . "</td>\n";
2577 echo " <td class='text' align='right' colspan='$form_num_amount_columns'><input type='text' name='form_totalpay' " .
2578 "value='' size='6' maxlength='8' " .
2579 "style='text-align:right;background-color:transparent' readonly";
2580 echo "></td>\n";
2581 echo " </tr>\n";
2583 // Line for Difference.
2584 echo " <tr>\n";
2585 echo " <td class='text' colspan='" . (5 + $num_optional_columns) .
2586 "' style='border-top:1px solid black; font-size:1pt; padding:0px;'>&nbsp;</td>\n";
2587 echo " </tr>\n";
2589 echo " <tr";
2590 // Hide this if only showing line item adjustments.
2591 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
2592 echo " style='display:none'";
2594 echo ">\n";
2595 echo " <td class='title' colspan='" . ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) .
2596 "' align='right'><b>" . xlt('Difference') . "</b></td>\n";
2597 echo " <td class='text' align='right' colspan='$form_num_amount_columns'><input type='text' name='form_difference' " .
2598 "value='' size='6' maxlength='8' " .
2599 "style='text-align:right;background-color:transparent' readonly";
2600 echo "></td>\n";
2601 echo " </tr>\n";
2603 if ($encounter_id) {
2604 $erow = sqlQuery(
2605 "SELECT provider_id FROM form_encounter WHERE pid = ? AND encounter = ? " .
2606 "ORDER BY id DESC LIMIT 1",
2607 array($patient_id, $encounter_id)
2609 $inv_provider = $erow['provider_id'] + 0;
2612 // Line for Discount.
2613 echo " <tr";
2614 // Hide this if only showing line item adjustments.
2615 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
2616 echo " style='display:none'";
2618 echo ">\n";
2619 echo " <td class='bold' colspan='" . ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) .
2620 "' align='right'>";
2621 if (AclMain::aclCheckCore('acct', 'disc') || AclMain::aclCheckCore('admin', 'super')) {
2622 echo "<a href='#' onclick='return computeDiscount()'>[" . xlt('Compute') . "]</a> <b>";
2623 echo xlt('Discount/Adjustment') . "</b></td>\n";
2624 echo " <td class='text' align='right' colspan='$form_num_amount_columns'>" .
2625 "<input type='text' name='form_discount' " .
2626 "value='' size='6' maxlength='8' onkeyup='billingChanged()' " .
2627 "style='text-align:right' />";
2628 } else {
2629 echo "" . xlt('Discount/Adjustment') . "</td>\n";
2630 echo " <td class='text' align='right' colspan='$form_num_amount_columns'>" .
2631 "<input type='text' name='form_discount' value='' size='6' " .
2632 "style='text-align:right;background-color:transparent' readonly />";
2634 echo "</td>\n";
2635 echo " </tr>\n";
2637 // Line for Balance Due
2638 echo " <tr>\n";
2639 echo " <td class='title' colspan='" . ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) .
2640 "' align='right'>" . xlt('Balance Due') . "</td>\n";
2641 echo " <td class='text' align='right' colspan='$form_num_amount_columns'>" .
2642 "<input type='text' name='form_balancedue' " .
2643 "value='' size='6' maxlength='8' " .
2644 "style='text-align:right;background-color:transparent' readonly";
2645 echo "></td>\n";
2646 echo " </tr>\n";
2649 <tr>
2650 <td class='bold' colspan='<?php echo ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) +
2651 (empty($GLOBALS['gbl_charge_categories']) ? 0 : 1); ?>' align='right'>
2652 <label class="control-label" for="form_date"><?php echo xlt('Posting Date'); ?>:</label>
2653 </td>
2654 <td class='text' colspan='<?php echo $form_num_amount_columns; ?>' align='right'>
2655 <input type='text' class='form-control datepicker' id='form_date' name='form_date'
2656 title='yyyy-mm-dd date of service'
2657 value='<?php echo attr($encounter_date) ?>' />
2658 </td>
2659 </tr>
2661 <?php
2662 // A current invoice reference number may be present if there was a previous checkout.
2663 $tmprow = sqlQuery(
2664 "SELECT invoice_refno FROM form_encounter WHERE " .
2665 "pid = ? AND encounter = ?",
2666 array($patient_id, $encounter_id)
2668 $current_irnumber = $tmprow['invoice_refno'];
2670 if (!$current_irnumber) {
2671 // If this user has a non-empty irnpool assigned, show the pending
2672 // invoice reference number.
2673 $irnumber = BillingUtilities::getInvoiceRefNumber();
2674 if (!empty($irnumber)) {
2676 <tr>
2677 <td class='bold' colspan='<?php echo ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) +
2678 (empty($GLOBALS['gbl_charge_categories']) ? 0 : 1); ?>' align='right'>
2679 <?php echo xlt('Tentative Invoice Ref No'); ?>
2680 </td>
2681 <td class='text' align='right' colspan='<?php echo $form_num_amount_columns; ?>'>
2682 <?php echo text($irnumber); ?>
2683 </td>
2684 </tr>
2685 <?php
2686 } else if (!empty($GLOBALS['gbl_mask_invoice_number'])) {
2687 // Otherwise if there is an invoice reference number mask, ask for the refno.
2689 <tr>
2690 <td class='bold' colspan='<?php echo ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) +
2691 (empty($GLOBALS['gbl_charge_categories']) ? 0 : 1); ?>' align='right'>
2692 <?php echo xlt('Invoice Reference Number'); ?>
2693 </td>
2694 <td class='text' align='right' colspan='<?php echo $form_num_amount_columns; ?>'>
2695 <input type='text' name='form_irnumber' size='10' value=''
2696 onkeyup='maskkeyup(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number']); ?>")'
2697 onblur='maskblur(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number']); ?>")'
2699 </td>
2700 </tr>
2701 <?php
2706 <tr>
2707 <td class='text' colspan='<?php echo 5 + $num_optional_columns; ?>' align='center'>
2708 &nbsp;<br>
2709 <input type='submit' name='form_save' value='<?php echo xlt('Save'); ?>'
2710 <?php if ($rapid_data_entry) { ?>
2711 style='background-color:#cc0000';color:#ffffff'
2712 <?php } ?>
2713 /> &nbsp;
2714 <?php if (empty($_GET['framed'])) { ?>
2715 <input type='button' value='Cancel' onclick='window.close()' />
2716 <?php } ?>
2717 <input type='hidden' name='form_provider' value='<?php echo attr($inv_provider); ?>' />
2718 <input type='hidden' name='form_payer' value='<?php echo attr($inv_payer); ?>' />
2719 <input type='hidden' name='form_encounter' value='<?php echo attr($encounter_id); ?>' />
2720 <input type='hidden' name='form_checksum' value='<?php echo attr($current_checksum); ?>' />
2721 </td>
2722 </tr>
2724 </table>
2726 </center>
2728 </form>
2730 <script>
2732 // Add a line for entering a payment.
2733 // Declared down here because $form_num_*_columns must be defined.
2734 var paylino = 0;
2735 function addPayLine() {
2736 var table = document.getElementById('paytable');
2737 for (var i = 0; i < table.rows.length; ++i) {
2738 if (table.rows[i].id == 'totalpay') {
2739 var row = table.insertRow(i);
2740 var cell;
2741 <?php
2742 foreach ($aCellHTML as $ix => $html) {
2743 echo " var html = \"$html\";\n";
2744 echo " cell = row.insertCell(row.cells.length);\n";
2745 if ($ix == 0) {
2746 echo " cell.colSpan = $form_num_type_columns;\n";
2748 if ($ix == 1) {
2749 echo " cell.colSpan = $form_num_method_columns;\n";
2751 if ($ix == 2) {
2752 echo " cell.colSpan = $form_num_ref_columns;\n";
2754 if ($ix == 3) {
2755 echo " cell.colSpan = $form_num_amount_columns;\n";
2757 echo " cell.innerHTML = html.replace(/%d/, paylino);\n";
2760 cell.align = 'right'; // last cell is right-aligned
2761 ++paylino;
2762 break;
2765 return false;
2769 // TBD: Clean up javascript indentation from here on. ////////////////////////////
2772 // Pop up the Payments window and close this one.
2773 function payprevious() {
2774 var width = 750;
2775 var height = 550;
2776 var loc = '../patient_file/front_payment.php?omitenc=' + <?php echo js_url($encounter_id); ?>;
2777 <?php if (empty($_GET['framed'])) { ?>
2778 opener.parent.left_nav.dlgopen(loc, '_blank', width, height);
2779 window.close();
2780 <?php } else { ?>
2781 var tmp = parent.left_nav ? parent.left_nav : parent.parent.left_nav;
2782 tmp.dlgopen(loc, '_blank', width, height);
2783 <?php } ?>
2786 discountTypeChanged();
2787 addPayLine();
2788 billingChanged();
2790 <?php
2791 if ($alertmsg) {
2792 echo "alert(" . js_escape($alertmsg) . ");\n";
2795 if ($gcac_related_visit && !$gcac_service_provided) {
2796 // Skip this warning if the GCAC visit form is not allowed.
2797 $grow = sqlQuery(
2798 "SELECT COUNT(*) AS count FROM layout_group_properties " .
2799 "WHERE grp_form_id = 'LBFgcac' AND grp_group_id = '' AND grp_activity = 1"
2801 if (!empty($grow['count'])) { // if gcac is used
2802 // Skip this warning if referral or abortion in TS.
2803 $grow = sqlQuery(
2804 "SELECT COUNT(*) AS count FROM transactions " .
2805 "WHERE title = 'Referral' AND refer_date IS NOT NULL AND " .
2806 "refer_date = ? AND pid = ?",
2807 array($inv_date, $patient_id)
2809 if (empty($grow['count'])) { // if there is no referral
2810 $grow = sqlQuery(
2811 "SELECT COUNT(*) AS count FROM forms " .
2812 "WHERE pid = ? AND encounter = ? AND " .
2813 "deleted = 0 AND formdir = 'LBFgcac'",
2814 array($patient_id, $encounter_id)
2816 if (empty($grow['count'])) { // if there is no gcac form
2817 echo " alert(" . xlj('This visit will need a GCAC form, referral or procedure service.') . ");\n";
2821 } // end if ($gcac_related_visit)
2823 if ($GLOBALS['ippf_specific']) {
2824 // More validation:
2825 // o If there is an initial contraceptive consult, make sure a LBFccicon form exists with that method on it.
2826 // o If a LBFccicon form exists with a new method on it, make sure the TS initial consult exists.
2828 require_once("$srcdir/contraception_billing_scan.inc.php");
2829 contraception_billing_scan($patient_id, $encounter_id);
2831 $csrow = sqlQuery(
2832 "SELECT field_value FROM shared_attributes WHERE pid = ? AND encounter = ? AND field_id = 'cgen_MethAdopt'",
2833 array($patient_id, $encounter_id)
2835 $csmethod = empty($csrow['field_value']) ? '' : $csrow['field_value'];
2837 if (($csmethod || $contraception_billing_code) && $csmethod != "IPPFCM:$contraception_billing_code") {
2838 $warningMessage = xl('Warning') . ': ';
2839 if (!$csmethod) {
2840 $warningMessage .= xl('there is a contraception service but no contraception form new method');
2841 } else if (!$contraception_billing_code) {
2842 $warningMessage .= xl('there is a contraception form new method but no contraception service');
2843 } else {
2844 $warningMessage .= xl('new method in contraception form does not match the contraception service');
2846 echo " alert(" . js_escape($warningMessage) . ");\n";
2850 </script>
2851 </body>
2852 </html>