Portal credential enhancements
[openemr.git] / interface / patient_file / pos_checkout.php
blob7b07218c4aee0caaafc19afd8e71d49aa6f1fb86
1 <?php
2 /**
3 * Checkout Module.
5 * This module supports a popup window to handle patient checkout
6 * as a point-of-sale transaction. Support for in-house drug sales
7 * is included.
9 * <pre>
10 * Important notes about system design:
11 * (1) Drug sales may or may not be associated with an encounter;
12 * they are if they are paid for concurrently with an encounter, or
13 * if they are "product" (non-prescription) sales via the Fee Sheet.
14 * (2) Drug sales without an encounter will have 20YYMMDD, possibly
15 * with a suffix, as the encounter-number portion of their invoice
16 * number.
17 * (3) Payments are saved as AR only, don't mess with the billing table.
18 * See library/classes/WSClaim.class.php for posting code.
19 * (4) On checkout, the billing and drug_sales table entries are marked
20 * as billed and so become unavailable for further billing.
21 * (5) Receipt printing must be a separate operation from payment,
22 * and repeatable.
24 * TBD:
25 * If this user has 'irnpool' set
26 * on display of checkout form
27 * show pending next invoice number
28 * on applying checkout
29 * save next invoice number to form_encounter
30 * compute new next invoice number
31 * on receipt display
32 * show invoice number
33 * </pre>
35 * @package OpenEMR
36 * @link http://www.open-emr.org
37 * @author Rod Roark <rod@sunsetsystems.com>
38 * @author Brady Miller <brady.g.miller@gmail.com>
39 * @copyright Copyright (c) 2006-2017 Rod Roark <rod@sunsetsystems.com>
40 * @copyright Copyright (c) 2017-2018 Brady Miller <brady.g.miller@gmail.com>
41 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
45 require_once("../globals.php");
46 require_once("$srcdir/acl.inc");
47 require_once("$srcdir/patient.inc");
48 require_once("../../custom/code_types.inc.php");
50 use OpenEMR\Billing\BillingUtilities;
51 use OpenEMR\Common\Csrf\CsrfUtils;
52 use OpenEMR\Core\Header;
53 use OpenEMR\OeUI\OemrUI;
54 use OpenEMR\Services\FacilityService;
56 $facilityService = new FacilityService();
58 $currdecimals = $GLOBALS['currency_decimals'];
60 $details = empty($_GET['details']) ? 0 : 1;
62 $patient_id = empty($_GET['ptid']) ? $pid : 0 + $_GET['ptid'];
64 // Get the patient's name and chart number.
65 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
67 // Output HTML for an invoice line item.
69 $prevsvcdate = '';
70 function receiptDetailLine($svcdate, $description, $amount, $quantity)
72 global $prevsvcdate, $details;
73 if (!$details) {
74 return;
76 $amount = sprintf('%01.2f', $amount);
77 if (empty($quantity)) {
78 $quantity = 1;
80 $price = sprintf('%01.4f', $amount / $quantity);
81 $tmp = sprintf('%01.2f', $price);
82 if ($price == $tmp) {
83 $price = $tmp;
85 echo " <tr>\n";
86 echo " <td>" . ($svcdate == $prevsvcdate ? '&nbsp;' : text(oeFormatShortDate($svcdate))) . "</td>\n";
87 echo " <td>" . text($description) . "</td>\n";
88 echo " <td align='right'>" . text(oeFormatMoney($price)) . "</td>\n";
89 echo " <td align='right'>" . text($quantity) . "</td>\n";
90 echo " <td align='right'>" . text(oeFormatMoney($amount)) . "</td>\n";
91 echo " </tr>\n";
92 $prevsvcdate = $svcdate;
95 // Output HTML for an invoice payment.
97 function receiptPaymentLine($paydate, $amount, $description = '')
99 $amount = sprintf('%01.2f', 0 - $amount); // make it negative
100 echo " <tr>\n";
101 echo " <td>" . text(oeFormatShortDate($paydate)) . "</td>\n";
102 echo " <td>" . xlt('Payment') . " " . text($description) . "</td>\n";
103 echo " <td colspan='2'>&nbsp;</td>\n";
104 echo " <td align='right'>" . text(oeFormatMoney($amount)) . "</td>\n";
105 echo " </tr>\n";
108 // Generate a receipt from the last-billed invoice for this patient,
109 // or for the encounter specified as a GET parameter.
111 function generate_receipt($patient_id, $encounter = 0)
113 //REMEMBER the entire receipt is generated here, have to echo DOC type etc and closing tags to create a valid webpsge
114 global $sl_err, $sl_cash_acc, $css_header, $details, $facilityService;
116 // Get details for what we guess is the primary facility.
117 $frow = $facilityService->getPrimaryBusinessEntity(array("useLegacyImplementation" => true));
119 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code,providerID');
121 // Get the most recent invoice data or that for the specified encounter.
123 // Adding a provider check so that their info can be displayed on receipts
124 if ($encounter) {
125 $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " .
126 "WHERE pid = ? AND encounter = ?", array($patient_id,$encounter));
127 } else {
128 $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " .
129 "WHERE pid = ? " .
130 "ORDER BY id DESC LIMIT 1", array($patient_id));
132 if (empty($ferow)) {
133 die(xlt("This patient has no activity."));
135 $trans_id = $ferow['id'];
136 $encounter = $ferow['encounter'];
137 $svcdate = substr($ferow['date'], 0, 10);
139 if ($GLOBALS['receipts_by_provider']) {
140 if (isset($ferow['provider_id'])) {
141 $encprovider = $ferow['provider_id'];
142 } elseif (isset($patdata['providerID'])) {
143 $encprovider = $patdata['providerID'];
144 } else {
145 $encprovider = -1;
149 if ($encprovider) {
150 $providerrow = sqlQuery("SELECT fname, mname, lname, title, street, streetb, " .
151 "city, state, zip, phone, fax FROM users WHERE id = ?", array($encprovider));
154 // Get invoice reference number.
155 $encrow = sqlQuery("SELECT invoice_refno FROM form_encounter WHERE " .
156 "pid = ? AND encounter = ? LIMIT 1", array($patient_id,$encounter));
157 $invoice_refno = $encrow['invoice_refno'];
159 // being deliberately echoed to indicate it is part of the php function generate_receipt
160 echo "<!DOCTYPE html>". PHP_EOL;
161 echo "<html>".PHP_EOL;
162 echo"<head>".PHP_EOL;
165 <?php Header::setupHeader(['datetime-picker']);?>
166 <title><?php echo xlt('Receipt for Payment'); ?></title>
167 <script language="JavaScript">
169 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
171 $(function() {
172 var win = top.printLogSetup ? top : opener.top;
173 win.printLogSetup(document.getElementById('printbutton'));
176 // Process click on Print button.
177 function printlog_before_print() {
178 var divstyle = document.getElementById('hideonprint').style;
179 divstyle.display = 'none';
182 // Process click on Delete button.
183 function deleteme() {
184 dlgopen('deleter.php?billing=' + <?php echo js_url($patient_id.".".$encounter); ?> + '&csrf_token_form=' + <?php echo js_url(CsrfUtils::collectCsrfToken()); ?>, '_blank', 500, 450);
185 return false;
188 // Called by the deleteme.php window on a successful delete.
189 function imdeleted() {
190 window.close();
193 </script>
194 <style>
195 @media only screen and (max-width: 768px) {
196 [class*="col-"] {
197 width: 100%;
198 text-align:left!Important;
201 .table {
202 margin: auto;
203 width: 90% !important;
205 @media (min-width: 992px){
206 .modal-lg {
207 width: 1000px !Important;
210 </style>
211 <title><?php echo xlt('Patient Checkout'); ?></title>
213 </head>
214 <body class="body_top">
215 <div class="container">
216 <div class= "row text-center">
217 <?php
218 if ($GLOBALS['receipts_by_provider'] && !empty($providerrow)) {
219 printProviderHeader($providerrow);
220 } else {
221 printFacilityHeader($frow);
222 } ?>
223 <?php
224 echo xlt("Receipt Generated") . ":" . text(date(' F j, Y'));
225 if ($invoice_refno) {
226 echo " " . xlt("Invoice Number") . ": " . text($invoice_refno) . " " . xlt("Service Date") . ": " . text($svcdate);
229 </div>
230 <div class= "row">
231 <div class= 'col-xs-6 col-lg-offset-2'>
232 <?php echo text($patdata['fname']) . ' ' . text($patdata['mname']) . ' ' . text($patdata['lname']) ?><br>
233 <?php echo text($patdata['street']) ?><br>
234 <?php echo text($patdata['city']) . ', ' . text($patdata['state']) . ' ' . text($patdata['postal_code']) ?><br>
235 </div>
236 </div>
237 <div class= "row ">
238 <div class= 'col-xs-6 col-lg-offset-3'>
239 <table class="table">
240 <thead>
241 <tr>
242 <th><strong><?php echo xlt('Date'); ?></strong></th>
243 <th><strong><?php echo xlt('Description'); ?></strong></th>
244 <th class='text-right'><strong><?php echo $details ? xlt('Price') : '&nbsp;'; ?></strong></th>
245 <th class='text-right'><strong><?php echo $details ? xlt('Qty') : '&nbsp;'; ?></strong></th>
246 <th class='text-right'><strong><?php echo xlt('Total'); ?></strong></th>
247 </tr>
248 </thead>
249 <?php
250 $charges = 0.00;
252 // Product sales
253 $inres = sqlStatement("SELECT s.sale_id, s.sale_date, s.fee, " .
254 "s.quantity, s.drug_id, d.name " .
255 "FROM drug_sales AS s LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
256 // "WHERE s.pid = '$patient_id' AND s.encounter = '$encounter' AND s.fee != 0 " .
257 "WHERE s.pid = ? AND s.encounter = ? " .
258 "ORDER BY s.sale_id", array($patient_id,$encounter));
259 while ($inrow = sqlFetchArray($inres)) {
260 $charges += sprintf('%01.2f', $inrow['fee']);
261 receiptDetailLine(
262 $inrow['sale_date'],
263 $inrow['name'],
264 $inrow['fee'],
265 $inrow['quantity']
268 // Service and tax items
269 $inres = sqlStatement("SELECT * FROM billing WHERE " .
270 "pid = ? AND encounter = ? AND " .
271 // "code_type != 'COPAY' AND activity = 1 AND fee != 0 " .
272 "code_type != 'COPAY' AND activity = 1 " .
273 "ORDER BY id", array($patient_id,$encounter));
274 while ($inrow = sqlFetchArray($inres)) {
275 $charges += sprintf('%01.2f', $inrow['fee']);
276 receiptDetailLine(
277 $svcdate,
278 $inrow['code_text'],
279 $inrow['fee'],
280 $inrow['units']
283 // Adjustments.
284 $inres = sqlStatement("SELECT " .
285 "a.code_type, a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
286 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
287 "FROM ar_activity AS a " .
288 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
289 "a.pid = ? AND a.encounter = ? AND " .
290 "a.adj_amount != 0 " .
291 "ORDER BY s.check_date, a.sequence_no", array($patient_id,$encounter));
292 while ($inrow = sqlFetchArray($inres)) {
293 $charges -= sprintf('%01.2f', $inrow['adj_amount']);
294 $payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
295 receiptDetailLine(
296 $svcdate,
297 $payer . ' ' . $inrow['memo'],
298 0 - $inrow['adj_amount'],
303 <tr>
304 <td colspan='5'>&nbsp;</td>
305 </tr>
306 <tr>
307 <td><?php echo text(oeFormatShortDate($svcdispdate)); ?></td>
308 <td><b><?php echo xlt('Total Charges'); ?></b></td>
309 <td class='text-right'>&nbsp;</td>
310 <td class='text-right'>&nbsp;</td>
311 <td class='text-right'><?php echo text(oeFormatMoney($charges, true)) ?></td>
312 </tr>
313 <tr>
314 <td colspan='5'>&nbsp;</td>
315 </tr>
316 <?php
317 // Get co-pays.
318 $inres = sqlStatement("SELECT fee, code_text FROM billing WHERE " .
319 "pid = ? AND encounter = ? AND " .
320 "code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
321 "ORDER BY id", array($patient_id,$encounter));
322 while ($inrow = sqlFetchArray($inres)) {
323 $charges += sprintf('%01.2f', $inrow['fee']);
324 receiptPaymentLine($svcdate, 0 - $inrow['fee'], $inrow['code_text']);
326 // Get other payments.
327 $inres = sqlStatement("SELECT " .
328 "a.code_type, a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
329 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
330 "FROM ar_activity AS a " .
331 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
332 "a.pid = ? AND a.encounter = ? AND " .
333 "a.pay_amount != 0 " .
334 "ORDER BY s.check_date, a.sequence_no", array($patient_id,$encounter));
335 while ($inrow = sqlFetchArray($inres)) {
336 $payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
337 $charges -= sprintf('%01.2f', $inrow['pay_amount']);
338 receiptPaymentLine(
339 $svcdate,
340 $inrow['pay_amount'],
341 $payer . ' ' . $inrow['reference']
345 <tr>
346 <td colspan='5'>&nbsp;</td>
347 </tr>
348 <tr>
349 <td>&nbsp;</td>
350 <td><b><?php echo xlt('Balance Due'); ?></b></td>
351 <td colspan='2'>&nbsp;</td>
352 <td class='text-right'><?php echo text(oeFormatMoney($charges, true)) ?></td>
353 </tr>
354 </table>
355 </div>
356 </div>
357 <br>
358 <div class= "row ">
359 <div class="form-group clearfix">
360 <div class="col-sm-12 text-center" id="hideonprint">
361 <div class="btn-group" role="group">
362 <button class="btn btn-default btn-print" id='printbutton'><?php echo xlt('Print'); ?></button>
363 <?php if (acl_check('acct', 'disc')) { ?>
364 <button class="btn btn-default btn-undo" onclick='return deleteme();'><?php echo xlt('Undo Checkout'); ?></button>
365 <?php } ?>
366 <?php if ($details) { ?>
367 <button class="btn btn-default btn-hide" onclick="top.restoreSession(); window.location.href = 'pos_checkout.php?details=0&ptid=<?php echo attr_url($patient_id); ?>&enc=<?php echo attr_url($encounter); ?>'"><?php echo xlt('Hide Details'); ?></button>
368 <?php } else { ?>
369 <button class="btn btn-default btn-show" onclick="top.restoreSession(); window.location.href = 'pos_checkout.php?details=1&ptid=<?php echo attr_url($patient_id); ?>&enc=<?php echo attr_url($encounter); ?>'"><?php echo xlt('Show Details'); ?></button>
370 <?php } ?>
371 </div>
372 </div>
373 </div>
374 </div>
375 </div><!--end of receipt container div-->
376 <?php // echoing the closing tags for receipts
377 echo"</body>".PHP_EOL;
378 echo "</html>".PHP_EOL;
379 } // end function generate_receipt()
381 <?php
384 // Function to output a line item for the input form.
386 $lino = 0;
387 function write_form_line(
388 $code_type,
389 $code,
390 $id,
391 $date,
392 $description,
393 $amount,
394 $units,
395 $taxrates
397 global $lino;
398 $amount = sprintf("%01.2f", $amount);
399 if (empty($units)) {
400 $units = 1;
402 $price = $amount / $units; // should be even cents, but ok here if not
403 if ($code_type == 'COPAY' && !$description) {
404 $description = xl('Payment');
406 echo " <tr>\n";
407 echo " <td>" . text(oeFormatShortDate($date));
408 echo "<input type='hidden' name='line[$lino][code_type]' value='" . attr($code_type) . "'>";
409 echo "<input type='hidden' name='line[$lino][code]' value='" . attr($code) . "'>";
410 echo "<input type='hidden' name='line[$lino][id]' value='" . attr($id) . "'>";
411 echo "<input type='hidden' name='line[$lino][description]' value='" . attr($description) . "'>";
412 echo "<input type='hidden' name='line[$lino][taxrates]' value='" . attr($taxrates) . "'>";
413 echo "<input type='hidden' name='line[$lino][price]' value='" . attr($price) . "'>";
414 echo "<input type='hidden' name='line[$lino][units]' value='" . attr($units) . "'>";
415 echo "</td>\n";
416 echo " <td>" . text($description) . "</td>";
417 echo " <td align='right'>" . text($units) . "</td>";
418 echo " <td align='right'><input type='text' name='line[$lino][amount]' " .
419 "value='" . attr($amount) . "' size='6' maxlength='8'";
420 // Modifying prices requires the acct/disc permission.
421 // if ($code_type == 'TAX' || ($code_type != 'COPAY' && !acl_check('acct','disc')))
422 echo " style='text-align:right;background-color:transparent' readonly";
423 // else echo " style='text-align:right' onkeyup='computeTotals()'";
424 echo "></td>\n";
425 echo " </tr>\n";
426 ++$lino;
429 // Create the taxes array. Key is tax id, value is
430 // (description, rate, accumulated total).
431 $taxes = array();
432 $pres = sqlStatement("SELECT option_id, title, option_value " .
433 "FROM list_options WHERE list_id = 'taxrate' AND activity = 1 ORDER BY seq, title, option_id");
434 while ($prow = sqlFetchArray($pres)) {
435 $taxes[$prow['option_id']] = array($prow['title'], $prow['option_value'], 0);
438 // Print receipt header for facility
439 function printFacilityHeader($frow)
441 echo "<p><b>" . text($frow['name']) .
442 "<br>" . text($frow['street']) .
443 "<br>" . text($frow['city']) . ', ' . text($frow['state']) . ' ' . text($frow['postal_code']) .
444 "<br>" . text($frow['phone']) .
445 "<br>&nbsp" .
446 "<br>";
449 // Pring receipt header for Provider
450 function printProviderHeader($pvdrow)
452 echo "<p><b>" . text($pvdrow['title']) . " " . text($pvdrow['fname']) . " " . text($pvdrow['mname']) . " " . text($pvdrow['lname']) . " " .
453 "<br>" . text($pvdrow['street']) .
454 "<br>" . text($pvdrow['city']) . ', ' . text($pvdrow['state']) . ' ' . text($pvdrow['postal_code']) .
455 "<br>" . text($pvdrow['phone']) .
456 "<br>&nbsp" .
457 "<br>";
460 // Mark the tax rates that are referenced in this invoice.
461 function markTaxes($taxrates)
463 global $taxes;
464 $arates = explode(':', $taxrates);
465 if (empty($arates)) {
466 return;
468 foreach ($arates as $value) {
469 if (!empty($taxes[$value])) {
470 $taxes[$value][2] = '1';
475 $payment_methods = array(
476 'Cash',
477 'Check',
478 'MC',
479 'VISA',
480 'AMEX',
481 'DISC',
482 'Other');
484 $alertmsg = ''; // anything here pops up in an alert box
486 // If the Save button was clicked...
488 if ($_POST['form_save']) {
489 if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) {
490 CsrfUtils::csrfNotVerified();
493 // On a save, do the following:
494 // Flag drug_sales and billing items as billed.
495 // Post the corresponding invoice with its payment(s) to sql-ledger
496 // and be careful to use a unique invoice number.
497 // Call the generate-receipt function.
498 // Exit.
500 $form_pid = $_POST['form_pid'];
501 $form_encounter = $_POST['form_encounter'];
503 // Get the posting date from the form as yyyy-mm-dd.
504 $dosdate = date("Y-m-d");
505 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $_POST['form_date'], $matches)) {
506 $dosdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
509 // If there is no associated encounter (i.e. this invoice has only
510 // prescriptions) then assign an encounter number of the service
511 // date, with an optional suffix to ensure that it's unique.
513 if (! $form_encounter) {
514 $form_encounter = substr($dosdate, 0, 4) . substr($dosdate, 5, 2) . substr($dosdate, 8, 2);
515 $tmp = '';
516 while (true) {
517 $ferow = sqlQuery("SELECT id FROM form_encounter WHERE " .
518 "pid = ? AND encounter = ?", array($form_pid, $form_encounter.$tmp));
519 if (empty($ferow)) {
520 break;
522 $tmp = $tmp ? $tmp + 1 : 1;
524 $form_encounter .= $tmp;
527 // Delete any TAX rows from billing because they will be recalculated.
528 sqlStatement("UPDATE billing SET activity = 0 WHERE " .
529 "pid = ? AND encounter = ? AND " .
530 "code_type = 'TAX'", array($form_pid,$form_encounter));
532 $form_amount = $_POST['form_amount'];
533 $lines = $_POST['line'];
535 for ($lino = 0; $lines[$lino]['code_type']; ++$lino) {
536 $line = $lines[$lino];
537 $code_type = $line['code_type'];
538 $id = $line['id'];
539 $amount = sprintf('%01.2f', trim($line['amount']));
542 if ($code_type == 'PROD') {
543 // Product sales. The fee and encounter ID may have changed.
544 $query = "update drug_sales SET fee = ?, " .
545 "encounter = ?, billed = 1 WHERE " .
546 "sale_id = ?";
547 sqlQuery($query, array($amount,$form_encounter,$id));
548 } elseif ($code_type == 'TAX') {
549 // In the SL case taxes show up on the invoice as line items.
550 // Otherwise we gotta save them somewhere, and in the billing
551 // table with a code type of TAX seems easiest.
552 // They will have to be stripped back out when building this
553 // script's input form.
554 BillingUtilities::addBilling(
555 $form_encounter,
556 'TAX',
557 'TAX',
558 'Taxes',
559 $form_pid,
564 $amount,
569 } else {
570 // Because there is no insurance here, there is no need for a claims
571 // table entry and so we do not call updateClaim(). Note we should not
572 // eliminate billed and bill_date from the billing table!
573 $query = "UPDATE billing SET fee = ?, billed = 1, " .
574 "bill_date = NOW() WHERE id = ?";
575 sqlQuery($query, array($amount,$id));
579 // Post discount.
580 if ($_POST['form_discount']) {
581 if ($GLOBALS['discount_by_money']) {
582 $amount = sprintf('%01.2f', trim($_POST['form_discount']));
583 } else {
584 $amount = sprintf('%01.2f', trim($_POST['form_discount']) * $form_amount / 100);
586 $memo = xl('Discount');
587 $time = date('Y-m-d H:i:s');
588 sqlBeginTrans();
589 $sequence_no = sqlQuery("SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array($form_pid, $form_encounter));
590 $query = "INSERT INTO ar_activity ( " .
591 "pid, encounter, sequence_no, code, modifier, payer_type, post_user, post_time, " .
592 "session_id, memo, adj_amount " .
593 ") VALUES ( " .
594 "?, " .
595 "?, " .
596 "?, " .
597 "'', " .
598 "'', " .
599 "'0', " .
600 "?, " .
601 "?, " .
602 "'0', " .
603 "?, " .
604 "? " .
605 ")";
606 sqlStatement($query, array($form_pid,$form_encounter,$sequence_no['increment'],$_SESSION['authUserID'],$time,$memo,$amount));
607 sqlCommitTrans();
610 // Post payment.
611 if ($_POST['form_amount']) {
612 $amount = sprintf('%01.2f', trim($_POST['form_amount']));
613 $form_source = trim($_POST['form_source']);
614 $paydesc = trim($_POST['form_method']);
615 //Fetching the existing code and modifier
616 $ResultSearchNew = sqlStatement(
617 "SELECT * FROM billing LEFT JOIN code_types ON billing.code_type=code_types.ct_key ".
618 "WHERE code_types.ct_fee=1 AND billing.activity!=0 AND billing.pid =? AND encounter=? ORDER BY billing.code,billing.modifier",
619 array($form_pid,$form_encounter)
621 if ($RowSearch = sqlFetchArray($ResultSearchNew)) {
622 $Codetype=$RowSearch['code_type'];
623 $Code=$RowSearch['code'];
624 $Modifier=$RowSearch['modifier'];
625 } else {
626 $Codetype='';
627 $Code='';
628 $Modifier='';
630 $session_id=sqlInsert(
631 "INSERT INTO ar_session (payer_id,user_id,reference,check_date,deposit_date,pay_total,".
632 " global_amount,payment_type,description,patient_id,payment_method,adjustment_code,post_to_date) ".
633 " VALUES ('0',?,?,now(),?,?,'','patient','COPAY',?,?,'patient_payment',now())",
634 array($_SESSION['authId'],$form_source,$dosdate,$amount,$form_pid,$paydesc)
637 sqlBeginTrans();
638 $sequence_no = sqlQuery("SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array($form_pid, $form_encounter));
639 $insrt_id=sqlInsert(
640 "INSERT INTO ar_activity (pid,encounter,sequence_no,code_type,code,modifier,payer_type,post_time,post_user,session_id,pay_amount,account_code)".
641 " VALUES (?,?,?,?,?,?,0,?,?,?,?,'PCP')",
642 array($form_pid,$form_encounter,$sequence_no['increment'],$Codetype,$Code,$Modifier,$dosdate,$_SESSION['authId'],$session_id,$amount)
644 sqlCommitTrans();
647 // If applicable, set the invoice reference number.
648 $invoice_refno = '';
649 if (isset($_POST['form_irnumber'])) {
650 $invoice_refno = trim($_POST['form_irnumber']);
651 } else {
652 $invoice_refno = BillingUtilities::updateInvoiceRefNumber();
654 if ($invoice_refno) {
655 sqlStatement("UPDATE form_encounter " .
656 "SET invoice_refno = ? " .
657 "WHERE pid = ? AND encounter = ?", array($invoice_refno,$form_pid,$form_encounter));
660 generate_receipt($form_pid, $form_encounter);
661 exit();
664 // If an encounter ID was given, then we must generate a receipt.
666 if (!empty($_GET['enc'])) {
667 generate_receipt($patient_id, $_GET['enc']);
668 exit();
671 // Get the unbilled billing table items for this patient.
672 $query = "SELECT id, date, code_type, code, modifier, code_text, " .
673 "provider_id, payer_id, units, fee, encounter " .
674 "FROM billing WHERE pid = ? AND activity = 1 AND " .
675 "billed = 0 AND code_type != 'TAX' " .
676 "ORDER BY encounter DESC, id ASC";
677 $bres = sqlStatement($query, array($patient_id));
679 // Get the product sales for this patient.
680 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, " .
681 "s.quantity, s.encounter, s.drug_id, d.name, r.provider_id " .
682 "FROM drug_sales AS s " .
683 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
684 "LEFT OUTER JOIN prescriptions AS r ON r.id = s.prescription_id " .
685 "WHERE s.pid = ? AND s.billed = 0 " .
686 "ORDER BY s.encounter DESC, s.sale_id ASC";
687 $dres = sqlStatement($query, array($patient_id));
689 // If there are none, just redisplay the last receipt and exit.
691 if (sqlNumRows($bres) == 0 && sqlNumRows($dres) == 0) {
692 generate_receipt($patient_id);
693 exit();
696 // Get the valid practitioners, including those not active.
697 $arr_users = array();
698 $ures = sqlStatement("SELECT id, username FROM users WHERE " .
699 "( authorized = 1 OR info LIKE '%provider%' ) AND username != ''");
700 while ($urow = sqlFetchArray($ures)) {
701 $arr_users[$urow['id']] = '1';
704 // Now write a data entry form:
705 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
706 // List unbilled product sales for the patient.
707 // Present an editable dollar amount for each line item, a total
708 // which is also the default value of the input payment amount,
709 // and OK and Cancel buttons.
711 <!DOCTYPE html>
712 <head>
713 <?php Header::setupHeader(['datetime-picker']);?>
714 <script language="JavaScript">
715 var mypcc = <?php echo js_escape($GLOBALS['phone_country_code']); ?>;
717 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
719 // This clears the tax line items in preparation for recomputing taxes.
720 function clearTax(visible) {
721 var f = document.forms[0];
722 for (var lino = 0; true; ++lino) {
723 var pfx = 'line[' + lino + ']';
724 if (! f[pfx + '[code_type]']) break;
725 if (f[pfx + '[code_type]'].value != 'TAX') continue;
726 f[pfx + '[price]'].value = '0.00';
727 if (visible) f[pfx + '[amount]'].value = '0.00';
731 // For a given tax ID and amount, compute the tax on that amount and add it
732 // to the "price" (same as "amount") of the corresponding tax line item.
733 // Note the tax line items include their "taxrate" to make this easy.
734 function addTax(rateid, amount, visible) {
735 if (rateid.length == 0) return 0;
736 var f = document.forms[0];
737 for (var lino = 0; true; ++lino) {
738 var pfx = 'line[' + lino + ']';
739 if (! f[pfx + '[code_type]']) break;
740 if (f[pfx + '[code_type]'].value != 'TAX') continue;
741 if (f[pfx + '[code]'].value != rateid) continue;
742 var tax = amount * parseFloat(f[pfx + '[taxrates]'].value);
743 tax = parseFloat(tax.toFixed(<?php echo js_escape($currdecimals); ?>));
744 var cumtax = parseFloat(f[pfx + '[price]'].value) + tax;
745 f[pfx + '[price]'].value = cumtax.toFixed(<?php echo js_escape($currdecimals); ?>); // requires JS 1.5
746 if (visible) f[pfx + '[amount]'].value = cumtax.toFixed(<?php echo js_escape($currdecimals); ?>); // requires JS 1.5
747 if (isNaN(tax)) alert('Tax rate not numeric at line ' + lino);
748 return tax;
750 return 0;
753 // This mess recomputes the invoice total and optionally applies a discount.
754 function computeDiscountedTotals(discount, visible) {
755 clearTax(visible);
756 var f = document.forms[0];
757 var total = 0.00;
758 for (var lino = 0; f['line[' + lino + '][code_type]']; ++lino) {
759 var code_type = f['line[' + lino + '][code_type]'].value;
760 // price is price per unit when the form was originally generated.
761 // By contrast, amount is the dynamically-generated discounted line total.
762 var price = parseFloat(f['line[' + lino + '][price]'].value);
763 if (isNaN(price)) alert('Price not numeric at line ' + lino);
764 if (code_type == 'COPAY' || code_type == 'TAX') {
765 // This works because the tax lines come last.
766 total += parseFloat(price.toFixed(<?php echo js_escape($currdecimals); ?>));
767 continue;
769 var units = f['line[' + lino + '][units]'].value;
770 var amount = price * units;
771 amount = parseFloat(amount.toFixed(<?php echo js_escape($currdecimals); ?>));
772 if (visible) f['line[' + lino + '][amount]'].value = amount.toFixed(<?php echo js_escape($currdecimals); ?>);
773 total += amount;
774 var taxrates = f['line[' + lino + '][taxrates]'].value;
775 var taxids = taxrates.split(':');
776 for (var j = 0; j < taxids.length; ++j) {
777 addTax(taxids[j], amount, visible);
780 return total - discount;
783 // Recompute displayed amounts with any discount applied.
784 function computeTotals() {
785 var f = document.forms[0];
786 var discount = parseFloat(f.form_discount.value);
787 if (isNaN(discount)) discount = 0;
788 <?php if (!$GLOBALS['discount_by_money']) { ?>
789 // This site discounts by percentage, so convert it to a money amount.
790 if (discount > 100) discount = 100;
791 if (discount < 0 ) discount = 0;
792 discount = 0.01 * discount * computeDiscountedTotals(0, false);
793 <?php } ?>
794 var total = computeDiscountedTotals(discount, true);
795 f.form_amount.value = total.toFixed(<?php echo js_escape($currdecimals); ?>);
796 return true;
799 $(function() {
800 $('.datepicker').datetimepicker({
801 <?php $datetimepicker_timepicker = false; ?>
802 <?php $datetimepicker_showseconds = false; ?>
803 <?php $datetimepicker_formatInput = false; ?>
804 <?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?>
805 <?php // can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
809 </script>
810 <style>
811 @media only screen and (max-width: 768px) {
812 [class*="col-"] {
813 width: 100%;
814 text-align:left!Important;
817 .table {
818 margin: auto;
819 width: 90% !important;
821 @media (min-width: 992px){
822 .modal-lg {
823 width: 1000px !Important;
826 </style>
827 <title><?php echo xlt('Patient Checkout'); ?></title>
828 <?php
829 $arrOeUiSettings = array(
830 'heading_title' => xl('Patient Checkout'),
831 'include_patient_name' => true,// use only in appropriate pages
832 'expandable' => false,
833 'expandable_files' => array(),//all file names need suffix _xpd
834 'action' => "",//conceal, reveal, search, reset, link or back
835 'action_title' => "",
836 'action_href' => "",//only for actions - reset, link or back
837 'show_help_icon' => false,
838 'help_file_name' => ""
840 $oemr_ui = new OemrUI($arrOeUiSettings);
842 </head>
843 <body>
844 <div id="container_div" class="<?php echo $oemr_ui->oeContainer();?>">
845 <div class="row">
846 <div class="col-sm-12">
847 <div class="page-header">
848 <?php echo $oemr_ui->pageHeading() . "\r\n"; ?>
849 </div>
850 </div>
851 </div>
852 <div class="row">
853 <div class="col-sm-12">
854 <form action='pos_checkout.php' method='post'>
855 <input type="hidden" name="csrf_token_form" value="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" />
856 <input name='form_pid' type='hidden' value='<?php echo attr($patient_id) ?>'>
857 <fieldset>
858 <legend><?php echo xlt('Item Details'); ?></legend>
859 <div class= "table-responsive">
860 <table class = "table">
861 <tr>
862 <td><b><?php echo xlt('Date'); ?></b></td>
863 <td><b><?php echo xlt('Description'); ?></b></td>
864 <td align='right'><b><?php echo xlt('Qty'); ?></b></td>
865 <td align='right'><b><?php echo xlt('Amount'); ?></b></td>
866 </tr><?php
867 $inv_encounter = '';
868 $inv_date = '';
869 $inv_provider = 0;
870 $inv_payer = 0;
871 $gcac_related_visit = false;
872 $gcac_service_provided = false;
874 // Process billing table items.
875 // Items that are not allowed to have a fee are skipped.
877 while ($brow = sqlFetchArray($bres)) {
878 // Skip all but the most recent encounter.
879 if ($inv_encounter && $brow['encounter'] != $inv_encounter) {
880 continue;
883 $thisdate = substr($brow['date'], 0, 10);
884 $code_type = $brow['code_type'];
886 // Collect tax rates, related code and provider ID.
887 $taxrates = '';
888 $related_code = '';
889 $sqlBindArray = array();
890 if (!empty($code_types[$code_type]['fee'])) {
891 $query = "SELECT taxrates, related_code FROM codes WHERE code_type = ? " .
892 " AND " .
893 "code = ? AND ";
894 array_push($sqlBindArray, $code_types[$code_type]['id'], $brow['code']);
895 if ($brow['modifier']) {
896 $query .= "modifier = ?";
897 array_push($sqlBindArray, $brow['modifier']);
898 } else {
899 $query .= "(modifier IS NULL OR modifier = '')";
901 $query .= " LIMIT 1";
902 $tmp = sqlQuery($query, $sqlBindArray);
903 $taxrates = $tmp['taxrates'];
904 $related_code = $tmp['related_code'];
905 markTaxes($taxrates);
908 write_form_line(
909 $code_type,
910 $brow['code'],
911 $brow['id'],
912 $thisdate,
913 $brow['code_text'],
914 $brow['fee'],
915 $brow['units'],
916 $taxrates
918 if (!$inv_encounter) {
919 $inv_encounter = $brow['encounter'];
921 $inv_payer = $brow['payer_id'];
922 if (!$inv_date || $inv_date < $thisdate) {
923 $inv_date = $thisdate;
926 // Custom logic for IPPF to determine if a GCAC issue applies.
927 if ($GLOBALS['ippf_specific'] && $related_code) {
928 $relcodes = explode(';', $related_code);
929 foreach ($relcodes as $codestring) {
930 if ($codestring === '') {
931 continue;
933 list($codetype, $code) = explode(':', $codestring);
934 if ($codetype !== 'IPPF') {
935 continue;
937 if (preg_match('/^25222/', $code)) {
938 $gcac_related_visit = true;
939 if (preg_match('/^25222[34]/', $code)) {
940 $gcac_service_provided = true;
947 // Process copays
949 $totalCopay = BillingUtilities::getPatientCopay($patient_id, $encounter);
950 if ($totalCopay < 0) {
951 write_form_line("COPAY", "", "", "", "", $totalCopay, "", "");
954 // Process drug sales / products.
956 while ($drow = sqlFetchArray($dres)) {
957 if ($inv_encounter && $drow['encounter'] && $drow['encounter'] != $inv_encounter) {
958 continue;
961 $thisdate = $drow['sale_date'];
962 if (!$inv_encounter) {
963 $inv_encounter = $drow['encounter'];
966 if (!$inv_provider && !empty($arr_users[$drow['provider_id']])) {
967 $inv_provider = $drow['provider_id'] + 0;
970 if (!$inv_date || $inv_date < $thisdate) {
971 $inv_date = $thisdate;
974 // Accumulate taxes for this product.
975 $tmp = sqlQuery("SELECT taxrates FROM drug_templates WHERE drug_id = ? " .
976 " ORDER BY selector LIMIT 1", array($drow['drug_id']));
977 // accumTaxes($drow['fee'], $tmp['taxrates']);
978 $taxrates = $tmp['taxrates'];
979 markTaxes($taxrates);
981 write_form_line(
982 'PROD',
983 $drow['drug_id'],
984 $drow['sale_id'],
985 $thisdate,
986 $drow['name'],
987 $drow['fee'],
988 $drow['quantity'],
989 $taxrates
993 // Write a form line for each tax that has money, adding to $total.
994 foreach ($taxes as $key => $value) {
995 if ($value[2]) {
996 write_form_line('TAX', $key, $key, date('Y-m-d'), $value[0], 0, 1, $value[1]);
1000 // Besides copays, do not collect any other information from ar_activity,
1001 // since this is for appt checkout.
1003 if ($inv_encounter) {
1004 $erow = sqlQuery("SELECT provider_id FROM form_encounter WHERE " .
1005 "pid = ? AND encounter = ? " .
1006 "ORDER BY id DESC LIMIT 1", array($patient_id,$inv_encounter));
1007 $inv_provider = $erow['provider_id'] + 0;
1010 </table>
1011 </div>
1012 </fieldset>
1013 <fieldset>
1014 <legend><?php echo xlt('Collect Payment'); ?></legend>
1015 <div class="col-xs-12 oe-custom-line">
1016 <div class="col-xs-3 col-lg-offset-3">
1017 <label class="control-label" for="form_discount"><?php echo $GLOBALS['discount_by_money'] ? xlt('Discount Amount') : xlt('Discount Percentage'); ?>:</label>
1018 </div>
1019 <div class="col-xs-3">
1020 <input maxlength='8' name='form_discount' id='form_discount' onkeyup='computeTotals()' class= 'form-control' type='text' value=''>
1021 </div>
1022 </div>
1023 <div class="col-xs-12 oe-custom-line">
1024 <div class="col-xs-3 col-lg-offset-3">
1025 <label class="control-label" for="form_method"><?php echo xlt('Payment Method'); ?>:</label>
1026 </div>
1027 <div class="col-xs-3">
1028 <select name='form_method' id='form_method' class='form-control'>
1029 <?php
1030 $query1112 = "SELECT * FROM list_options where list_id=? ORDER BY seq, title ";
1031 $bres1112 = sqlStatement($query1112, array('payment_method'));
1032 while ($brow1112 = sqlFetchArray($bres1112)) {
1033 if ($brow1112['option_id']=='electronic' || $brow1112['option_id']=='bank_draft') {
1034 continue;
1036 echo "<option value='".attr($brow1112['option_id'])."'>".text(xl_list_label($brow1112['title']))."</option>";
1039 </select>
1040 </div>
1041 </div>
1042 <div class="col-xs-12 oe-custom-line">
1043 <div class="col-xs-3 col-lg-offset-3">
1044 <label class="control-label" for="form_source"><?php echo xlt('Check/Reference Number'); ?>:</label>
1045 </div>
1046 <div class="col-xs-3">
1047 <input name='form_source' id='form_source' class= 'form-control' type='text' value=''>
1048 </div>
1049 </div>
1050 <div class="col-xs-12 oe-custom-line">
1051 <div class="col-xs-3 col-lg-offset-3">
1052 <label class="control-label" for="form_amount"><?php echo xlt('Amount Paid'); ?>:</label>
1053 </div>
1054 <div class="col-xs-3">
1055 <input name='form_amount' id='form_amount'class='form-control' type='text' value='0.00'>
1056 </div>
1057 </div>
1058 <div class="col-xs-12 oe-custom-line">
1059 <div class="col-xs-3 col-lg-offset-3">
1060 <label class="control-label" for="form_date"><?php echo xlt('Posting Date'); ?>:</label>
1061 </div>
1062 <div class="col-xs-3">
1063 <input class='form-control datepicker' id='form_date' name='form_date' title='yyyy-mm-dd date of service' type='text' value='<?php echo attr($inv_date) ?>'>
1064 </div>
1065 </div>
1066 <?php
1067 // If this user has a non-empty irnpool assigned, show the pending
1068 // invoice reference number.
1069 $irnumber = BillingUtilities::getInvoiceRefNumber();
1070 if (!empty($irnumber)) {
1072 <div class="col-xs-12 oe-custom-line">
1073 <div class="col-xs-3 col-lg-offset-3">
1074 <label class="control-label" for="form_tentative"><?php echo xlt('Tentative Invoice Ref No'); ?>:</label>
1075 </div>
1076 <div class="col-xs-3">
1077 <div name='form_source' id='form_tentative' id='form_tentative' class= 'form-control'><?php echo text($irnumber); ?></div>
1078 </div>
1079 </div>
1080 <?php
1081 } elseif (!empty($GLOBALS['gbl_mask_invoice_number'])) { // Otherwise if there is an invoice
1082 // reference number mask, ask for the refno.
1084 <div class="col-xs-12 oe-custom-line">
1085 <div class="col-xs-3 col-lg-offset-3">
1086 <label class="control-label" for="form_irnumber"><?php echo xlt('Invoice Reference Number'); ?>:</label>
1087 </div>
1088 <div class="col-xs-3">
1089 <input type='text' name='form_irnumber' id='form_irnumber' class='form-control' value='' onkeyup='maskkeyup(this,<?php echo attr_js($GLOBALS['gbl_mask_invoice_number']); ?>)' onblur='maskblur(this,<?php echo attr_js($GLOBALS['gbl_mask_invoice_number']); ?>)' />
1090 </div>
1091 </div>
1092 <?php
1095 </fieldset>
1096 <div class="form-group">
1097 <div class="col-sm-12 text-left position-override">
1098 <div class="btn-group btn-group-pinch" role="group">
1099 <!--<input type='submit' class="btn btn-default btn-save" name='form_save' id='form_save' value='<?php echo xla('Save'); ?>' />-->
1100 <button type='submit' class="btn btn-default btn-save" name='form_save' id='form_save' ><?php echo xla('Save'); ?></button>
1101 <?php if (empty($_GET['framed'])) { ?>
1102 <button type='button' class="btn btn-link btn-cancel btn-separate-left" onclick='window.close()'><?php echo xlt('Cancel'); ?></button>
1103 <?php } ?>
1104 <input type='hidden' name='form_provider' value='<?php echo attr($inv_provider) ?>' />
1105 <input type='hidden' name='form_payer' value='<?php echo attr($inv_payer) ?>' />
1106 <input type='hidden' name='form_encounter' value='<?php echo attr($inv_encounter) ?>' />
1107 </div>
1108 </div>
1109 </div>
1110 </form>
1111 </div>
1112 </div>
1113 </div><!-- end of div container-->
1114 <?php $oemr_ui->oeBelowContainerDiv();?>
1115 <script language='JavaScript'>
1116 computeTotals();
1117 <?php
1118 if ($gcac_related_visit && !$gcac_service_provided) {
1119 // Skip this warning if the GCAC visit form is not allowed.
1120 $grow = sqlQuery("SELECT COUNT(*) AS count FROM layout_group_properties " .
1121 "WHERE grp_form_id = 'LBFgcac' grp_group_id = '' AND grp_activity = 1");
1122 if (!empty($grow['count'])) { // if gcac is used
1123 // Skip this warning if referral or abortion in TS.
1124 $grow = sqlQuery("SELECT COUNT(*) AS count FROM transactions " .
1125 "WHERE title = 'Referral' AND refer_date IS NOT NULL AND " .
1126 "refer_date = ? AND pid = ?", array($inv_date,$patient_id));
1127 if (empty($grow['count'])) { // if there is no referral
1128 $grow = sqlQuery("SELECT COUNT(*) AS count FROM forms " .
1129 "WHERE pid = ? AND encounter = ? AND " .
1130 "deleted = 0 AND formdir = 'LBFgcac'", array($patient_id,$inv_encounter));
1131 if (empty($grow['count'])) { // if there is no gcac form
1132 echo " alert(" . xlj('This visit will need a GCAC form, referral or procedure service.') . ");\n";
1136 } // end if ($gcac_related_visit)
1138 </script>
1139 </body>
1140 </html>