revert composer from PR #5046 (#5058)
[openemr.git] / interface / patient_file / pos_checkout_ippf.php
blob1a6736fa6c8f2deec2f076f7d1fedc600e9a9788
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");
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\Core\Header;
63 use OpenEMR\OeUI\OemrUI;
64 use OpenEMR\Services\FacilityService;
66 $facilityService = new FacilityService();
68 // Change this to get the old appearance.
69 $TAXES_AFTER_ADJUSTMENT = true;
71 $currdecimals = intval($GLOBALS['currency_decimals'] ?? 2);
73 // Details default to yes now.
74 $details = (!isset($_GET['details']) || !empty($_GET['details'])) ? 1 : 0;
76 $patient_id = empty($_GET['ptid']) ? $pid : intval($_GET['ptid']);
77 $encounter_id = empty($_GET['enid']) ? 0 : intval($_GET['enid']);
78 $checkout_id = empty($_GET['coid']) ? '' : $_GET['coid']; // timestamp of checkout
80 // This flag comes from the Fee Sheet form and perhaps later others.
81 $rapid_data_entry = empty($_GET['rde']) ? 0 : 1;
83 if (
84 !AclMain::aclCheckCore('admin', 'super') &&
85 !AclMain::aclCheckCore('acct', 'bill') &&
86 !AclMain::aclCheckCore('acct', 'disc')
87 ) {
88 die("Not authorized!");
91 // This will be used for SQL timestamps that we write.
92 $this_bill_date = date('Y-m-d H:i:s');
94 // Get the patient's name and chart number.
95 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
97 // Adjustments from the ar_activity table.
98 $aAdjusts = array();
100 // Holds possible javascript error messages.
101 $alertmsg = '';
103 // Format a money amount with decimals but no other decoration.
104 // Second argument is used when extra precision is required.
105 function formatMoneyNumber($value, $extradecimals = 0)
107 return sprintf('%01.' . ($GLOBALS['currency_decimals'] + $extradecimals) . 'f', $value);
110 // Get a list item's title, translated if appropriate.
112 function getListTitle($list, $option)
114 $row = sqlQuery(
115 "SELECT title FROM list_options WHERE list_id = ? AND option_id = ? AND activity = 1",
116 array($list, $option)
118 if (empty($row['title'])) {
119 return $option;
121 return xl_list_label($row['title']);
124 function generate_layout_display_field($formid, $fieldid, $currvalue)
126 $frow = sqlQuery(
127 "SELECT * FROM layout_options WHERE form_id = ? AND field_id = ? LIMIT 1",
128 array($formid, $fieldid)
130 if (empty($frow)) {
131 return $currvalue;
133 return generate_display_field($frow, $currvalue);
136 // This creates and loads the array $aAdjusts of adjustment data for this encounter.
138 function load_adjustments($patient_id, $encounter_id)
140 global $aAdjusts;
141 // Create array aAdjusts from ar_activity rows for $encounter_id.
142 $aAdjusts = array();
143 $ares = sqlStatement(
144 "SELECT " .
145 "a.payer_type, a.adj_amount, a.memo, a.code_type, a.code, a.post_time, a.post_date, " .
146 "s.session_id, s.reference, s.check_date, lo.title AS memotitle " .
147 "FROM ar_activity AS a " .
148 "LEFT JOIN list_options AS lo ON lo.list_id = 'adjreason' AND lo.option_id = a.memo AND " .
149 "lo.activity = 1 " .
150 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
151 "a.pid = ? AND a.encounter = ? AND a.deleted IS NULL AND " .
152 "( a.adj_amount != 0 OR a.pay_amount = 0 ) " .
153 "ORDER BY s.check_date, a.sequence_no",
154 array($patient_id, $encounter_id)
156 while ($arow = sqlFetchArray($ares)) {
157 if (empty($arow['memotitle'])) {
158 $arow['memotitle'] = $arow['memo'];
160 $aAdjusts[] = $arow;
164 // Total and clear adjustments in $aAdjusts matching this line item. Should only
165 // happen for billed items, and matching includes the billing timestamp in order
166 // to handle the case of multiple checkouts.
167 function pull_adjustment($code_type, $code, $billtime, &$memo)
169 global $aAdjusts;
170 $adjust = 0;
171 $memo = '';
172 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
173 for ($i = 0; $i < count($aAdjusts); ++$i) {
174 if (
175 $aAdjusts[$i]['code_type'] == $code_type && $aAdjusts[$i]['code'] == $code &&
176 $aAdjusts[$i]['post_time'] == $billtime &&
177 ($aAdjusts[$i]['adj_amount'] != 0 || $aAdjusts[$i]['memotitle'] !== '')
179 $adjust += $aAdjusts[$i]['adj_amount'];
180 if ($memo && $aAdjusts[$i]['memotitle']) {
181 $memo .= ', ';
183 $memo .= $aAdjusts[$i]['memotitle'];
184 $aAdjusts[$i]['adj_amount'] = 0;
185 $aAdjusts[$i]['memotitle'] = '';
189 return $adjust;
192 // Generate $aTaxNames = array of tax names, and $aInvTaxes = array of taxes for this invoice.
193 // For a given tax ID and line ID, $aInvTaxes[$taxID][$lineID] = tax amount.
194 // $lineID identifies the invoice line item (product or service) that was taxed, and is of the
195 // form S:<billing.id> or P:<drug_sales.sale_id>
196 // Taxes may change from time to time and $aTaxNames reflects only the taxes that were present
197 // for this invoice.
199 function load_taxes($patient_id, $encounter)
201 global $aTaxNames, $aInvTaxes, $taxes;
202 global $num_optional_columns, $rcpt_num_method_columns, $rcpt_num_ref_columns, $rcpt_num_amount_columns;
203 global $form_num_type_columns, $form_num_method_columns, $form_num_ref_columns, $form_num_amount_columns;
205 $aTaxNames = array();
206 $aInvTaxes = array();
207 foreach ($taxes as $taxid => $taxarr) {
208 $aTaxNames[$taxid] = $taxarr[0];
209 $aInvTaxes[$taxid] = array();
212 $taxres = sqlStatement(
213 "SELECT code, fee, ndc_info FROM billing WHERE " .
214 "pid = ? AND encounter = ? AND code_type = 'TAX' AND activity = 1 " .
215 "ORDER BY id",
216 array($patient_id, $encounter)
218 while ($taxrow = sqlFetchArray($taxres)) {
219 $aInvTaxes[$taxrow['code']][$taxrow['ndc_info']] = $taxrow['fee'];
222 // Knowing the number of tax columns we can now compute the total number of optional
223 // columns and from that the colspan values for various things.
224 $num_optional_columns = (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1) +
225 (empty($GLOBALS['gbl_charge_categories']) ? 0 : 1) +
226 (empty($GLOBALS['gbl_checkout_line_adjustments']) ? 0 : 2) +
227 count($aTaxNames);
228 // Compute colspans for receipt payment rows.
229 // What's in play here are columns for Qty, Price, the optionals, and Total.
230 $rcpt_num_method_columns = 1;
231 $rcpt_num_ref_columns = 1;
232 if ($num_optional_columns == 1) {
233 $rcpt_num_method_columns = 2;
234 } else if ($num_optional_columns > 1) {
235 $rcpt_num_method_columns = 3;
236 $rcpt_num_ref_columns = $num_optional_columns - 1;
238 $rcpt_num_amount_columns = 3 + $num_optional_columns - $rcpt_num_method_columns - $rcpt_num_ref_columns;
239 // Compute colspans for form payment rows.
240 $form_num_type_columns = 2;
241 $form_num_method_columns = 1;
242 $form_num_ref_columns = 1;
243 if ($num_optional_columns > 0) {
244 $form_num_method_columns = 2;
246 if ($num_optional_columns > 1) {
247 $form_num_type_columns = 3;
249 $form_num_amount_columns = 5 + $num_optional_columns - $form_num_type_columns - $form_num_method_columns - $form_num_ref_columns;
252 // Use $lineid to match up (and delete) entries in $aInvTaxes with the line.
253 // $lineid looks like: S:<billing.id> or P:<drug_sales.sale_id>.
254 // This writes to the $aTaxes argument and returns the total tax for the line.
255 function pull_tax($lineid, &$aTaxes)
257 global $aInvTaxes;
258 $totlinetax = 0;
259 foreach ($aInvTaxes as $taxid => $taxarr) {
260 $aTaxes[$taxid] = 0;
261 if ($lineid !== '') {
262 foreach ($taxarr as $taxlineid => $tax) {
263 if ($taxlineid === $lineid) {
264 $aTaxes[$taxid] += $tax;
265 $totlinetax += $tax;
266 $aInvTaxes[$taxid][$taxlineid] = 0;
271 // $aTaxes now contains the total of each tax type (keyed on tax ID) for this line item,
272 // and those matched amounts are removed from $aInvTaxes.
273 return $totlinetax;
276 // Output HTML for a receipt line item.
278 function receiptDetailLine(
279 $code_type,
280 $code,
281 $description,
282 $quantity,
283 $charge,
284 &$aTotals = '',
285 $lineid = '',
286 $billtime = '',
287 $postdate = '',
288 $chargecat = ''
290 global $details, $TAXES_AFTER_ADJUSTMENT;
292 // Use $lineid to match up (and delete) entries in $aInvTaxes with the line.
293 $aTaxes = array();
294 $totlinetax = pull_tax($lineid, $aTaxes);
295 // $aTaxes now contains the total of each tax type for this line item, and those matched
296 // amounts are removed from $aInvTaxes.
298 $adjust = 0;
299 $memo = '';
300 $isadjust = false;
302 // If an adjustment, do appropriate interpretation.
303 if ($code_type === '') {
304 $isadjust = true;
305 $adjust = 0 - $charge;
306 $charge = 0;
307 list($payer, $code_type, $code) = explode('|', $code);
308 $memo = $description;
309 $description = $GLOBALS['simplified_demographics'] ? '' : "$payer ";
310 $description .= $code ? xl('Item Adjustment') : xl('Invoice Adjustment');
311 $quantity = '';
312 } else {
313 // Total and clear adjustments in $aAdjusts matching this line item.
314 $adjust += pull_adjustment($code_type, $code, $billtime, $memo);
317 $charge = formatMoneyNumber($charge);
318 $total = formatMoneyNumber($charge + $totlinetax - $adjust);
319 if (empty($quantity)) {
320 $quantity = 1;
322 $price = formatMoneyNumber($charge / $quantity, 2);
323 $tmp = formatMoneyNumber($price);
324 if ($price == $tmp) {
325 $price = $tmp;
327 if (is_array($aTotals)) {
328 $aTotals[0] += $quantity;
329 $aTotals[1] += $price;
330 $aTotals[2] += $charge;
331 $aTotals[3] += $adjust;
332 $aTotals[4] += $total;
333 // Accumulate columns 5 and beyond for taxes.
334 $i = 5;
335 foreach ($aTaxes as $tax) {
336 $aTotals[$i++] += $tax;
340 if (!$details) {
341 return;
343 if (empty($postdate) || substr($postdate, 0, 4) == '0000') {
344 $postdate = $billtime;
346 echo " <tr>\n";
347 echo " <td title='" . xla('Entered') . ' ' .
348 text(oeFormatShortDate($billtime)) . attr(substr($billtime, 10)) . "'>" .
349 text(oeFormatShortDate($postdate)) . "</td>\n";
350 echo " <td>" . text($code) . "</td>\n";
351 echo " <td>" . text($description) . "</td>\n";
352 echo " <td class='text-center'>" . ($isadjust ? '' : $quantity) . "</td>\n";
353 echo " <td class='text-right'>" . text(oeFormatMoney($price, false, true)) . "</td>\n";
355 if (!empty($GLOBALS['gbl_checkout_charges'])) {
356 echo " <td class='text-right'>" . text(oeFormatMoney($charge, false, true)) . "</td>\n";
359 if (!$TAXES_AFTER_ADJUSTMENT) {
360 // Write tax amounts.
361 foreach ($aTaxes as $tax) {
362 echo " <td class='text-right'>" . text(oeFormatMoney($tax, false, true)) . "</td>\n";
366 // Charge Category
367 if (!empty($GLOBALS['gbl_charge_categories'])) {
368 echo " <td class='text-right'>" . text($chargecat) . "</td>\n";
371 // Adjustment and its description.
372 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
373 echo " <td class='text-right'>" . text($memo) . "</td>\n";
374 echo " <td class='text-right'>" . text(oeFormatMoney($adjust, false, true)) . "</td>\n";
377 if ($TAXES_AFTER_ADJUSTMENT) {
378 // Write tax amounts.
379 foreach ($aTaxes as $tax) {
380 echo " <td class='text-right'>" . text(oeFormatMoney($tax, false, true)) . "</td>\n";
384 echo " <td class='text-right'>" . text(oeFormatMoney($total)) . "</td>\n";
385 echo " </tr>\n";
388 // Output HTML for a receipt payment line.
390 function receiptPaymentLine($paydate, $amount, $description = '', $method = '', $refno = '', $billtime = '')
392 global $aTaxNames, $num_optional_columns;
393 global $rcpt_num_method_columns, $rcpt_num_ref_columns, $rcpt_num_amount_columns;
394 $amount = formatMoneyNumber($amount); // make it negative
395 if ($description == 'Pt') {
396 $description = '';
398 // Resolve the payment method portion of the memo to display properly.
399 if (!empty($method)) {
400 $tmp = explode(' ', $method, 2);
401 $method = getListTitle('paymethod', $tmp[0]);
402 if (isset($tmp[1])) {
403 // If the description is not interesting then let it hold the check number
404 // or similar, otherwise append that to the payment method.
405 if ($description == '') {
406 $description = $tmp[1];
407 } else {
408 $method .= ' ' . $tmp[1];
412 echo " <tr>\n";
413 echo " <td";
414 if (!empty($billtime) && substr($billtime, 0, 4) != '0000') {
415 echo " title='" . xla('Entered') . ' ' .
416 text(oeFormatShortDate($billtime)) . attr(substr($billtime, 10)) . "'";
418 echo ">" . text(oeFormatShortDate($paydate)) . "</td>\n";
419 echo " <td colspan='2'>" . text($refno) . "</td>\n";
420 echo " <td colspan='$rcpt_num_method_columns' class='text-left'>" . text($method) . "</td>\n";
421 echo " <td colspan='$rcpt_num_ref_columns' class='text-left'>" . text($description) . "</td>\n";
422 echo " <td colspan='$rcpt_num_amount_columns' class='text-right'>" . text(oeFormatMoney($amount)) . "</td>\n";
423 echo " </tr>\n";
426 // Compute a current checksum of this encounter's invoice-related data from the database.
428 function invoiceChecksum($pid, $encounter)
430 $row1 = sqlQuery(
431 "SELECT BIT_XOR(CRC32(CONCAT_WS(',', " .
432 "id, code, modifier, units, fee, authorized, provider_id, ndc_info, justify, billed, user, bill_date" .
433 "))) AS checksum FROM billing WHERE " .
434 "pid = ? AND encounter = ? AND activity = 1",
435 array($pid, $encounter)
437 $row2 = sqlQuery(
438 "SELECT BIT_XOR(CRC32(CONCAT_WS(',', " .
439 "sale_id, inventory_id, prescription_id, quantity, fee, sale_date, billed, bill_date" .
440 "))) AS checksum FROM drug_sales WHERE " .
441 "pid = ? AND encounter = ?",
442 array($pid, $encounter)
444 $row3 = sqlQuery(
445 "SELECT BIT_XOR(CRC32(CONCAT_WS(',', " .
446 "sequence_no, code, modifier, payer_type, post_time, post_user, memo, pay_amount, adj_amount, post_date" .
447 "))) AS checksum FROM ar_activity WHERE " .
448 "pid = ? AND encounter = ?",
449 array($pid, $encounter)
451 $row4 = sqlQuery(
452 "SELECT BIT_XOR(CRC32(CONCAT_WS(',', " .
453 "id, date, reason, facility_id, provider_id, supervisor_id, invoice_refno" .
454 "))) AS checksum FROM form_encounter WHERE " .
455 "pid = ? AND encounter = ?",
456 array($pid, $encounter)
458 return (0 + $row1['checksum']) ^ (0 + $row2['checksum']) ^ (0 + $row3['checksum']) ^ (0 + $row4['checksum']);
461 //////////////////////////////////////////////////////////////////////
463 // Generate a receipt from the last-billed invoice for this patient,
464 // or for the encounter specified as a GET parameter.
466 function generate_receipt($patient_id, $encounter = 0)
468 global $details, $rapid_data_entry, $aAdjusts;
469 global $web_root, $webserver_root, $code_types;
470 global $aTaxNames, $aInvTaxes, $checkout_times, $current_checksum;
471 global $num_optional_columns, $rcpt_num_method_columns, $rcpt_num_ref_columns, $rcpt_num_amount_columns;
472 global $TAXES_AFTER_ADJUSTMENT;
473 global $facilityService, $alertmsg;
475 // Get the most recent invoice data or that for the specified encounter.
476 if ($encounter) {
477 $ferow = sqlQuery(
478 "SELECT id, date, encounter, facility_id, invoice_refno " .
479 "FROM form_encounter WHERE pid = ? AND encounter = ?",
480 array($patient_id, $encounter)
482 } else {
483 $ferow = sqlQuery(
484 "SELECT id, date, encounter, facility_id, invoice_refno " .
485 "FROM form_encounter WHERE pid = ? ORDER BY id DESC LIMIT 1",
486 array($patient_id)
489 if (empty($ferow)) {
490 die(xlt("This patient has no activity."));
492 $trans_id = $ferow['id'];
493 $encounter = $ferow['encounter'];
494 $svcdate = substr($ferow['date'], 0, 10);
495 $invoice_refno = $ferow['invoice_refno'];
497 // Generate checksum.
498 $current_checksum = invoiceChecksum($patient_id, $encounter);
500 // Get details for the visit's facility.
501 $frow = $facilityService->getById($ferow['facility_id']);
503 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
505 // Get array of checkout timestamps.
506 $checkout_times = craGetTimestamps($patient_id, $encounter);
508 // Generate $aTaxNames = array of tax names, and $aInvTaxes = array of taxes for this invoice.
509 load_taxes($patient_id, $encounter);
511 <!-- The following is from the php function generate_receipt. -->
512 <!DOCTYPE html>
513 <html>
514 <head>
515 <?php Header::setupHeader(['datetime-picker']);?>
516 <title><?php echo xlt('Client Receipt'); ?></title>
518 <script>
520 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
522 $(function () {
523 var win = top.printLogSetup ? top : opener.top;
524 win.printLogSetup(document.getElementById('printbutton'));
527 // Process click on Print button.
528 function printme(checkout_id) {
529 <?php if (!empty($GLOBALS['gbl_custom_receipt'])) { ?>
530 // Custom checkout receipt needs to be sent as a PDF in a new window or tab.
531 window.open('pos_checkout.php?ptid=' + <?php echo js_url($patient_id); ?>
532 + '&enc=' + <?php echo js_url($encounter); ?>
533 + '&pdf=1&coid=' + encodeURIComponent(checkout_id),
534 '_blank', 'width=750,height=550,resizable=1,scrollbars=1');
535 <?php } else { ?>
536 var divstyle = document.getElementById('hideonprint').style;
537 divstyle.display = 'none';
538 if (checkout_id != '*') {
539 window.print();
541 <?php } ?>
542 return false;
545 // Process click on Print button before printing.
546 function printlog_before_print() {
547 // * means do not call window.print().
548 printme('*');
551 // Process click on Delete button.
552 function deleteme() {
553 dlgopen('deleter.php?billing=' + <?php echo js_url($patient_id . "." . $encounter); ?> + '&csrf_token_form=' + <?php echo js_url(CsrfUtils::collectCsrfToken()); ?>, '_blank', 500, 450);
554 return false;
557 // Called by the deleteme.php window on a successful delete.
558 function imdeleted() {
559 window.close();
562 var voidaction = ''; // saves action argument from voidme()
564 // Submit the form to complete a void operation.
565 function voidwrap(form_reason, form_notes) {
566 top.restoreSession();
567 document.location.href = 'pos_checkout.php?ptid=' + <?php echo js_url($patient_id); ?> +
568 '&' + encodeURIComponent(voidaction) + '=' + <?php echo js_url($encounter); ?> +
569 '&form_checksum=' + <?php echo js_url($current_checksum); ?> +
570 '&form_reason=' + encodeURIComponent(form_reason) +
571 '&form_notes=' + encodeURIComponent(form_notes) +
572 '<?php if (!empty($_GET['framed'])) {
573 echo '&framed=1';} ?>';
574 return false;
577 // Process click on a void option.
578 // action can be 'regen', 'void' or 'voidall'.
579 function voidme(action) {
580 voidaction = action;
581 if (action == 'void' || action == 'voidall') {
582 if (!confirm(<?php echo xlj('This will advance the receipt number. Please print the receipt if you have not already done so.'); ?>)) {
583 return false;
585 dlgopen('void_dialog.php', '_blank', 500, 450);
586 return false;
588 // TBD: Better defaults for void reason and notes.
589 voidwrap('', '');
590 return false;
593 </script>
595 <style>
596 @media (min-width: 992px){
597 .modal-lg {
598 width: 1000px !Important;
601 </style>
602 <title><?php echo xlt('Patient Checkout'); ?></title>
603 </head>
605 <body>
606 <div class='container mt-3'>
607 <div class='row'>
608 <div class='col text-center'>
609 <table class='table' width='95%'>
610 <tr>
611 <td width='25%' align='left' valign='top'>
612 <?php
613 // TBD: Maybe make a global for this file name.
614 if ($tmp = UrlIfImageExists('ma_logo.png')) {
615 echo "<img src='$tmp' />";
616 } else {
617 echo "&nbsp;";
620 </td>
621 <td width='50%' align='center' valign='top' class='font-weight-bold'>
622 <?php echo text($frow['name']); ?>
623 <br><?php echo text($frow['street']); ?>
624 <br><?php
625 echo text($frow['city']) . ", ";
626 echo text($frow['state']) . " ";
627 echo text($frow['postal_code']); ?>
628 <br><?php echo text($frow['phone']); ?>
629 </td>
630 <td width='25%' align='right' valign='top'>
631 <!-- This space available. -->
632 &nbsp;
633 </td>
634 </tr>
635 </table>
636 <p class='font-weight-bold'>
637 <?php
638 echo xlt("Client Receipt");
639 if ($invoice_refno) {
640 echo " " . xlt("for Invoice") . text(" $invoice_refno");
643 <br />&nbsp;
644 </p>
645 <?php
646 // Compute numbers for summary on right side of page.
647 $head_begbal = get_patient_balance_excluding($patient_id, $encounter);
648 $row = sqlQuery(
649 "SELECT SUM(fee) AS amount FROM billing WHERE " .
650 "pid = ? AND encounter = ? AND activity = 1 AND " .
651 "code_type != 'COPAY'",
652 array($patient_id, $encounter)
654 $head_charges = $row['amount'];
655 $row = sqlQuery(
656 "SELECT SUM(fee) AS amount FROM drug_sales WHERE pid = ? AND encounter = ?",
657 array($patient_id, $encounter)
659 $head_charges += $row['amount'];
660 $row = sqlQuery(
661 "SELECT SUM(pay_amount) AS payments, " .
662 "SUM(adj_amount) AS adjustments FROM ar_activity WHERE " .
663 "pid = ? AND encounter = ? AND deleted IS NULL",
664 array($patient_id, $encounter)
666 $head_adjustments = $row['adjustments'];
667 $head_payments = $row['payments'];
668 $row = sqlQuery(
669 "SELECT SUM(fee) AS amount FROM billing WHERE " .
670 "pid = ? AND encounter = ? AND activity = 1 AND " .
671 "code_type = 'COPAY'",
672 array($patient_id, $encounter)
674 $head_payments -= $row['amount'];
675 $head_endbal = $head_begbal + $head_charges - $head_adjustments - $head_payments;
677 <table class='table' width='95%'>
678 <tr>
679 <td width='50%' class='text-left' valign='top'>
680 <?php echo text($patdata['fname'] . ' ' . $patdata['mname'] . ' ' . $patdata['lname']); ?>
681 <br /><?php echo text($patdata['street']); ?>
682 <br /><?php
683 echo generate_layout_display_field('DEM', 'city', $patdata['city']) . ", ";
684 echo generate_layout_display_field('DEM', 'state', $patdata['state']) . " ";
685 echo text($patdata['postal_code']); ?>
686 </td>
687 <td width='50%' class='text-right' valign='top'>
688 <table>
689 <tr>
690 <td><?php echo xlt('Beginning Account Balance'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
691 <td class='text-right'><?php echo text(oeFormatMoney($head_begbal)); ?></td>
692 </tr>
693 <tr>
694 <td><?php echo xlt('Total Visit Charges'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
695 <td class='text-right'><?php echo text(oeFormatMoney($head_charges)); ?></td>
696 </tr>
697 <tr>
698 <td><?php echo xlt('Adjustments'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
699 <td class='text-right'><?php echo text(oeFormatMoney($head_adjustments)); ?></td>
700 </tr>
701 <tr>
702 <td><?php echo xlt('Payments'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
703 <td class='text-right'><?php echo text(oeFormatMoney($head_payments)); ?></td>
704 </tr>
705 <tr>
706 <td><?php echo xlt('Ending Account Balance'); ?>&nbsp;&nbsp;&nbsp;&nbsp;</td>
707 <td class='text-right'><?php echo text(oeFormatMoney($head_endbal)); ?></td>
708 </tr>
709 </table>
710 </td>
711 </tr>
712 </table>
714 <table class='table' width='95%'>
715 <?php if ($details) { ?>
716 <tr>
717 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
718 style='padding-top:5pt;' class='font-weight-bold'>
719 <?php echo xlt('Charges for') . ' ' . text(oeFormatShortDate($svcdate)); ?>
720 </td>
721 </tr>
723 <tr>
724 <td class='font-weight-bold'><?php echo xlt('Date'); ?></td>
725 <td class='font-weight-bold'><?php echo xlt('Code'); ?></td>
726 <td class='font-weight-bold'><?php echo xlt('Description'); ?></td>
727 <td class='font-weight-bold text-center'><?php echo $details ? xlt('Qty') : '&nbsp;'; ?></td>
728 <td class='font-weight-bold text-right'><?php echo $details ? xlt('Price') : '&nbsp;'; ?></td>
729 <?php if (!empty($GLOBALS['gbl_checkout_charges'])) { ?>
730 <td class='font-weight-bold text-right'><?php echo xlt('Charge'); ?></td>
731 <?php } ?>
732 <?php
733 if (!$TAXES_AFTER_ADJUSTMENT) {
734 foreach ($aTaxNames as $taxname) {
735 echo " <td class='font-weight-bold text-right'>" . text($taxname) . "</td>\n";
739 <?php if (!empty($GLOBALS['gbl_charge_categories'])) { ?>
740 <td class='font-weight-bold text-right'><?php echo xlt('Customer'); ?></td>
741 <?php } ?>
742 <?php if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) { ?>
743 <td class='font-weight-bold text-right'><?php echo xlt('Adj Type'); ?></td>
744 <td class='font-weight-bold text-right'><?php echo xlt('Adj Amt'); ?></td>
745 <?php } ?>
746 <?php
747 if ($TAXES_AFTER_ADJUSTMENT) {
748 foreach ($aTaxNames as $taxname) {
749 echo " <td class='font-weight-bold text-right'>" . text($taxname) . "</td>\n";
753 <td class='font-weight-bold text-right'><?php echo xlt('Total'); ?></td>
754 </tr>
756 <?php } // end if details ?>
758 <tr>
759 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
760 style='border-top:1px solid black; font-size:1px; padding:0;'>
761 &nbsp;
762 </td>
763 </tr>
764 <?php
765 // Create array aAdjusts from ar_activity rows for $encounter.
766 load_adjustments($patient_id, $encounter);
768 $aTotals = array(0, 0, 0, 0, 0);
769 for ($i = 0; $i < count($aTaxNames); ++$i) {
770 $aTotals[5 + $i] = 0;
773 // Product sales
774 $inres = sqlStatement(
775 "SELECT s.sale_id, s.sale_date, s.fee, " .
776 "s.quantity, s.drug_id, s.billed, s.bill_date, s.selector, d.name, lo.title " .
777 "FROM drug_sales AS s " .
778 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
779 "LEFT JOIN list_options AS lo ON lo.list_id = 'chargecats' and lo.option_id = s.chargecat AND lo.activity = 1 " .
780 "WHERE s.pid = ? AND s.encounter = ? " .
781 "ORDER BY s.sale_id",
782 array($patient_id, $encounter)
784 while ($inrow = sqlFetchArray($inres)) {
785 $billtime = $inrow['billed'] ? $inrow['bill_date'] : '';
786 $tmpname = $inrow['name'];
787 if ($tmpname !== $inrow['selector']) {
788 $tmpname .= ' / ' . $inrow['selector'];
790 $units = $inrow['quantity'] / FeeSheet::getBasicUnits($inrow['drug_id'], $inrow['selector']);
791 receiptDetailLine(
792 'PROD',
793 $inrow['drug_id'],
794 $tmpname,
795 $units,
796 $inrow['fee'],
797 $aTotals,
798 'P:' . $inrow['sale_id'],
799 $billtime,
800 $svcdate,
801 $inrow['title']
805 // Service items.
806 $inres = sqlStatement(
807 "SELECT * FROM billing AS b " .
808 "LEFT JOIN list_options AS lo ON lo.list_id = 'chargecats' and lo.option_id = b.chargecat AND lo.activity = 1 " .
809 "WHERE b.pid = ? AND b.encounter = ? AND " .
810 "b.code_type != 'COPAY' AND b.code_type != 'TAX' AND b.activity = 1 " .
811 "ORDER BY b.id",
812 array($patient_id, $encounter)
814 while ($inrow = sqlFetchArray($inres)) {
815 // Write the line item if it allows fees or is not a diagnosis.
816 if (!empty($code_types[$inrow['code_type']]['fee']) || empty($code_types[$inrow['code_type']]['diag'])) {
817 $billtime = $inrow['billed'] ? $inrow['bill_date'] : '';
818 receiptDetailLine(
819 $inrow['code_type'],
820 $inrow['code'],
821 $inrow['code_text'],
822 $inrow['units'],
823 $inrow['fee'],
824 $aTotals,
825 'S:' . $inrow['id'],
826 $billtime,
827 $svcdate,
828 $inrow['title']
833 // Write any adjustments left in the aAdjusts array.
834 foreach ($aAdjusts as $arow) {
835 if ($arow['adj_amount'] == 0 && $arow['memotitle'] == '') {
836 continue;
838 $payer = empty($arow['payer_type']) ? 'Pt' : ('Ins' . $arow['payer_type']);
839 receiptDetailLine(
841 "$payer|" . $arow['code_type'] . "|" . $arow['code'],
842 $arow['memotitle'],
844 0 - $arow['adj_amount'],
845 $aTotals,
847 $arow['post_time'],
848 $arow['post_date']
852 <tr>
853 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
854 style='border-top:1px solid black; font-size:1px; padding:0;'>
855 &nbsp;
856 </td>
857 </tr>
859 <?php
860 // Sub-Total line with totals of all numeric columns.
861 if ($details) {
862 echo " <tr>\n";
863 echo " <td colspan='3' align='right'><b>" . xlt('Sub-Total') . "</b></td>\n";
864 echo " <td align='center'>" . text($aTotals[0]) . "</td>\n";
865 echo " <td align='right'>" . text(oeFormatMoney($aTotals[1])) . "</td>\n";
866 // Optional charge amount.
867 if (!empty($GLOBALS['gbl_checkout_charges'])) {
868 echo " <td align='right'>" . text(oeFormatMoney($aTotals[2])) . "</td>\n";
870 if (!$TAXES_AFTER_ADJUSTMENT) {
871 // Put tax columns, if any, in the subtotals.
872 for ($i = 0; $i < count($aTaxNames); ++$i) {
873 echo " <td align='right'>" . text(oeFormatMoney($aTotals[5 + $i])) . "</td>\n";
876 // Optional charge category empty column.
877 if (!empty($GLOBALS['gbl_charge_categories'])) {
878 echo " <td align='right'>&nbsp;</td>\n";
880 // Optional adjustment columns.
881 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
882 echo " <td align='right'>&nbsp;</td>\n";
883 echo " <td align='right'>" . text(oeFormatMoney($aTotals[3])) . "</td>\n";
885 if ($TAXES_AFTER_ADJUSTMENT) {
886 // Put tax columns, if any, in the subtotals.
887 for ($i = 0; $i < count($aTaxNames); ++$i) {
888 echo " <td align='right'>" . text(oeFormatMoney($aTotals[5 + $i])) . "</td>\n";
891 echo " <td align='right'>" . text(oeFormatMoney($aTotals[4])) . "</td>\n";
892 echo " </tr>\n";
895 // Write a line for each tax item that did not match.
896 // Should only happen for old invoices before taxes were assigned to line items.
897 foreach ($aInvTaxes as $taxid => $taxarr) {
898 foreach ($taxarr as $taxlineid => $tax) {
899 if ($tax) {
900 receiptDetailLine('TAX', $taxid, $aTaxNames[$taxid], 1, $tax, $aTotals);
901 $aInvTaxes[$taxid][$taxlineid] = 0;
906 // Total Charges line.
907 echo " <tr>\n";
908 echo " <td colspan='" . (3 + $num_optional_columns) . "'>&nbsp;</td>\n";
909 echo " <td colspan='" . 2 .
910 "' align='right'><b>" . xlt('Total Charges') . "</b></td>\n";
911 echo " <td align='right'>" . text(oeFormatMoney($aTotals[4])) . "</td>\n";
912 echo " </tr>\n";
915 <tr>
916 <td colspan='<?php echo 6 + $num_optional_columns; ?>' style='padding-top:5pt;'>
917 <b><?php echo xlt('Payments'); ?></b>
918 </td>
919 </tr>
921 <tr>
922 <td><b><?php echo xlt('Date'); ?></b></td>
923 <td colspan='2'><b><?php echo xlt('Checkout Receipt Ref'); ?></b></td>
924 <td colspan="<?php echo text($rcpt_num_method_columns); ?>"
925 align='left'><b><?php echo xlt('Payment Method'); ?></b></td>
926 <td colspan="<?php echo text($rcpt_num_ref_columns); ?>"
927 align='left'><b><?php echo xlt('Ref No'); ?></b></td>
928 <td colspan='<?php echo text($rcpt_num_amount_columns); ?>'
929 align='right'><b><?php echo xlt('Amount'); ?></b></td>
930 </tr>
932 <tr>
933 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
934 style='border-top:1px solid black; font-size:1px; padding:0;'>
935 &nbsp;
936 </td>
937 </tr>
939 <?php
940 $payments = 0;
942 // Get co-pays.
943 $inres = sqlStatement(
944 "SELECT fee, code_text FROM billing WHERE " .
945 "pid = ? AND encounter = ? AND " .
946 "code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
947 "ORDER BY id",
948 array($patient_id, $encounter)
950 while ($inrow = sqlFetchArray($inres)) {
951 $payments -= formatMoneyNumber($inrow['fee']);
952 receiptPaymentLine($svcdate, 0 - $inrow['fee'], $inrow['code_text'], 'COPAY');
955 // Get other payments.
956 $inres = sqlStatement(
957 "SELECT " .
958 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
959 "a.post_time, IFNULL(a.post_date, a.post_time) AS post_date, " .
960 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
961 "FROM ar_activity AS a " .
962 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
963 "a.pid = ? AND a.encounter = ? AND a.deleted IS NULL AND " .
964 "a.pay_amount != 0 " .
965 "ORDER BY s.check_date, a.sequence_no",
966 array($patient_id, $encounter)
968 $payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
969 while ($inrow = sqlFetchArray($inres)) {
970 $payments += formatMoneyNumber($inrow['pay_amount']);
971 // Compute invoice number with payment suffix.
972 $tmp = array_search($inrow['post_time'], $checkout_times);
973 $tmp = $tmp === false ? 0 : ($tmp + 1);
974 $refno = $invoice_refno ? "$invoice_refno-$tmp" : "$encounter-$tmp";
975 receiptPaymentLine(
976 $inrow['post_date'],
977 $inrow['pay_amount'],
978 trim($payer . ' ' . $inrow['reference']),
979 $inrow['memo'],
980 $refno,
981 $inrow['post_time']
986 <tr>
987 <td colspan='<?php echo 6 + $num_optional_columns; ?>'
988 style='border-top:1px solid black; font-size:1px; padding:0;'>
989 &nbsp;
990 </td>
991 </tr>
993 <tr>
994 <td colspan='<?php echo 3 + $num_optional_columns; ?>'>&nbsp;</td>
995 <td colspan='2' align='right'><b><?php echo xlt('Total Payments'); ?></b></td>
996 <td align='right'><?php echo str_replace(' ', '&nbsp;', text(oeFormatMoney($payments, true))); ?></td>
997 </tr>
999 </table>
1000 </div>
1001 </div>
1003 <div class='row'>
1004 <div class='col'>
1006 <?php
1007 // The user-customizable note.
1008 if (!empty($GLOBALS['gbl_checkout_receipt_note'])) {
1009 echo "<p>";
1010 echo str_repeat('*', 80) . '<br />';
1011 echo '&nbsp;&nbsp;' . text($GLOBALS['gbl_checkout_receipt_note']) . '<br />';
1012 echo str_repeat('*', 80) . '<br />';
1013 echo "</p>";
1018 <b><?php echo xlt("Printed on") . ' ' . text(dateformat()); ?></b>
1019 </p>
1021 <div id='hideonprint'>
1023 &nbsp;
1025 <?php
1026 if (count($checkout_times) > 1 && !empty($GLOBALS['gbl_custom_receipt'])) {
1027 // Multiple checkouts so allow selection of the one to print.
1028 // This is only applicable for custom checkout receipts.
1029 echo "<select onchange='printme(this.value)' >\n";
1030 echo " <option value=''>" . xlt('Print Checkout') . "</option>\n";
1031 $i = 0;
1032 foreach ($checkout_times as $tmp) {
1033 ++$i;
1034 echo " <option value='" . attr($tmp) . "'>" . text("$i: $tmp") . "</option>\n";
1036 echo "</select>\n";
1037 } else {
1038 echo "<a href='#' onclick='return printme(\"\");'>" . xlt('Print') . "</a>\n";
1042 <?php if (AclMain::aclCheckCore('acct', 'disc')) { ?>
1043 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1044 <a href='#' onclick='return voidme("regen");'><?php echo xlt('Generate New Receipt Number'); ?></a>
1045 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1046 <a href='#' onclick='return voidme("void");' title='<?php echo xla('Applies to this visit only'); ?>'>
1047 <?php echo xlt('Void Last Checkout'); ?></a>
1048 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1049 <a href='#' onclick='return voidme("voidall");' title='<?php echo xla('Applies to this visit only'); ?>'>
1050 <?php echo xlt('Void All Checkouts'); ?></a>
1051 <?php } ?>
1053 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1055 <?php if ($details) { ?>
1056 <a href='pos_checkout.php?details=0&ptid=<?php echo attr_url($patient_id); ?>&enc=<?php echo attr_url($encounter); ?>'
1057 onclick='top.restoreSession()'><?php echo xlt('Hide Details'); ?></a>
1058 <?php } else { ?>
1059 <a href='pos_checkout.php?details=1&ptid=<?php echo attr_url($patient_id); ?>&enc=<?php echo attr_url($encounter); ?>'
1060 onclick='top.restoreSession()'><?php echo xlt('Show Details'); ?></a>
1061 <?php } ?>
1062 </p>
1063 </div><!-- end hideonprint -->
1064 </div><!-- end col -->
1065 </div><!-- end row -->
1066 </div><!-- end container -->
1067 <script>
1068 <?php
1069 if ($alertmsg) {
1070 echo " alert(" . js_escape($alertmsg) . ");\n";
1073 </script>
1074 </body>
1075 </html>
1076 <?php
1079 // end function generate_receipt()
1081 //////////////////////////////////////////////////////////////////////
1083 // Function to write the heading lines for the data entry form.
1084 // This is deferred because we need to know which encounter was chosen.
1086 $form_headers_written = false;
1087 function write_form_headers()
1089 global $form_headers_written, $patdata, $patient_id, $encounter_id, $aAdjusts;
1090 global $taxes, $encounter_date, $num_optional_columns, $TAXES_AFTER_ADJUSTMENT;
1092 if ($form_headers_written) {
1093 return;
1095 $form_headers_written = true;
1097 // Create arrays $aAdjusts, $aTaxNames and $aInvTaxes for this encounter.
1098 load_adjustments($patient_id, $encounter_id);
1099 // This also initializes $num_optional_columns and related colspan values.
1100 load_taxes($patient_id, $encounter_id);
1102 $ferow = sqlQuery(
1103 "SELECT date FROM form_encounter WHERE pid = ? AND encounter = ?",
1104 array($patient_id, $encounter_id)
1106 $encounter_date = substr($ferow['date'], 0, 10);
1108 <tr>
1109 <td colspan='<?php echo 5 + $num_optional_columns; ?>' align='center' class='title'>
1110 <?php echo xlt('Patient Checkout for '); ?><?php echo text($patdata['fname']) . " " .
1111 text($patdata['lname']) . " (" . text($patdata['pubpid']) . ")" ?>
1112 <br />&nbsp;
1113 <p class='bold'>
1114 <?php
1115 $prvbal = get_patient_balance_excluding($patient_id, $encounter_id);
1116 echo xlt('Previous Balance') . '&nbsp;&nbsp;&nbsp;&nbsp;';
1117 echo "<input type='text' value='" . attr(oeFormatMoney($prvbal)) . "' size='6' ";
1118 echo "style='text-align:right;background-color:transparent' readonly />\n";
1119 if ($prvbal > 0) {
1120 echo "&nbsp;<input type='button' value='" . xla('Pay Previous Balance') .
1121 "' onclick='payprevious()' />\n";
1124 <br />&nbsp;
1125 </p>
1126 </td>
1127 </tr>
1129 <tr>
1130 <?php if (!$TAXES_AFTER_ADJUSTMENT) { ?>
1131 <td colspan='<?php echo 4 + (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1) + count($taxes); ?>' class='bold'>
1132 <?php } else { ?>
1133 <td colspan='<?php echo 4 + (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1); ?>' class='bold'>
1134 <?php } ?>
1135 &nbsp;
1136 </td>
1137 <?php if (!empty($GLOBALS['gbl_charge_categories'])) { ?>
1138 <td align='right' class='bold' nowrap>
1139 <?php echo xlt('Default Customer'); ?>
1140 </td>
1141 <?php } ?>
1142 <?php if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) { ?>
1143 <td align='right' class='bold' nowrap>
1144 <?php echo xlt('Default Adjust Type'); ?>
1145 </td>
1146 <td class='bold'>
1147 &nbsp;
1148 </td>
1149 <?php } ?>
1150 <?php if (!$TAXES_AFTER_ADJUSTMENT) { ?>
1151 <td class='bold'>
1152 <?php } else { ?>
1153 <td colspan='<?php echo 1 + count($taxes); ?>' class='bold'>
1154 <?php } ?>
1155 &nbsp;
1156 </td>
1157 </tr>
1159 <tr>
1160 <?php if (!$TAXES_AFTER_ADJUSTMENT) { ?>
1161 <td colspan='<?php echo 4 + (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1) + count($taxes); ?>' class='title'>
1162 <?php } else { ?>
1163 <td colspan='<?php echo 4 + (empty($GLOBALS['gbl_checkout_charges']) ? 0 : 1); ?>' class='title'>
1164 <?php } ?>
1165 <?php echo xlt('Current Charges'); ?>
1166 </td>
1167 <?php if (!empty($GLOBALS['gbl_charge_categories'])) { // charge category default ?>
1168 <td align='right' class='bold'>
1169 <?php echo generate_select_list('form_charge_category', 'chargecats', '', '', ' ', '', 'chargeCategoryChanged();'); ?>
1170 </td>
1171 <?php } ?>
1172 <?php if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) { // adjustmenty reason default ?>
1173 <td align='right' class='bold'>
1174 <?php echo generate_select_list('form_discount_type', 'adjreason', '', '', ' ', '', 'discountTypeChanged();billingChanged();'); ?>
1175 </td>
1176 <td class='bold'>
1177 &nbsp;
1178 </td>
1179 <?php } ?>
1180 <?php if (!$TAXES_AFTER_ADJUSTMENT) { ?>
1181 <td class='bold'>
1182 <?php } else { ?>
1183 <td colspan='<?php echo 1 + count($taxes); ?>' class='bold'>
1184 <?php } ?>
1185 &nbsp;
1186 </td>
1187 </tr>
1189 <tr>
1190 <td class='bold'><?php echo xlt('Date'); ?></td>
1191 <td class='bold'><?php echo xlt('Description'); ?></td>
1192 <td align='right' class='bold'><?php echo xlt('Quantity'); ?></td>
1193 <?php if (empty($GLOBALS['gbl_checkout_charges'])) { // if no charges column ?>
1194 <td align='right' class='bold'><?php echo xlt('Charge'); ?></td>
1195 <?php } else { // charges column needed ?>
1196 <td align='right' class='bold'><?php echo xlt('Price'); ?></td>
1197 <td align='right' class='bold'><?php echo xlt('Charge'); ?></td>
1198 <?php } ?>
1199 <?php
1200 if (!$TAXES_AFTER_ADJUSTMENT) {
1201 foreach ($taxes as $taxarr) {
1202 echo " <td align='right' class='bold'>" . text($taxarr[0]) . "</td>";
1206 <?php if (!empty($GLOBALS['gbl_charge_categories'])) { // charge category ?>
1207 <td align='right' class='bold'><?php echo xlt('Customer'); ?></td>
1208 <?php } ?>
1209 <?php if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) { ?>
1210 <td align='right' class='bold'><?php echo xlt('Adjust Type'); ?></td>
1211 <td align='right' class='bold'><?php echo xlt('Adj'); ?></td>
1212 <?php } ?>
1213 <?php
1214 if ($TAXES_AFTER_ADJUSTMENT) {
1215 foreach ($taxes as $taxarr) {
1216 echo " <td align='right' class='bold'>" . text($taxarr[0]) . "</td>";
1220 <td align='right' class='bold'><?php echo xlt('Total'); ?></td>
1221 </tr>
1222 <?php
1225 // Function to output a line item for the input form.
1227 $totalchg = 0; // totals charges after adjustments
1228 function write_form_line(
1229 $code_type,
1230 $code,
1231 $id,
1232 $date,
1233 $description,
1234 $amount,
1235 $units,
1236 $taxrates,
1237 $billtime = '',
1238 $chargecat = ''
1240 global $lino, $totalchg, $aAdjusts, $taxes, $encounter_date, $TAXES_AFTER_ADJUSTMENT;
1242 // Write heading rows if that is not already done.
1243 write_form_headers();
1244 $amount = formatMoneyNumber($amount);
1245 if (empty($units)) {
1246 $units = 1;
1248 $price = formatMoneyNumber($amount / $units, 2); // should be even cents, but...
1249 if (substr($price, -2) === '00') {
1250 $price = formatMoneyNumber($price);
1253 // Total and clear adjustments in aAdjusts matching this line item. Should only
1254 // happen for billed items, and matching includes the billing timestamp in order
1255 // to handle the case of multiple checkouts.
1256 $memo = '';
1257 $adjust = pull_adjustment($code_type, $code, $billtime, $memo);
1258 $total = formatMoneyNumber($amount - $adjust);
1259 if (empty($GLOBALS['discount_by_money'])) {
1260 // Convert $adjust to a percentage of the amount, up to 4 decimal places.
1261 $adjust = round(100 * $adjust / $amount, 4);
1264 // Compute the string of numeric tax rates to store with the charge line.
1265 $taxnumrates = '';
1266 $arates = explode(':', $taxrates);
1267 foreach ($taxes as $taxid => $taxarr) {
1268 $rate = $taxarr[1];
1269 if (empty($arates) || !in_array($taxid, $arates)) {
1270 $rate = 0;
1272 $taxnumrates .= $rate . ':';
1275 echo " <tr>\n";
1276 echo " <td class='text'>" . text(oeFormatShortDate($encounter_date));
1277 echo "<input type='hidden' name='line[$lino][code_type]' value='" . attr($code_type) . "'>";
1278 echo "<input type='hidden' name='line[$lino][code]' value='" . attr($code) . "'>";
1279 echo "<input type='hidden' name='line[$lino][id]' value='" . attr($id) . "'>";
1280 echo "<input type='hidden' name='line[$lino][description]' value='" . attr($description) . "'>";
1281 // String of numeric tax rates is written here as a form field only for JavaScript tax computations.
1282 echo "<input type='hidden' name='line[$lino][taxnumrates]' value='" . attr($taxnumrates) . "'>";
1283 echo "<input type='hidden' name='line[$lino][units]' value='" . attr($units) . "'>";
1284 // Indicator of whether and when this line item was previously billed:
1285 echo "<input type='hidden' name='line[$lino][billtime]' value='" . attr($billtime) . "'>";
1286 echo "</td>\n";
1287 echo " <td class='text'>" . text($description) . "</td>";
1288 echo " <td class='text' align='right'>" . text($units) . "</td>";
1290 if (empty($GLOBALS['gbl_checkout_charges'])) {
1291 // We show only total charges here.
1292 echo " <td class='text' align='right'>";
1293 echo "<input type='hidden' name='line[$lino][price]' value='" . attr($price) . "'>";
1294 echo "<input type='text' name='line[$lino][charge]' value='" . attr($amount) . "' size='6'";
1295 echo " style='text-align:right;background-color:transparent' readonly />";
1296 echo "</td>\n";
1297 } else {
1298 // In this case show price and extended charge amount.
1299 echo " <td class='text' align='right'>";
1300 echo "<input type='text' name='line[$lino][price]' value='" . attr($price) . "' size='6'";
1301 echo " style='text-align:right;background-color:transparent' readonly />";
1302 echo "</td>\n";
1303 echo " <td class='text' align='right'>";
1304 echo "<input type='text' name='line[$lino][charge]' value='" . attr($amount) . "' size='6'";
1305 echo " style='text-align:right;background-color:transparent' readonly />";
1306 echo "</td>\n";
1309 // Match up (and delete) entries in $aInvTaxes with the line.
1310 $lineid = $code_type == 'PROD' ? "P:$id" : "S:$id";
1311 $aTaxes = array();
1312 pull_tax($lineid, $aTaxes); // fills in $aTaxes
1314 if (!$TAXES_AFTER_ADJUSTMENT) {
1315 // A tax column for each tax. JavaScript will compute the amounts and
1316 // account for how the discount affects them.
1317 $i = 0;
1318 foreach ($taxes as $taxid => $dummy) {
1319 echo " <td class='text' align='right'>";
1320 echo "<input type='text' name='line[$lino][tax][$i]' size='6'";
1321 // Set tax amounts for existing billed items. JS must not recompute those.
1322 echo " value='" . attr(formatMoneyNumber($aTaxes[$taxid])) . "'";
1323 echo " style='text-align:right;background-color:transparent' readonly />";
1324 echo "</td>\n";
1325 ++$i;
1329 // Optional Charge Category.
1330 if (!empty($GLOBALS['gbl_charge_categories'])) {
1331 echo " <td class='text' align='right'>";
1332 echo generate_select_list(
1333 "line[$lino][chargecat]",
1334 'chargecats',
1335 $chargecat,
1337 ' ',
1341 $billtime ? array('disabled' => 'disabled') : null
1343 echo "</td>\n";
1346 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
1347 echo " <td class='text' align='right'>";
1348 echo generate_select_list(
1349 "line[$lino][memo]",
1350 'adjreason',
1351 $memo,
1353 ' ',
1355 'billingChanged()',
1357 $billtime ? array('disabled' => 'disabled') : null
1359 echo "</td>\n";
1360 echo " <td class='text' align='right' nowrap>";
1361 echo empty($GLOBALS['discount_by_money']) ? '' : text($GLOBALS['gbl_currency_symbol']);
1362 echo "<input type='text' name='line[$lino][adjust]' size='6'";
1363 echo " value='" . attr(formatMoneyNumber($adjust)) . "'";
1364 // Modifying discount requires the acct/disc permission.
1365 if ($billtime || $code_type == 'TAX' || $code_type == 'COPAY' || !AclMain::aclCheckCore('acct', 'disc')) {
1366 echo " style='text-align:right;background-color:transparent' readonly";
1367 } else {
1368 echo " style='text-align:right' maxlength='8' onkeyup='lineDiscountChanged($lino)'";
1370 echo " /> ";
1371 echo empty($GLOBALS['discount_by_money']) ? '%' : '';
1372 echo "</td>\n";
1375 if ($TAXES_AFTER_ADJUSTMENT) {
1376 // A tax column for each tax. JavaScript will compute the amounts and
1377 // account for how the discount affects them.
1378 $i = 0;
1379 foreach ($taxes as $taxid => $dummy) {
1380 echo " <td class='text' align='right'>";
1381 echo "<input type='text' name='line[$lino][tax][$i]' size='6'";
1382 // Set tax amounts for existing billed items. JS must not recompute those.
1383 echo " value='" . attr(formatMoneyNumber($aTaxes[$taxid])) . "'";
1384 echo " style='text-align:right;background-color:transparent' readonly />";
1385 echo "</td>\n";
1386 ++$i;
1390 // Extended amount after adjustments and taxes.
1391 echo " <td class='text' align='right'>";
1392 echo "<input type='text' name='line[$lino][amount]' value='" . attr($total) . "' size='6'";
1393 echo " style='text-align:right;background-color:transparent' readonly />";
1394 echo "</td>\n";
1396 echo " </tr>\n";
1397 ++$lino;
1398 $totalchg += $amount;
1401 // Function to output a past payment/adjustment line to the form.
1403 function write_old_payment_line($pay_type, $date, $method, $reference, $amount)
1405 global $lino, $taxes, $num_optional_columns;
1406 global $form_num_type_columns, $form_num_method_columns, $form_num_ref_columns, $form_num_amount_columns;
1407 // Write heading rows if that is not already done.
1408 write_form_headers();
1409 $amount = formatMoneyNumber($amount);
1410 echo " <tr>\n";
1411 echo " <td class='text' colspan='$form_num_type_columns'>" . text($pay_type) . "</td>\n";
1412 echo " <td class='text' colspan='$form_num_method_columns'>" . text($method) . "</td>\n";
1413 echo " <td class='text' colspan='$form_num_ref_columns'>" . text($reference) . "</td>\n";
1414 echo " <td class='text' align='right' colspan='$form_num_amount_columns'><input type='text' name='oldpay[$lino][amount]' " .
1415 "value='$amount' size='6' maxlength='8'";
1416 echo " style='text-align:right;background-color:transparent' readonly";
1417 echo "></td>\n";
1418 echo " </tr>\n";
1419 ++$lino;
1422 // Mark the tax rates that are referenced in this invoice.
1423 function markTaxes($taxrates)
1425 global $taxes;
1426 $arates = explode(':', $taxrates);
1427 if (empty($arates)) {
1428 return;
1430 foreach ($arates as $value) {
1431 if (!empty($taxes[$value])) {
1432 $taxes[$value][2] = '1';
1437 // Create the taxes array. Key is tax id, value is
1438 // (description, rate, indicator). Indicator seems to be unused.
1439 $taxes = array();
1440 $pres = sqlStatement(
1441 "SELECT option_id, title, option_value " .
1442 "FROM list_options WHERE list_id = 'taxrate' AND activity = 1 ORDER BY seq, title, option_id"
1444 while ($prow = sqlFetchArray($pres)) {
1445 $taxes[$prow['option_id']] = array($prow['title'], $prow['option_value'], 0);
1448 // Array of HTML for the 4 or 5 cells of an input payment row.
1449 // "%d" will be replaced by a payment line number on the client side.
1451 $aCellHTML = array();
1452 $aCellHTML[] = "<span id='paytitle_%d'>" . text(xl('New Payment')) . "</span>";
1453 $aCellHTML[] = strtr(generate_select_list('payment[%d][method]', 'paymethod', '', '', ''), array("\n" => ""));
1454 $aCellHTML[] = "<input type='text' name='payment[%d][refno]' size='10' />";
1455 $aCellHTML[] = "<input type='text' name='payment[%d][amount]' size='6' style='text-align:right' onkeyup='setComputedValues()' />";
1457 $alertmsg = ''; // anything here pops up in an alert box
1459 // Make sure we have the encounter ID applicable to this request.
1460 if (!empty($_POST['form_save'])) {
1461 $patient_id = 0 + $_POST['form_pid'];
1462 $encounter_id = 0 + $_POST['form_encounter'];
1463 } else {
1464 foreach (array('regen', 'enc', 'void', 'voidall') as $key) {
1465 if (!empty($_GET[$key])) {
1466 $encounter_id = 0 + $_GET[$key];
1467 break;
1472 // Compute and validate the checksum.
1473 $current_checksum = 0;
1474 if ($patient_id && $encounter_id) {
1475 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1476 if (!empty($_REQUEST['form_checksum'])) {
1477 if ($_REQUEST['form_checksum'] != $current_checksum) {
1478 $alertmsg = xl('Someone else has just changed this visit. Please cancel this page and try again.');
1483 // If the Save button was clicked...
1485 if (!empty($_POST['form_save']) && !$alertmsg) {
1486 if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) {
1487 CsrfUtils::csrfNotVerified();
1490 // On a save, do the following:
1491 // Flag this form's drug_sales and billing items as billed.
1492 // Post line-level adjustments, replacing any existing ones for the same charges.
1493 // Post any invoice-level adjustment.
1494 // Post payments and be careful to use a unique invoice number.
1495 // Call the generate-receipt function.
1496 // Exit.
1498 // A current invoice reference number may be present if there was a previous checkout.
1499 $tmprow = sqlQuery(
1500 "SELECT invoice_refno FROM form_encounter WHERE " .
1501 "pid = ? AND encounter = ?",
1502 array($patient_id, $encounter_id)
1504 $current_irnumber = $tmprow['invoice_refno'];
1506 // Get the posting date from the form as yyyy-mm-dd.
1507 $postdate = substr($this_bill_date, 0, 10);
1508 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $_POST['form_date'], $matches)) {
1509 $postdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
1511 $dosdate = $postdate; // not sure if this is appropriate
1513 if (! $encounter_id) {
1514 die("Internal error: Encounter ID is missing!");
1517 // Delete unbilled TAX rows from billing because they will be recalculated.
1518 // Do not delete already-billed taxes; we must not touch billed stuff.
1519 sqlStatement(
1520 "UPDATE billing SET activity = 0 WHERE " .
1521 "pid = ? AND encounter = ? AND " .
1522 "code_type = 'TAX' AND billed = 0 AND activity = 1",
1523 array($patient_id, $encounter_id)
1526 $form_amount = $_POST['form_totalpay'];
1527 $lines = $_POST['line'];
1529 for ($lino = 0; !empty($lines[$lino]['code_type']); ++$lino) {
1530 $line = $lines[$lino];
1531 $code_type = $line['code_type'];
1532 $code = $line['code'];
1533 $id = $line['id'];
1534 $chargecat = $line['chargecat'] ?? '';
1535 $amount = formatMoneyNumber(trim($line['amount']));
1536 $linetax = 0;
1538 // Skip saving taxes and adjustments for billed items.
1539 if (!empty($line['billtime'])) {
1540 continue;
1543 // Insert any taxes for this line.
1544 // There's a chance of input data and the $taxes array being out of sync if someone
1545 // updates the taxrate list during data entry... we oughta do something about that.
1546 if (is_array($line['tax'])) {
1547 // For tax rows the ndc_info field is used to identify the charge item that is taxed.
1548 // P indicates drug_sales.sale_id, S indicates billing.id.
1549 $ndc_info = $code_type == 'PROD' ? "P:$id" : "S:$id";
1550 $i = 0;
1551 foreach ($taxes as $taxid => $taxarr) {
1552 $taxamount = $line['tax'][$i++] + 0;
1553 if ($taxamount != 0) {
1554 BillingUtilities::addBilling(
1555 $encounter_id,
1556 'TAX',
1557 $taxid,
1558 $taxarr[0],
1559 $patient_id,
1564 $taxamount,
1565 $ndc_info,
1569 // billed=0 because we will set billed and bill_date for unbilled items below.
1570 $linetax += $taxamount;
1575 // If there is an adjustment for this line, insert it.
1576 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
1577 $adjust = 0.00 + trim($line['adjust']);
1578 $memo = formDataCore($line['memo']);
1579 if ($adjust != 0 || $memo !== '') {
1580 // $memo = xl('Discount');
1581 if ($memo === '') {
1582 $memo = formData('form_discount_type');
1584 sqlBeginTrans();
1585 $sequence_no = sqlQuery(
1586 "SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE " .
1587 "pid = ? AND encounter = ?",
1588 array($patient_id, $encounter_id)
1590 $query = "INSERT INTO ar_activity ( " .
1591 "pid, encounter, sequence_no, code_type, code, modifier, payer_type, " .
1592 "post_user, post_time, post_date, session_id, memo, adj_amount " .
1593 ") VALUES ( " .
1594 "?, ?, ?, ?, ?, '', '0', ?, ?, ?, '0', ?, ? " .
1595 ")";
1596 sqlStatement($query, array(
1597 $patient_id,
1598 $encounter_id,
1599 $sequence_no['increment'],
1600 $code_type,
1601 $code,
1602 $_SESSION['authUserID'],
1603 $this_bill_date,
1604 $postdate,
1605 $memo,
1606 $adjust
1608 sqlCommitTrans();
1612 if (!empty($GLOBALS['gbl_charge_categories'])) {
1613 // Update charge category for this line item.
1614 if ($code_type == 'PROD') {
1615 $query = "UPDATE drug_sales SET chargecat = ? WHERE sale_id = ?";
1616 sqlQuery($query, array($chargecat, $id));
1617 } else {
1618 $query = "UPDATE billing SET chargecat = ? WHERE id = ?";
1619 sqlQuery($query, array($chargecat, $id));
1624 // Flag the encounter as billed.
1625 $query = "UPDATE billing SET billed = 1, bill_date = ? WHERE " .
1626 "pid = ? AND encounter = ? AND activity = 1 AND billed = 0";
1627 sqlQuery($query, array($this_bill_date, $patient_id, $encounter_id));
1628 $query = "update drug_sales SET billed = 1, bill_date = ? WHERE " .
1629 "pid = ? AND encounter = ? AND billed = 0";
1630 sqlQuery($query, array($this_bill_date, $patient_id, $encounter_id));
1632 // Post discount.
1633 if ($_POST['form_discount'] != 0) {
1634 if ($GLOBALS['discount_by_money']) {
1635 $amount = formatMoneyNumber(trim($_POST['form_discount']));
1636 } else {
1637 $amount = formatMoneyNumber(trim($_POST['form_discount']) * $form_amount / 100);
1639 $memo = formData('form_discount_type');
1640 sqlBeginTrans();
1641 $sequence_no = sqlQuery(
1642 "SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE " .
1643 "pid = ? AND encounter = ?",
1644 array($patient_id, $encounter_id)
1646 $query = "INSERT INTO ar_activity ( " .
1647 "pid, encounter, sequence_no, code, modifier, payer_type, post_user, post_time, " .
1648 "post_date, session_id, memo, adj_amount " .
1649 ") VALUES ( " .
1650 "?, ?, ?, '', '', '0', ?, ?, ?, '0', ?, ? " .
1651 ")";
1652 sqlStatement($query, array(
1653 $patient_id,
1654 $encounter_id,
1655 $sequence_no['increment'],
1656 $_SESSION['authUserID'],
1657 $this_bill_date,
1658 $postdate,
1659 $memo,
1660 $amount
1662 sqlCommitTrans();
1665 // Post the payments.
1666 if (is_array($_POST['payment'])) {
1667 $lines = $_POST['payment'];
1668 for ($lino = 0; isset($lines[$lino]['amount']); ++$lino) {
1669 $line = $lines[$lino];
1670 $amount = formatMoneyNumber(trim($line['amount']));
1671 if ($amount != 0.00) {
1672 $method = $line['method'];
1673 $refno = $line['refno'];
1674 if ($method !== '' && $refno !== '') {
1675 $method .= " $refno";
1677 $session_id = 0; // Is this OK?
1678 SLEOB::arPostPayment(
1679 $patient_id,
1680 $encounter_id,
1681 $session_id,
1682 $amount,
1685 $method,
1687 $this_bill_date,
1689 $postdate
1695 // If applicable, set the invoice reference number.
1696 if (!$current_irnumber) {
1697 $invoice_refno = '';
1698 if (isset($_POST['form_irnumber'])) {
1699 $invoice_refno = formData('form_irnumber', 'P', true);
1700 } else {
1701 $invoice_refno = add_escape_custom(BillingUtilities::updateInvoiceRefNumber());
1703 if ($invoice_refno) {
1704 sqlStatement(
1705 "UPDATE form_encounter SET invoice_refno = ? WHERE pid = ? AND encounter = ?",
1706 array($invoice_refno, $patient_id, $encounter_id)
1711 // If appropriate, update the status of the related appointment to
1712 // "Checked out".
1713 updateAppointmentStatus($patient_id, $dosdate, '>');
1715 generate_receipt($patient_id, $encounter_id);
1716 exit();
1719 // Void attributes.
1720 $form_reason = empty($_GET['form_reason']) ? '' : $_GET['form_reason'];
1721 $form_notes = empty($_GET['form_notes' ]) ? '' : $_GET['form_notes'];
1723 // If "regen" encounter ID was given, then we must generate a new receipt ID.
1725 if (!$alertmsg && $patient_id && !empty($_GET['regen'])) {
1726 BillingUtilities::doVoid(
1727 $patient_id,
1728 $encounter_id,
1729 false,
1731 $form_reason,
1732 $form_notes
1734 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1735 $_GET['enc'] = $encounter_id;
1738 // If "enc" encounter ID was given, then we must generate a receipt and exit.
1740 if ($patient_id && !empty($_GET['enc'])) {
1741 if (empty($_GET['pdf'])) {
1742 generate_receipt($patient_id, $_GET['enc']);
1743 } else {
1744 // PDF receipt is requested. In this case we are probably in a new window.
1745 require_once($GLOBALS['OE_SITE_DIR'] . "/" . $GLOBALS['gbl_custom_receipt']);
1746 // $checkout_id is an optional specified checkout timestamp.
1747 $billtime = $checkout_id;
1748 if (!$billtime) {
1749 // No timestamp specified so use the last one.
1750 $checkout_times = craGetTimestamps($patient_id, $_GET['enc']);
1751 $billtime = empty($checkout_times) ? '' : $checkout_times[count($checkout_times) - 1];
1753 generateCheckoutReceipt($patient_id, $_GET['enc'], $billtime);
1755 exit();
1758 // If "void" encounter ID was given, then we must undo the last checkout.
1759 // Or for "voidall" undo all checkouts for the encounter.
1761 if (!$alertmsg && $patient_id && !empty($_GET['void'])) {
1762 BillingUtilities::doVoid($patient_id, $encounter_id, true, '', $form_reason, $form_notes);
1763 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1764 } else if (!$alertmsg && $patient_id && !empty($_GET['voidall'])) {
1765 BillingUtilities::doVoid($patient_id, $encounter_id, true, 'all', $form_reason, $form_notes);
1766 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1769 // Get the specified or first unbilled encounter ID for this patient.
1771 if (!$encounter_id) {
1772 $query = "SELECT encounter FROM billing WHERE " .
1773 "pid = ? AND activity = 1 AND billed = 0 AND code_type != 'TAX' " .
1774 "ORDER BY encounter DESC LIMIT 1";
1775 $brow = sqlQuery($query, array($patient_id));
1776 $query = "SELECT encounter FROM drug_sales WHERE " .
1777 "pid = ? AND billed = 0 " .
1778 "ORDER BY encounter DESC LIMIT 1";
1779 $drow = sqlQuery($query, array($patient_id));
1780 if (!empty($brow['encounter'])) {
1781 if (!empty($drow['encounter'])) {
1782 $encounter_id = min(intval($brow['encounter']), intval($drow['encounter']));
1783 } else {
1784 $encounter_id = $brow['encounter'];
1786 } else if (!empty($drow['encounter'])) {
1787 $encounter_id = $drow['encounter'];
1791 // If there are none, just redisplay the last receipt and exit.
1793 if (!$encounter_id) {
1794 generate_receipt($patient_id);
1795 exit();
1798 // Form requires billing permission.
1799 if (!AclMain::aclCheckCore('admin', 'super') && !AclMain::aclCheckCore('acct', 'bill')) {
1800 die("Not authorized!");
1803 // We have $patient_id and $encounter_id. Generate checksum if not already done.
1804 if (!$current_checksum) {
1805 $current_checksum = invoiceChecksum($patient_id, $encounter_id);
1808 // Get the valid practitioners, including those not active.
1809 $arr_users = array();
1810 $ures = sqlStatement(
1811 "SELECT id, username FROM users WHERE " .
1812 "( authorized = 1 OR info LIKE '%provider%' ) AND username != ''"
1814 while ($urow = sqlFetchArray($ures)) {
1815 $arr_users[$urow['id']] = '1';
1818 // Now write a data entry form:
1819 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
1820 // List unbilled product sales for the patient.
1821 // Present an editable dollar amount for each line item, a total
1822 // which is also the default value of the input payment amount,
1823 // and OK and Cancel buttons.
1825 <!DOCTYPE html>
1826 <html>
1827 <head>
1828 <?php Header::setupHeader(['datetime-picker']);?>
1830 <title><?php echo xlt('Patient Checkout'); ?></title>
1832 <style>
1833 @media (min-width: 992px){
1834 .modal-lg {
1835 width: 1000px !Important;
1838 </style>
1840 <script>
1841 var mypcc = <?php echo js_escape($GLOBALS['phone_country_code']); ?>;
1843 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
1845 // This clears tax amounts in preparation for recomputing taxes.
1846 // TBD: Probably don't need this at all.
1847 function clearTax(visible) {
1848 var f = document.forms[0];
1849 for (var i = 0; f['totaltax[' + i + ']']; ++i) {
1850 f['totaltax[' + i + ']'].value = '0.00';
1854 // This computes taxes and extended amount for the specified line, and returns
1855 // the extended amount.
1856 function calcTax(lino) {
1857 var f = document.forms[0];
1858 var pfx = 'line[' + lino + ']';
1859 var taxable = parseFloat(f[pfx + '[charge]'].value);
1860 var adjust = 0.00;
1861 if (f[pfx + '[adjust]']) {
1862 adjust = parseFloat(f[pfx + '[adjust]'].value);
1864 var adjreason = '';
1865 if (f[pfx + '[memo]']) {
1866 adjreason = f[pfx + '[memo]'].value;
1868 var extended = taxable - adjust;
1869 if (true
1870 <?php
1871 // Generate JavaScript that checks if the chosen adjustment type is to be
1872 // applied before taxes are computed. option_value 1 indicates that the
1873 // "After Taxes" checkbox is checked for an adjustment type.
1874 $tmpres = sqlStatement(
1875 "SELECT option_id FROM list_options WHERE " .
1876 "list_id = 'adjreason' AND option_value = 1 AND activity = 1"
1878 while ($tmprow = sqlFetchArray($tmpres)) {
1879 echo " && adjreason != " . js_escape($tmprow['option_id']) . "\n";
1883 taxable -= adjust;
1885 var taxnumrates = f[pfx + '[taxnumrates]'].value;
1886 var rates = taxnumrates.split(':');
1887 for (var i = 0; i < rates.length; ++i) {
1888 if (! f[pfx + '[tax][' + i + ']']) {
1889 break;
1891 var tax = 0;
1892 if (f[pfx + '[billtime]'].value) {
1893 // Line item is billed, use the tax amounts that were previously set for it.
1894 tax = parseFloat(f[pfx + '[tax][' + i + ']'].value);
1896 else {
1897 tax = taxable * parseFloat(rates[i]);
1898 tax = parseFloat(tax.toFixed(<?php echo $currdecimals ?>));
1899 if (isNaN(tax)) {
1900 alert('Tax rate not numeric at line ' + lino);
1902 f[pfx + '[tax][' + i + ']'].value = tax.toFixed(<?php echo $currdecimals ?>);
1904 extended += tax;
1905 var totaltax = parseFloat(f['totaltax[' + i + ']'].value) + tax;
1906 f['totaltax[' + i + ']'].value = totaltax.toFixed(<?php echo $currdecimals ?>);
1908 f[pfx + '[amount]'].value = extended.toFixed(<?php echo $currdecimals ?>);
1909 return extended;
1912 // This mess recomputes total charges and optionally applies a discount.
1913 // As part of this, taxes and extended amount are recomputed for each line.
1914 function computeDiscountedTotals(discount, visible) {
1915 clearTax(visible);
1916 var f = document.forms[0];
1917 var total = 0.00;
1918 for (var lino = 0; f['line[' + lino + '][code_type]']; ++lino) {
1919 var code_type = f['line[' + lino + '][code_type]'].value;
1920 // price is price per unit when the form was originally generated.
1921 // By contrast, amount is the dynamically-generated discounted line total.
1922 var price = parseFloat(f['line[' + lino + '][price]'].value);
1923 if (isNaN(price)) {
1924 alert('Price not numeric at line ' + lino);
1926 if (code_type == 'COPAY' || code_type == 'TAX') {
1927 // I think this case is obsolete now.
1928 total += parseFloat(price.toFixed(<?php echo $currdecimals ?>));
1929 continue;
1931 // Compute and set taxes and extended amount for the given line.
1932 // This also returns the extended amount.
1933 total += calcTax(lino);
1935 if (visible) {
1936 f.totalchg.value = total.toFixed(<?php echo $currdecimals ?>);
1938 return total - discount;
1941 // This computes and returns the total of payments.
1942 function computePaymentTotal() {
1943 var f = document.forms[0];
1944 var total = 0.00;
1945 for (var lino = 0; ('oldpay[' + lino + '][amount]') in f; ++lino) {
1946 var amount = parseFloat(f['oldpay[' + lino + '][amount]'].value);
1947 if (isNaN(amount)) {
1948 continue;
1950 amount = parseFloat(amount.toFixed(<?php echo $currdecimals ?>));
1951 total += amount;
1953 for (var lino = 0; ('payment[' + lino + '][amount]') in f; ++lino) {
1954 var amount = parseFloat(f['payment[' + lino + '][amount]'].value);
1955 if (isNaN(amount)) {
1956 amount = parseFloat(0);
1958 amount = parseFloat(amount.toFixed(<?php echo $currdecimals ?>));
1959 total += amount;
1960 // Set payment row's description to Refund if the amount is negative.
1961 var title = amount < 0 ? <?php echo xlj('Refund'); ?> : <?php echo xlj('New payment'); ?>;
1962 var span = document.getElementById('paytitle_' + lino);
1963 span.innerHTML = title;
1965 return total;
1968 // Recompute default payment amount with any discount applied, but
1969 // not if there is more than one input payment line.
1970 // This is called when the discount amount is changed, and initially.
1971 // As a side effect the tax line items are recomputed and
1972 // setComputedValues() is called.
1973 function billingChanged() {
1974 var f = document.forms[0];
1975 var discount = parseFloat(f.form_discount.value);
1976 if (isNaN(discount)) {
1977 discount = 0;
1979 <?php if (!$GLOBALS['discount_by_money']) { ?>
1980 // This site discounts by percentage, so convert it to a money amount.
1981 if (discount > 100) {
1982 discount = 100;
1984 if (discount < 0 ) {
1985 discount = 0;
1987 discount = 0.01 * discount * computeDiscountedTotals(0, false);
1988 <?php } ?>
1989 var total = computeDiscountedTotals(discount, true);
1990 // Get out if there is more than one input payment line.
1991 if (!('payment[1][amount]' in f)) {
1992 f['payment[0][amount]'].value = 0;
1993 total -= computePaymentTotal();
1994 f['payment[0][amount]'].value = total.toFixed(<?php echo $currdecimals ?>);
1996 setComputedValues();
1997 return true;
2000 // Function to return the adjustment type, if any, identified in a customer's Notes field.
2001 function adjTypeFromCustomer(customer) {
2002 var ret = '';
2003 <?php
2004 $tmpres = sqlStatement(
2005 "SELECT option_id, notes FROM list_options WHERE " .
2006 "list_id = 'chargecats' AND activity = 1"
2008 while ($tmprow = sqlFetchArray($tmpres)) {
2009 if (
2010 preg_match('/ADJ=(\w+)/', $tmprow['notes'], $matches) ||
2011 preg_match('/ADJ="(.*?)"/', $tmprow['notes'], $matches)
2013 echo " if (customer == " . js_escape($tmprow['option_id']) . ") ret = " . js_escape($matches[1]) . ";\n";
2017 return ret;
2020 // A line item adjustment was changed, so recompute stuff.
2021 function lineDiscountChanged(lino) {
2022 var f = document.forms[0];
2023 var discount = parseFloat(f['line[' + lino + '][adjust]'].value);
2024 if (isNaN(discount)) {
2025 discount = 0;
2027 var charge = parseFloat(f['line[' + lino + '][charge]'].value);
2028 if (isNaN(charge)) {
2029 charge = 0;
2031 <?php if (!$GLOBALS['discount_by_money']) { ?>
2032 // This site discounts by percentage, so convert it to a money amount.
2033 if (discount > 100) {
2034 discount = 100;
2036 if (discount < 0 ) {
2037 discount = 0;
2039 discount = 0.01 * discount * charge;
2040 <?php } ?>
2041 var amount = charge - discount;
2042 f['line[' + lino + '][amount]'].value = amount.toFixed(<?php echo $currdecimals ?>);
2043 // alert(f['line[' + lino + '][amount]'].value); // debugging
2044 if (discount) {
2045 // Apply default adjustment type if one is specified in the customer (charge category).
2046 var custElem = f['line[' + lino + '][chargecat]'];
2047 var adjtElem = f['line[' + lino + '][memo]'];
2048 if (custElem && custElem.value && adjtElem && !adjtElem.value) {
2049 var ccAdjType = adjTypeFromCustomer(custElem.value);
2050 if (ccAdjType) {
2051 adjtElem.value = ccAdjType;
2055 return billingChanged();
2058 // Set Total Payments, Difference and Balance Due when any amount changes.
2059 function setComputedValues() {
2060 var f = document.forms[0];
2061 var payment = computePaymentTotal();
2062 var difference = computeDiscountedTotals(0, false) - payment;
2063 var discount = parseFloat(f.form_discount.value);
2064 if (isNaN(discount)) {
2065 discount = 0;
2067 <?php if (!$GLOBALS['discount_by_money']) { ?>
2068 // This site discounts by percentage, so convert it to a money amount.
2069 if (discount > 100) {
2070 discount = 100;
2072 if (discount < 0 ) {
2073 discount = 0;
2075 discount = 0.01 * discount * computeDiscountedTotals(0, false);
2076 <?php } ?>
2077 var balance = difference - discount;
2078 f.form_totalpay.value = payment.toFixed(<?php echo $currdecimals ?>);
2079 f.form_difference.value = difference.toFixed(<?php echo $currdecimals ?>);
2080 f.form_balancedue.value = balance.toFixed(<?php echo $currdecimals ?>);
2081 return true;
2084 // This is called when [Compute] is clicked by the user.
2085 // Computes and sets the discount value from total charges less payment.
2086 // This also calls setComputedValues() so the balance due will be correct.
2087 function computeDiscount() {
2088 var f = document.forms[0];
2089 var charges = computeDiscountedTotals(0, false);
2090 var payment = computePaymentTotal();
2091 var discount = charges - payment;
2092 <?php if (!$GLOBALS['discount_by_money']) { ?>
2093 // This site discounts by percentage, so convert to that.
2094 discount = charges ? (100 * discount / charges) : 0;
2095 f.form_discount.value = discount.toFixed(4);
2096 <?php } else { ?>
2097 f.form_discount.value = discount.toFixed(<?php echo $currdecimals ?>);
2098 <?php } ?>
2099 setComputedValues();
2100 return false;
2103 // When the main adjustment reason changes, duplicate it to all per-line reasons.
2104 function discountTypeChanged() {
2105 var f = document.forms[0];
2106 if (f.form_discount_type && f.form_discount_type.selectedIndex) {
2107 for (lino = 0; f['line[' + lino + '][memo]']; ++lino) {
2108 // But do not change adjustment reason for billed items.
2109 if (f['line[' + lino + '][billtime]'].value) {
2110 continue;
2112 f['line[' + lino + '][memo]'].selectedIndex = f.form_discount_type.selectedIndex;
2117 // When the main charge category changes, duplicate it to all per-line categories.
2118 function chargeCategoryChanged() {
2119 var f = document.forms[0];
2120 if (f.form_charge_category && f.form_charge_category.selectedIndex) {
2121 for (lino = 0; f['line[' + lino + '][chargecat]']; ++lino) {
2122 // But do not change categories for billed items.
2123 if (f['line[' + lino + '][billtime]'].value) {
2124 continue;
2126 f['line[' + lino + '][chargecat]'].selectedIndex = f.form_charge_category.selectedIndex;
2131 // This is specific to IPPF and Suriname.
2132 function check_referrals() {
2133 <?php if (!empty($GLOBALS['gbl_menu_surinam_insurance'])) { ?>
2134 var msg = '';
2135 var f = document.forms[0];
2136 var services_needing_referral = '';
2137 for (var lino = 0; f['line[' + lino + '][chargecat]']; ++lino) {
2138 var pfx = 'line[' + lino + ']';
2139 var cust = f[pfx+'[chargecat]'].value;
2140 var price = parseFloat(f[pfx + '[price]'].value);
2141 if (isNaN(price)) {
2142 price = 0;
2144 if ((cust == 'SZF' || cust == 'KL-0098') && f[pfx+'[code_type]'].value != 'PROD' && price != 0) {
2145 services_needing_referral += f[pfx+'[code_type]'].value + ':' + f[pfx+'[code]'].value + ';';
2148 if (services_needing_referral) {
2149 top.restoreSession();
2150 $.ajax({
2151 dataType: "json",
2152 async: false, // We cannot continue without an answer.
2153 url: "<?php echo $GLOBALS['webroot']; ?>/library/ajax/check_szf_referrals_ajax.php",
2154 data: {
2155 "pid": <?php echo intval($patient_id); ?>,
2156 "encounter": <?php echo intval($encounter_id); ?>,
2157 "services": services_needing_referral
2159 success: function (jsondata, textstatus) {
2160 msg = jsondata['message'];
2164 if (msg && !confirm(msg)) {
2165 return false;
2167 <?php } ?>
2168 return true;
2171 // This is specific to IPPF and the NetSuite project.
2172 function check_giftcards() {
2173 <?php if (empty($GLOBALS['gbl_menu_netsuite'])) { ?>
2174 return true;
2175 <?php } else { ?>
2176 var f = document.forms[0];
2177 // If there is no gift card customer return true.
2178 var gc_customer_exists = false;
2179 for (lino = 0; f['line[' + lino + '][chargecat]']; ++lino) {
2180 var chargecat = f['line[' + lino + '][chargecat]'].value;
2181 if (
2182 1 == 2
2183 <?php
2184 $lres = sqlStatement(
2185 "SELECT option_id FROM list_options WHERE " .
2186 "list_id = 'chargecats' AND activity = 1 AND notes LIKE '%GIFTCARD=Y%' ORDER BY seq, title"
2188 while ($lrow = sqlFetchArray($lres)) {
2189 echo " || chargecat == " . js_escape($lrow['option_id']) . "\n";
2193 gc_customer_exists = true;
2196 if (!gc_customer_exists) {
2197 return true;
2199 // If there is a gift card payment method in the form return true.
2200 for (lino = 0; f['payment[' + lino + '][method]']; ++lino) {
2201 var method = f['payment[' + lino + '][method]'].value;
2202 if (
2203 1 == 2
2204 <?php
2205 $lres = sqlStatement(
2206 "SELECT option_id FROM list_options WHERE " .
2207 "list_id = 'paymethod' AND activity = 1 AND notes LIKE '%GIFTCARD=Y%' ORDER BY seq, title"
2209 while ($lrow = sqlFetchArray($lres)) {
2210 echo " || method == " . js_escape($lrow['option_id']) . "\n";
2214 if (!f['payment[' + lino + '][refno]'].value) {
2215 // There is a gift card payment method but no gift card ID is entered.
2216 alert(<?php echo xlj("Enter Gift Card number in the Reference field"); ?>);
2217 return false;
2219 return true; // There is a gift card payment method or no such methods exist.
2222 // There is a gift card customer but no gift card payment method.
2223 alert(<?php echo xlj("Gift Card Customer requires at least one Gift Card Payment Method"); ?>);
2224 return false;
2225 <?php } ?>
2228 function validate() {
2229 var f = document.forms[0];
2230 var missingtypeamt = false;
2231 var missingtypeany = false;
2232 for (lino = 0; f['line[' + lino + '][memo]']; ++lino) {
2233 if (f['line[' + lino + '][memo]'].selectedIndex == 0 && f['line[' + lino + '][billtime]'].value == '') {
2234 missingtypeany = true;
2235 if (parseFloat(f['line[' + lino + '][adjust]'].value) != 0) {
2236 missingtypeamt = true;
2240 <?php if (false /* adjustments_indicate_insurance */) { ?>
2241 if (missingtypeany) {
2242 alert(<?php echo xlj('Adjustment type is required for every line item.') ?>);
2243 return false;
2245 <?php } else { ?>
2246 if (missingtypeamt) {
2247 alert(<?php echo xlj('Adjustment type is required for each line with an adjustment.') ?>);
2248 return false;
2250 <?php } ?>
2251 if (!check_referrals()) {
2252 return false;
2254 if (!check_giftcards()) {
2255 return false;
2257 top.restoreSession();
2258 return true;
2261 </script>
2262 <?php
2263 // TBD: Not sure this will be used here.
2264 $arrOeUiSettings = array(
2265 'heading_title' => xl('Patient Checkout'),
2266 'include_patient_name' => true,// use only in appropriate pages
2267 'expandable' => false,
2268 'expandable_files' => array(),//all file names need suffix _xpd
2269 'action' => "",//conceal, reveal, search, reset, link or back
2270 'action_title' => "",
2271 'action_href' => "",//only for actions - reset, link or back
2272 'show_help_icon' => false,
2273 'help_file_name' => ""
2275 $oemr_ui = new OemrUI($arrOeUiSettings);
2277 </head>
2279 <body>
2281 <?php
2282 echo "<form method='post' action='pos_checkout.php?rde=" . attr_url($rapid_data_entry);
2283 if ($encounter_id) {
2284 echo "&enid=" . attr_url($encounter_id);
2286 if (!empty($_GET['framed'])) {
2287 echo '&framed=1';
2289 echo "' onsubmit='return validate()'>\n";
2290 echo "<input type='hidden' name='form_pid' value='" . attr($patient_id) . "' />\n";
2292 <input type="hidden" name="csrf_token_form" value="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" />
2294 <center>
2297 <table cellspacing='5' id='paytable' width='85%'>
2298 <?php
2299 $inv_date = '';
2300 $inv_provider = 0;
2301 $inv_payer = 0;
2302 $gcac_related_visit = false;
2303 $gcac_service_provided = false;
2305 // This is set by write_form_headers() when the encounter is known.
2306 $encounter_date = '';
2308 // This to save copays from the billing table.
2309 $aCopays = array();
2311 $lino = 0;
2313 $query = "SELECT id, date, code_type, code, modifier, code_text, " .
2314 "provider_id, payer_id, units, fee, encounter, billed, bill_date, chargecat " .
2315 "FROM billing WHERE pid = ? AND encounter = ? AND activity = 1 AND " .
2316 "code_type != 'TAX' ORDER BY id ASC";
2317 $bres = sqlStatement($query, array($patient_id, $encounter_id));
2319 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, s.quantity, " .
2320 "s.encounter, s.drug_id, s.billed, s.bill_date, s.selector, s.chargecat, d.name, r.provider_id " .
2321 "FROM drug_sales AS s " .
2322 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
2323 "LEFT OUTER JOIN prescriptions AS r ON r.id = s.prescription_id " .
2324 "WHERE s.pid = ? AND s.encounter = ? " .
2325 "ORDER BY s.sale_id ASC";
2326 $dres = sqlStatement($query, array($patient_id, $encounter_id));
2328 // Process billing table items. Note this includes co-pays.
2329 // Items that are not allowed to have a fee are skipped.
2331 while ($brow = sqlFetchArray($bres)) {
2332 $thisdate = substr($brow['date'], 0, 10);
2333 $code_type = $brow['code_type'];
2334 $inv_payer = $brow['payer_id'];
2335 if (!$inv_date || $inv_date < $thisdate) {
2336 $inv_date = $thisdate;
2338 // Co-pays are saved for later.
2339 if ($code_type == 'COPAY') {
2340 $aCopays[] = $brow;
2341 continue;
2344 $billtime = $brow['billed'] ? $brow['bill_date'] : '';
2346 // Collect tax rates, related code and provider ID.
2347 $taxrates = '';
2348 $related_code = '';
2349 if (!empty($code_types[$code_type]['fee'])) {
2350 $query = "SELECT taxrates, related_code FROM codes WHERE code_type = ? AND " .
2351 "code = ? AND ";
2352 $binds = array($code_types[$code_type]['id'], $brow['code']);
2353 if ($brow['modifier']) {
2354 $query .= "modifier = ?";
2355 $binds[] = $brow['modifier'];
2356 } else {
2357 $query .= "(modifier IS NULL OR modifier = '')";
2359 $query .= " LIMIT 1";
2360 $tmp = sqlQuery($query, $binds);
2361 $taxrates = $tmp['taxrates'];
2362 $related_code = $tmp['related_code'];
2363 markTaxes($taxrates);
2366 // Write the line item if it allows fees or is not a diagnosis.
2367 if (!empty($code_types[$code_type]['fee']) || empty($code_types[$code_type]['diag'])) {
2368 write_form_line(
2369 $code_type,
2370 $brow['code'],
2371 $brow['id'],
2372 $thisdate,
2373 ucfirst(strtolower($brow['code_text'])),
2374 $brow['fee'],
2375 $brow['units'],
2376 $taxrates,
2377 $billtime,
2378 $brow['chargecat']
2382 // Custom logic for IPPF to determine if a GCAC issue applies.
2383 if ($GLOBALS['ippf_specific'] && $related_code) {
2384 $relcodes = explode(';', $related_code);
2385 foreach ($relcodes as $codestring) {
2386 if ($codestring === '') {
2387 continue;
2389 list($codetype, $code) = explode(':', $codestring);
2390 if ($codetype !== 'IPPF2') {
2391 continue;
2393 if (preg_match('/^211/', $code)) {
2394 $gcac_related_visit = true;
2395 if (
2396 preg_match('/^211313030110/', $code) // Medical
2397 || preg_match('/^211323030230/', $code) // Surgical
2398 || preg_match('/^211403030110/', $code) // Incomplete Medical
2399 || preg_match('/^211403030230/', $code) // Incomplete Surgical
2401 $gcac_service_provided = true;
2408 // Process drug sales / products.
2410 while ($drow = sqlFetchArray($dres)) {
2411 if ($encounter_id && $drow['encounter'] != $encounter_id) {
2412 continue;
2414 $thisdate = $drow['sale_date'];
2415 if (!$encounter_id) {
2416 $encounter_id = $drow['encounter'];
2418 if (!$inv_provider && !empty($arr_users[$drow['provider_id']])) {
2419 $inv_provider = $drow['provider_id'] + 0;
2421 if (!$inv_date || $inv_date < $thisdate) {
2422 $inv_date = $thisdate;
2424 $billtime = $drow['billed'] ? $drow['bill_date'] : '';
2426 // Accumulate taxes for this product.
2427 $tmp = sqlQuery(
2428 "SELECT taxrates FROM drug_templates WHERE drug_id = ? ORDER BY selector LIMIT 1",
2429 array($drow['drug_id'])
2431 $taxrates = $tmp['taxrates'];
2432 markTaxes($taxrates);
2434 $tmpname = $drow['name'];
2435 if ($tmpname !== $drow['selector']) {
2436 $tmpname .= ' / ' . $drow['selector'];
2438 $units = $drow['quantity'] / FeeSheet::getBasicUnits($drow['drug_id'], $drow['selector']);
2440 write_form_line(
2441 'PROD',
2442 $drow['drug_id'],
2443 $drow['sale_id'],
2444 $thisdate,
2445 $tmpname,
2446 $drow['fee'],
2447 $units,
2448 $taxrates,
2449 $billtime,
2450 $drow['chargecat']
2454 // Line for total charges.
2455 $totalchg = formatMoneyNumber($totalchg);
2456 echo " <tr>\n";
2457 echo " <td class='bold' colspan='" . (!empty($GLOBALS['gbl_checkout_charges']) ? 4 : 3) .
2458 "' align='right'>" . xlt('Total Charges This Visit') . "</td>\n";
2459 echo " <td class='text' align='right'><input type='text' name='totalcba' " .
2460 "value='" . attr($totalchg) . "' size='6' maxlength='8' " .
2461 "style='text-align:right;background-color:transparent' readonly";
2462 echo "></td>\n";
2463 if (!$TAXES_AFTER_ADJUSTMENT) {
2464 for ($i = 0; $i < count($taxes); ++$i) {
2465 echo " <td class='text' align='right'><input type='text' name='totaltax[$i]' " .
2466 "value='0.00' size='6' maxlength='8' " .
2467 "style='text-align:right;background-color:transparent' readonly";
2468 echo "></td>\n";
2471 if (!empty($GLOBALS['gbl_charge_categories'])) {
2472 echo " <td class='text' align='right'>&nbsp;</td>\n"; // Empty space in charge category column.
2474 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
2475 // Note $totalchg is the total of charges before adjustments, and the following
2476 // field will be recomputed at onload time and as adjustments are entered.
2477 echo " <td class='text' align='right'>&nbsp;</td>\n"; // Empty space in adjustment type column.
2478 echo " <td class='text' align='right'>&nbsp;</td>\n"; // TBD: Total adjustments can go here.
2480 if ($TAXES_AFTER_ADJUSTMENT) {
2481 for ($i = 0; $i < count($taxes); ++$i) {
2482 echo " <td class='text' align='right'><input type='text' name='totaltax[$i]' " .
2483 "value='0.00' size='6' maxlength='8' " .
2484 "style='text-align:right;background-color:transparent' readonly";
2485 echo "></td>\n";
2488 echo " <td class='text' align='right'><input type='text' name='totalchg' " .
2489 "value='" . attr($totalchg) . "' size='6' maxlength='8' " .
2490 "style='text-align:right;background-color:transparent' readonly";
2491 echo "></td>\n";
2492 echo " </tr>\n";
2495 <tr>
2496 <td class='title' colspan='<?php echo 5 + $num_optional_columns; ?>'
2497 style='border-top:1px solid black; padding-top:5pt;'>
2498 <b><?php echo xlt('Payments'); ?></b>
2499 </td>
2500 </tr>
2502 <?php
2503 // Start new section for payments.
2504 echo " <td class='bold' colspan='$form_num_type_columns'>" . xlt('Type') . "</td>\n";
2505 echo " <td class='bold' colspan='$form_num_method_columns'>" . xlt('Payment Method') . "</td>\n";
2506 echo " <td class='bold' colspan='$form_num_ref_columns'>" . xlt('Reference') . "</td>\n";
2507 echo " <td class='bold' colspan='$form_num_amount_columns' align='right' nowrap>" . xlt('Payment Amount') . "</td>\n";
2508 echo " </tr>\n";
2510 $lino = 0;
2512 // Write co-pays.
2513 foreach ($aCopays as $brow) {
2514 $thisdate = substr($brow['date'], 0, 10);
2515 write_old_payment_line(
2516 xl('Prepayment'),
2517 $thisdate,
2518 $brow['code_text'],
2520 0 - $brow['fee']
2524 // Write any adjustments left in the aAdjusts array. This should only happen if
2525 // there was an invoice-level discount in a prior checkout of this encounter.
2526 foreach ($aAdjusts as $arow) {
2527 $memo = $arow['memotitle'];
2528 if ($arow['adj_amount'] == 0 && $memo === '') {
2529 continue;
2531 $reference = $arow['reference'];
2532 write_old_payment_line(
2533 xl('Adjustment'),
2534 $thisdate,
2535 $memo,
2536 $reference,
2537 $arow['adj_amount']
2541 // Write ar_activity payments.
2542 $ares = sqlStatement(
2543 "SELECT " .
2544 "a.payer_type, a.pay_amount, a.memo, s.session_id, s.reference, s.check_date " .
2545 "FROM ar_activity AS a " .
2546 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
2547 "a.pid = ? AND a.encounter = ? AND a.deleted IS NULL AND a.pay_amount != 0 " .
2548 "ORDER BY s.check_date, a.sequence_no",
2549 array($patient_id, $encounter_id)
2551 while ($arow = sqlFetchArray($ares)) {
2552 $memo = $arow['memo'];
2553 $reference = $arow['reference'];
2554 if (empty($arow['session_id'])) {
2555 $atmp = explode(' ', $memo, 2);
2556 $memo = $atmp[0];
2557 $reference = $atmp[1];
2559 $rowtype = $arow['payer_type'] ? xl('Insurance payment') : xl('Prepayment');
2560 write_old_payment_line(
2561 $rowtype,
2562 $thisdate,
2563 $memo,
2564 $reference,
2565 $arow['pay_amount']
2569 // Line for total payments.
2570 echo " <tr id='totalpay'>\n";
2571 echo " <td class='bold' colspan='$form_num_type_columns'><a href='#' onclick='return addPayLine()'>[" . xlt('Add Row') . "]</a></td>\n";
2572 echo " <td class='bold' colspan='" . ($form_num_method_columns + $form_num_ref_columns) .
2573 "' align='right'>" . xlt('Total Payments This Visit') . "</td>\n";
2574 echo " <td class='text' align='right' colspan='$form_num_amount_columns'><input type='text' name='form_totalpay' " .
2575 "value='' size='6' maxlength='8' " .
2576 "style='text-align:right;background-color:transparent' readonly";
2577 echo "></td>\n";
2578 echo " </tr>\n";
2580 // Line for Difference.
2581 echo " <tr>\n";
2582 echo " <td class='text' colspan='" . (5 + $num_optional_columns) .
2583 "' style='border-top:1px solid black; font-size:1pt; padding:0px;'>&nbsp;</td>\n";
2584 echo " </tr>\n";
2586 echo " <tr";
2587 // Hide this if only showing line item adjustments.
2588 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
2589 echo " style='display:none'";
2591 echo ">\n";
2592 echo " <td class='title' colspan='" . ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) .
2593 "' align='right'><b>" . xlt('Difference') . "</b></td>\n";
2594 echo " <td class='text' align='right' colspan='$form_num_amount_columns'><input type='text' name='form_difference' " .
2595 "value='' size='6' maxlength='8' " .
2596 "style='text-align:right;background-color:transparent' readonly";
2597 echo "></td>\n";
2598 echo " </tr>\n";
2600 if ($encounter_id) {
2601 $erow = sqlQuery(
2602 "SELECT provider_id FROM form_encounter WHERE pid = ? AND encounter = ? " .
2603 "ORDER BY id DESC LIMIT 1",
2604 array($patient_id, $encounter_id)
2606 $inv_provider = $erow['provider_id'] + 0;
2609 // Line for Discount.
2610 echo " <tr";
2611 // Hide this if only showing line item adjustments.
2612 if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
2613 echo " style='display:none'";
2615 echo ">\n";
2616 echo " <td class='bold' colspan='" . ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) .
2617 "' align='right'>";
2618 if (AclMain::aclCheckCore('acct', 'disc') || AclMain::aclCheckCore('admin', 'super')) {
2619 echo "<a href='#' onclick='return computeDiscount()'>[" . xlt('Compute') . "]</a> <b>";
2620 echo xlt('Discount/Adjustment') . "</b></td>\n";
2621 echo " <td class='text' align='right' colspan='$form_num_amount_columns'>" .
2622 "<input type='text' name='form_discount' " .
2623 "value='' size='6' maxlength='8' onkeyup='billingChanged()' " .
2624 "style='text-align:right' />";
2625 } else {
2626 echo "" . xlt('Discount/Adjustment') . "</td>\n";
2627 echo " <td class='text' align='right' colspan='$form_num_amount_columns'>" .
2628 "<input type='text' name='form_discount' value='' size='6' " .
2629 "style='text-align:right;background-color:transparent' readonly />";
2631 echo "</td>\n";
2632 echo " </tr>\n";
2634 // Line for Balance Due
2635 echo " <tr>\n";
2636 echo " <td class='title' colspan='" . ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) .
2637 "' align='right'>" . xlt('Balance Due') . "</td>\n";
2638 echo " <td class='text' align='right' colspan='$form_num_amount_columns'>" .
2639 "<input type='text' name='form_balancedue' " .
2640 "value='' size='6' maxlength='8' " .
2641 "style='text-align:right;background-color:transparent' readonly";
2642 echo "></td>\n";
2643 echo " </tr>\n";
2646 <tr>
2647 <td class='bold' colspan='<?php echo ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) +
2648 (empty($GLOBALS['gbl_charge_categories']) ? 0 : 1); ?>' align='right'>
2649 <label class="control-label" for="form_date"><?php echo xlt('Posting Date'); ?>:</label>
2650 </td>
2651 <td class='text' colspan='<?php echo $form_num_amount_columns; ?>' align='right'>
2652 <input type='text' class='form-control datepicker' id='form_date' name='form_date'
2653 title='yyyy-mm-dd date of service'
2654 value='<?php echo attr($encounter_date) ?>' />
2655 </td>
2656 </tr>
2658 <?php
2659 // A current invoice reference number may be present if there was a previous checkout.
2660 $tmprow = sqlQuery(
2661 "SELECT invoice_refno FROM form_encounter WHERE " .
2662 "pid = ? AND encounter = ?",
2663 array($patient_id, $encounter_id)
2665 $current_irnumber = $tmprow['invoice_refno'];
2667 if (!$current_irnumber) {
2668 // If this user has a non-empty irnpool assigned, show the pending
2669 // invoice reference number.
2670 $irnumber = BillingUtilities::getInvoiceRefNumber();
2671 if (!empty($irnumber)) {
2673 <tr>
2674 <td class='bold' colspan='<?php echo ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) +
2675 (empty($GLOBALS['gbl_charge_categories']) ? 0 : 1); ?>' align='right'>
2676 <?php echo xlt('Tentative Invoice Ref No'); ?>
2677 </td>
2678 <td class='text' align='right' colspan='<?php echo $form_num_amount_columns; ?>'>
2679 <?php echo text($irnumber); ?>
2680 </td>
2681 </tr>
2682 <?php
2683 } else if (!empty($GLOBALS['gbl_mask_invoice_number'])) {
2684 // Otherwise if there is an invoice reference number mask, ask for the refno.
2686 <tr>
2687 <td class='bold' colspan='<?php echo ($form_num_type_columns + $form_num_method_columns + $form_num_ref_columns) +
2688 (empty($GLOBALS['gbl_charge_categories']) ? 0 : 1); ?>' align='right'>
2689 <?php echo xlt('Invoice Reference Number'); ?>
2690 </td>
2691 <td class='text' align='right' colspan='<?php echo $form_num_amount_columns; ?>'>
2692 <input type='text' name='form_irnumber' size='10' value=''
2693 onkeyup='maskkeyup(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number']); ?>")'
2694 onblur='maskblur(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number']); ?>")'
2696 </td>
2697 </tr>
2698 <?php
2703 <tr>
2704 <td class='text' colspan='<?php echo 5 + $num_optional_columns; ?>' align='center'>
2705 &nbsp;<br>
2706 <input type='submit' name='form_save' value='<?php echo xlt('Save'); ?>'
2707 <?php if ($rapid_data_entry) { ?>
2708 style='background-color:#cc0000';color:#ffffff'
2709 <?php } ?>
2710 /> &nbsp;
2711 <?php if (empty($_GET['framed'])) { ?>
2712 <input type='button' value='Cancel' onclick='window.close()' />
2713 <?php } ?>
2714 <input type='hidden' name='form_provider' value='<?php echo attr($inv_provider); ?>' />
2715 <input type='hidden' name='form_payer' value='<?php echo attr($inv_payer); ?>' />
2716 <input type='hidden' name='form_encounter' value='<?php echo attr($encounter_id); ?>' />
2717 <input type='hidden' name='form_checksum' value='<?php echo attr($current_checksum); ?>' />
2718 </td>
2719 </tr>
2721 </table>
2723 </center>
2725 </form>
2727 <script>
2729 // Add a line for entering a payment.
2730 // Declared down here because $form_num_*_columns must be defined.
2731 var paylino = 0;
2732 function addPayLine() {
2733 var table = document.getElementById('paytable');
2734 for (var i = 0; i < table.rows.length; ++i) {
2735 if (table.rows[i].id == 'totalpay') {
2736 var row = table.insertRow(i);
2737 var cell;
2738 <?php
2739 foreach ($aCellHTML as $ix => $html) {
2740 echo " var html = \"$html\";\n";
2741 echo " cell = row.insertCell(row.cells.length);\n";
2742 if ($ix == 0) {
2743 echo " cell.colSpan = $form_num_type_columns;\n";
2745 if ($ix == 1) {
2746 echo " cell.colSpan = $form_num_method_columns;\n";
2748 if ($ix == 2) {
2749 echo " cell.colSpan = $form_num_ref_columns;\n";
2751 if ($ix == 3) {
2752 echo " cell.colSpan = $form_num_amount_columns;\n";
2754 echo " cell.innerHTML = html.replace(/%d/, paylino);\n";
2757 cell.align = 'right'; // last cell is right-aligned
2758 ++paylino;
2759 break;
2762 return false;
2766 // TBD: Clean up javascript indentation from here on. ////////////////////////////
2769 // Pop up the Payments window and close this one.
2770 function payprevious() {
2771 var width = 750;
2772 var height = 550;
2773 var loc = '../patient_file/front_payment.php?omitenc=' + <?php echo js_url($encounter_id); ?>;
2774 <?php if (empty($_GET['framed'])) { ?>
2775 opener.parent.left_nav.dlgopen(loc, '_blank', width, height);
2776 window.close();
2777 <?php } else { ?>
2778 var tmp = parent.left_nav ? parent.left_nav : parent.parent.left_nav;
2779 tmp.dlgopen(loc, '_blank', width, height);
2780 <?php } ?>
2783 discountTypeChanged();
2784 addPayLine();
2785 billingChanged();
2787 <?php
2788 if ($alertmsg) {
2789 echo "alert(" . js_escape($alertmsg) . ");\n";
2792 if ($gcac_related_visit && !$gcac_service_provided) {
2793 // Skip this warning if the GCAC visit form is not allowed.
2794 $grow = sqlQuery(
2795 "SELECT COUNT(*) AS count FROM layout_group_properties " .
2796 "WHERE grp_form_id = 'LBFgcac' AND grp_group_id = '' AND grp_activity = 1"
2798 if (!empty($grow['count'])) { // if gcac is used
2799 // Skip this warning if referral or abortion in TS.
2800 $grow = sqlQuery(
2801 "SELECT COUNT(*) AS count FROM transactions " .
2802 "WHERE title = 'Referral' AND refer_date IS NOT NULL AND " .
2803 "refer_date = ? AND pid = ?",
2804 array($inv_date, $patient_id)
2806 if (empty($grow['count'])) { // if there is no referral
2807 $grow = sqlQuery(
2808 "SELECT COUNT(*) AS count FROM forms " .
2809 "WHERE pid = ? AND encounter = ? AND " .
2810 "deleted = 0 AND formdir = 'LBFgcac'",
2811 array($patient_id, $encounter_id)
2813 if (empty($grow['count'])) { // if there is no gcac form
2814 echo " alert(" . xlj('This visit will need a GCAC form, referral or procedure service.') . ");\n";
2818 } // end if ($gcac_related_visit)
2820 if ($GLOBALS['ippf_specific']) {
2821 // More validation:
2822 // o If there is an initial contraceptive consult, make sure a LBFccicon form exists with that method on it.
2823 // o If a LBFccicon form exists with a new method on it, make sure the TS initial consult exists.
2825 require_once("$srcdir/contraception_billing_scan.inc.php");
2826 contraception_billing_scan($patient_id, $encounter_id);
2828 $csrow = sqlQuery(
2829 "SELECT field_value FROM shared_attributes WHERE pid = ? AND encounter = ? AND field_id = 'cgen_MethAdopt'",
2830 array($patient_id, $encounter_id)
2832 $csmethod = empty($csrow['field_value']) ? '' : $csrow['field_value'];
2834 if (($csmethod || $contraception_billing_code) && $csmethod != "IPPFCM:$contraception_billing_code") {
2835 $warningMessage = xl('Warning') . ': ';
2836 if (!$csmethod) {
2837 $warningMessage .= xl('there is a contraception service but no contraception form new method');
2838 } else if (!$contraception_billing_code) {
2839 $warningMessage .= xl('there is a contraception form new method but no contraception service');
2840 } else {
2841 $warningMessage .= xl('new method in contraception form does not match the contraception service');
2843 echo " alert(" . js_escape($warningMessage) . ");\n";
2847 </script>
2848 </body>
2849 </html>