5 * This module supports a popup window to handle patient checkout
6 * as a point-of-sale transaction. Support for in-house drug sales
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
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,
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
35 * Copyright (C) 2006-2016 Rod Roark <rod@sunsetsystems.com>
36 * Copyright (C) 2017 Brady Miller <brady.g.miller@gmail.com>
38 * LICENSE: This program is free software; you can redistribute it and/or
39 * modify it under the terms of the GNU General Public License
40 * as published by the Free Software Foundation; either version 2
41 * of the License, or (at your option) any later version.
42 * This program is distributed in the hope that it will be useful,
43 * but WITHOUT ANY WARRANTY; without even the implied warranty of
44 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45 * GNU General Public License for more details.
46 * You should have received a copy of the GNU General Public License
47 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
50 * @author Rod Roark <rod@sunsetsystems.com>
51 * @author Brady Miller <brady.g.miller@gmail.com>
52 * @link http://www.open-emr.org
55 require_once("../globals.php");
56 require_once("$srcdir/acl.inc");
57 require_once("$srcdir/patient.inc");
58 require_once("$srcdir/billing.inc");
59 require_once("../../custom/code_types.inc.php");
61 use OpenEMR\Core\Header
;
62 use OpenEMR\Services\FacilityService
;
64 $facilityService = new FacilityService();
66 $currdecimals = $GLOBALS['currency_decimals'];
68 $details = empty($_GET['details']) ?
0 : 1;
70 $patient_id = empty($_GET['ptid']) ?
$pid : 0 +
$_GET['ptid'];
72 // Get the patient's name and chart number.
73 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
75 // Output HTML for an invoice line item.
78 function receiptDetailLine($svcdate, $description, $amount, $quantity)
80 global $prevsvcdate, $details;
84 $amount = sprintf('%01.2f', $amount);
85 if (empty($quantity)) {
88 $price = sprintf('%01.4f', $amount / $quantity);
89 $tmp = sprintf('%01.2f', $price);
94 echo " <td>" . ($svcdate == $prevsvcdate ?
' ' : text(oeFormatShortDate($svcdate))) . "</td>\n";
95 echo " <td>" . text($description) . "</td>\n";
96 echo " <td align='right'>" . text(oeFormatMoney($price)) . "</td>\n";
97 echo " <td align='right'>" . text($quantity) . "</td>\n";
98 echo " <td align='right'>" . text(oeFormatMoney($amount)) . "</td>\n";
100 $prevsvcdate = $svcdate;
103 // Output HTML for an invoice payment.
105 function receiptPaymentLine($paydate, $amount, $description = '')
107 $amount = sprintf('%01.2f', 0 - $amount); // make it negative
109 echo " <td>" . text(oeFormatShortDate($paydate)) . "</td>\n";
110 echo " <td>" . xlt('Payment') . " " . text($description) . "</td>\n";
111 echo " <td colspan='2'> </td>\n";
112 echo " <td align='right'>" . text(oeFormatMoney($amount)) . "</td>\n";
116 // Generate a receipt from the last-billed invoice for this patient,
117 // or for the encounter specified as a GET parameter.
119 function generate_receipt($patient_id, $encounter = 0)
121 //REMEMBER the entire receipt is generated here, have to echo DOC type etc and closing tags to create a valid webpsge
122 global $sl_err, $sl_cash_acc, $css_header, $details, $facilityService;
124 // Get details for what we guess is the primary facility.
125 $frow = $facilityService->getPrimaryBusinessEntity(array("useLegacyImplementation" => true));
127 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code,providerID');
129 // Get the most recent invoice data or that for the specified encounter.
131 // Adding a provider check so that their info can be displayed on receipts
133 $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " .
134 "WHERE pid = ? AND encounter = ?", array($patient_id,$encounter));
136 $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " .
138 "ORDER BY id DESC LIMIT 1", array($patient_id));
141 die(xlt("This patient has no activity."));
143 $trans_id = $ferow['id'];
144 $encounter = $ferow['encounter'];
145 $svcdate = substr($ferow['date'], 0, 10);
147 if ($GLOBALS['receipts_by_provider']) {
148 if (isset($ferow['provider_id'])) {
149 $encprovider = $ferow['provider_id'];
150 } elseif (isset($patdata['providerID'])) {
151 $encprovider = $patdata['providerID'];
158 $providerrow = sqlQuery("SELECT fname, mname, lname, title, street, streetb, " .
159 "city, state, zip, phone, fax FROM users WHERE id = ?", array($encprovider));
162 // Get invoice reference number.
163 $encrow = sqlQuery("SELECT invoice_refno FROM form_encounter WHERE " .
164 "pid = ? AND encounter = ? LIMIT 1", array($patient_id,$encounter));
165 $invoice_refno = $encrow['invoice_refno'];
167 // being deliberately echoed to indicate it is part of the php function generate_receipt
168 echo "<!DOCTYPE html>". PHP_EOL
;
169 echo "<html>".PHP_EOL
;
170 echo"<head>".PHP_EOL
;
173 <?php Header
::setupHeader(['datetime-picker']);?
>
174 <title
><?php
echo xlt('Receipt for Payment'); ?
></title
>
175 <script language
="JavaScript">
177 <?php
require($GLOBALS['srcdir'] . "/restoreSession.php"); ?
>
179 $
(document
).ready(function() {
180 var win
= top
.printLogSetup ? top
: opener
.top
;
181 win
.printLogSetup(document
.getElementById('printbutton'));
184 // Process click on Print button.
185 function printlog_before_print() {
186 var divstyle
= document
.getElementById('hideonprint').style
;
187 divstyle
.display
= 'none';
190 // Process click on Delete button.
191 function deleteme() {
192 dlgopen('deleter.php?billing=<?php echo attr("$patient_id.$encounter"); ?>', '_blank', 500, 450);
196 // Called by the deleteme.php window on a successful delete.
197 function imdeleted() {
203 @media only screen
and (max
-width
: 768px
) {
206 text
-align
:left
!Important
;
211 width
: 90%
!important
;
213 @media
(min
-width
: 992px
){
215 width
: 1000px
!Important
;
219 <title
><?php
echo xlt('Patient Checkout'); ?
></title
>
221 <body
class="body_top">
222 <div
class="container">
223 <div
class= "row text-center">
225 if ($GLOBALS['receipts_by_provider'] && !empty($providerrow)) {
226 printProviderHeader($providerrow);
228 printFacilityHeader($frow);
231 echo xlt("Receipt Generated") . ":" . text(date(' F j, Y'));
232 if ($invoice_refno) {
233 echo " " . xlt("Invoice Number") . ": " . text($invoice_refno) . " " . xlt("Service Date") . ": " . text($svcdate);
238 <div
class= 'col-xs-6 col-lg-offset-2'>
239 <?php
echo text($patdata['fname']) . ' ' . text($patdata['mname']) . ' ' . text($patdata['lname']) ?
><br
>
240 <?php
echo text($patdata['street']) ?
><br
>
241 <?php
echo text($patdata['city']) . ', ' . text($patdata['state']) . ' ' . text($patdata['postal_code']) ?
><br
>
245 <div
class= 'col-xs-6 col-lg-offset-3'>
246 <table
class="table">
249 <th
><strong
><?php
echo xlt('Date'); ?
></strong
></th
>
250 <th
><strong
><?php
echo xlt('Description'); ?
></strong
></th
>
251 <th
class='text-right'><strong
><?php
echo $details ?
xlt('Price') : ' '; ?
></strong
></th
>
252 <th
class='text-right'><strong
><?php
echo $details ?
xlt('Qty') : ' '; ?
></strong
></th
>
253 <th
class='text-right'><strong
><?php
echo xlt('Total'); ?
></strong
></th
>
260 $inres = sqlStatement("SELECT s.sale_id, s.sale_date, s.fee, " .
261 "s.quantity, s.drug_id, d.name " .
262 "FROM drug_sales AS s LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
263 // "WHERE s.pid = '$patient_id' AND s.encounter = '$encounter' AND s.fee != 0 " .
264 "WHERE s.pid = ? AND s.encounter = ? " .
265 "ORDER BY s.sale_id", array($patient_id,$encounter));
266 while ($inrow = sqlFetchArray($inres)) {
267 $charges +
= sprintf('%01.2f', $inrow['fee']);
275 // Service and tax items
276 $inres = sqlStatement("SELECT * FROM billing WHERE " .
277 "pid = ? AND encounter = ? AND " .
278 // "code_type != 'COPAY' AND activity = 1 AND fee != 0 " .
279 "code_type != 'COPAY' AND activity = 1 " .
280 "ORDER BY id", array($patient_id,$encounter));
281 while ($inrow = sqlFetchArray($inres)) {
282 $charges +
= sprintf('%01.2f', $inrow['fee']);
291 $inres = sqlStatement("SELECT " .
292 "a.code_type, a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
293 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
294 "FROM ar_activity AS a " .
295 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
296 "a.pid = ? AND a.encounter = ? AND " .
297 "a.adj_amount != 0 " .
298 "ORDER BY s.check_date, a.sequence_no", array($patient_id,$encounter));
299 while ($inrow = sqlFetchArray($inres)) {
300 $charges -= sprintf('%01.2f', $inrow['adj_amount']);
301 $payer = empty($inrow['payer_type']) ?
'Pt' : ('Ins' . $inrow['payer_type']);
304 $payer . ' ' . $inrow['memo'],
305 0 - $inrow['adj_amount'],
311 <td colspan
='5'> 
;</td
>
314 <td
><?php
echo text(oeFormatShortDate($svcdispdate)); ?
></td
>
315 <td
><b
><?php
echo xlt('Total Charges'); ?
></b
></td
>
316 <td
class='text-right'> 
;</td
>
317 <td
class='text-right'> 
;</td
>
318 <td
class='text-right'><?php
echo text(oeFormatMoney($charges, true)) ?
></td
>
321 <td colspan
='5'> 
;</td
>
325 $inres = sqlStatement("SELECT fee, code_text FROM billing WHERE " .
326 "pid = ? AND encounter = ? AND " .
327 "code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
328 "ORDER BY id", array($patient_id,$encounter));
329 while ($inrow = sqlFetchArray($inres)) {
330 $charges +
= sprintf('%01.2f', $inrow['fee']);
331 receiptPaymentLine($svcdate, 0 - $inrow['fee'], $inrow['code_text']);
333 // Get other payments.
334 $inres = sqlStatement("SELECT " .
335 "a.code_type, a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
336 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
337 "FROM ar_activity AS a " .
338 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
339 "a.pid = ? AND a.encounter = ? AND " .
340 "a.pay_amount != 0 " .
341 "ORDER BY s.check_date, a.sequence_no", array($patient_id,$encounter));
342 while ($inrow = sqlFetchArray($inres)) {
343 $payer = empty($inrow['payer_type']) ?
'Pt' : ('Ins' . $inrow['payer_type']);
344 $charges -= sprintf('%01.2f', $inrow['pay_amount']);
347 $inrow['pay_amount'],
348 $payer . ' ' . $inrow['reference']
353 <td colspan
='5'> 
;</td
>
357 <td
><b
><?php
echo xlt('Balance Due'); ?
></b
></td
>
358 <td colspan
='2'> 
;</td
>
359 <td
class='text-right'><?php
echo text(oeFormatMoney($charges, true)) ?
></td
>
366 <div
class="form-group clearfix">
367 <div
class="col-sm-12 text-center" id
="hideonprint">
368 <div
class="btn-group" role
="group">
369 <button
class="btn btn-default btn-print" id
='printbutton'><?php
echo xlt('Print'); ?
></button
>
370 <?php
if (acl_check('acct', 'disc')) { ?
>
371 <button
class="btn btn-default btn-undo" onclick
='return deleteme();'><?php
echo xlt('Undo Checkout'); ?
></button
>
373 <?php
if ($details) { ?
>
374 <button
class="btn btn-default btn-hide" onclick
="top.restoreSession(); window.location.href = 'pos_checkout.php?details=0&ptid=<?php echo attr($patient_id); ?>&enc=<?php echo attr($encounter); ?>'"><?php
echo xlt('Hide Details'); ?
></button
>
376 <button
class="btn btn-default btn-show" onclick
="top.restoreSession(); window.location.href = 'pos_checkout.php?details=1&ptid=<?php echo attr($patient_id); ?>&enc=<?php echo attr($encounter); ?>'"><?php
echo xlt('Show Details'); ?
></button
>
382 </div
><!--end of receipt container div
-->
383 <?php
// echoing the closing tags for receipts
384 echo"</body>".PHP_EOL
;
385 echo "</html>".PHP_EOL
;
386 } // end function generate_receipt()
391 // Function to output a line item for the input form.
394 function write_form_line(
405 $amount = sprintf("%01.2f", $amount);
409 $price = $amount / $units; // should be even cents, but ok here if not
410 if ($code_type == 'COPAY' && !$description) {
411 $description = xl('Payment');
414 echo " <td>" . text(oeFormatShortDate($date));
415 echo "<input type='hidden' name='line[$lino][code_type]' value='" . attr($code_type) . "'>";
416 echo "<input type='hidden' name='line[$lino][code]' value='" . attr($code) . "'>";
417 echo "<input type='hidden' name='line[$lino][id]' value='" . attr($id) . "'>";
418 echo "<input type='hidden' name='line[$lino][description]' value='" . attr($description) . "'>";
419 echo "<input type='hidden' name='line[$lino][taxrates]' value='" . attr($taxrates) . "'>";
420 echo "<input type='hidden' name='line[$lino][price]' value='" . attr($price) . "'>";
421 echo "<input type='hidden' name='line[$lino][units]' value='" . attr($units) . "'>";
423 echo " <td>" . text($description) . "</td>";
424 echo " <td align='right'>" . text($units) . "</td>";
425 echo " <td align='right'><input type='text' name='line[$lino][amount]' " .
426 "value='" . attr($amount) . "' size='6' maxlength='8'";
427 // Modifying prices requires the acct/disc permission.
428 // if ($code_type == 'TAX' || ($code_type != 'COPAY' && !acl_check('acct','disc')))
429 echo " style='text-align:right;background-color:transparent' readonly";
430 // else echo " style='text-align:right' onkeyup='computeTotals()'";
436 // Create the taxes array. Key is tax id, value is
437 // (description, rate, accumulated total).
439 $pres = sqlStatement("SELECT option_id, title, option_value " .
440 "FROM list_options WHERE list_id = 'taxrate' AND activity = 1 ORDER BY seq, title, option_id");
441 while ($prow = sqlFetchArray($pres)) {
442 $taxes[$prow['option_id']] = array($prow['title'], $prow['option_value'], 0);
445 // Print receipt header for facility
446 function printFacilityHeader($frow)
448 echo "<p><b>" . text($frow['name']) .
449 "<br>" . text($frow['street']) .
450 "<br>" . text($frow['city']) . ', ' . text($frow['state']) . ' ' . text($frow['postal_code']) .
451 "<br>" . text($frow['phone']) .
456 // Pring receipt header for Provider
457 function printProviderHeader($pvdrow)
459 echo "<p><b>" . text($pvdrow['title']) . " " . text($pvdrow['fname']) . " " . text($pvdrow['mname']) . " " . text($pvdrow['lname']) . " " .
460 "<br>" . text($pvdrow['street']) .
461 "<br>" . text($pvdrow['city']) . ', ' . text($pvdrow['state']) . ' ' . text($pvdrow['postal_code']) .
462 "<br>" . text($pvdrow['phone']) .
467 // Mark the tax rates that are referenced in this invoice.
468 function markTaxes($taxrates)
471 $arates = explode(':', $taxrates);
472 if (empty($arates)) {
475 foreach ($arates as $value) {
476 if (!empty($taxes[$value])) {
477 $taxes[$value][2] = '1';
482 $payment_methods = array(
491 $alertmsg = ''; // anything here pops up in an alert box
493 // If the Save button was clicked...
495 if ($_POST['form_save']) {
496 // On a save, do the following:
497 // Flag drug_sales and billing items as billed.
498 // Post the corresponding invoice with its payment(s) to sql-ledger
499 // and be careful to use a unique invoice number.
500 // Call the generate-receipt function.
503 $form_pid = $_POST['form_pid'];
504 $form_encounter = $_POST['form_encounter'];
506 // Get the posting date from the form as yyyy-mm-dd.
507 $dosdate = date("Y-m-d");
508 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $_POST['form_date'], $matches)) {
509 $dosdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
512 // If there is no associated encounter (i.e. this invoice has only
513 // prescriptions) then assign an encounter number of the service
514 // date, with an optional suffix to ensure that it's unique.
516 if (! $form_encounter) {
517 $form_encounter = substr($dosdate, 0, 4) . substr($dosdate, 5, 2) . substr($dosdate, 8, 2);
520 $ferow = sqlQuery("SELECT id FROM form_encounter WHERE " .
521 "pid = ? AND encounter = ?", array($form_pid, $form_encounter.$tmp));
525 $tmp = $tmp ?
$tmp +
1 : 1;
527 $form_encounter .= $tmp;
530 // Delete any TAX rows from billing because they will be recalculated.
531 sqlStatement("UPDATE billing SET activity = 0 WHERE " .
532 "pid = ? AND encounter = ? AND " .
533 "code_type = 'TAX'", array($form_pid,$form_encounter));
535 $form_amount = $_POST['form_amount'];
536 $lines = $_POST['line'];
538 for ($lino = 0; $lines[$lino]['code_type']; ++
$lino) {
539 $line = $lines[$lino];
540 $code_type = $line['code_type'];
542 $amount = sprintf('%01.2f', trim($line['amount']));
545 if ($code_type == 'PROD') {
546 // Product sales. The fee and encounter ID may have changed.
547 $query = "update drug_sales SET fee = ?, " .
548 "encounter = ?, billed = 1 WHERE " .
550 sqlQuery($query, array($amount,$form_encounter,$id));
551 } elseif ($code_type == 'TAX') {
552 // In the SL case taxes show up on the invoice as line items.
553 // Otherwise we gotta save them somewhere, and in the billing
554 // table with a code type of TAX seems easiest.
555 // They will have to be stripped back out when building this
556 // script's input form.
573 // Because there is no insurance here, there is no need for a claims
574 // table entry and so we do not call updateClaim(). Note we should not
575 // eliminate billed and bill_date from the billing table!
576 $query = "UPDATE billing SET fee = ?, billed = 1, " .
577 "bill_date = NOW() WHERE id = ?";
578 sqlQuery($query, array($amount,$id));
583 if ($_POST['form_discount']) {
584 if ($GLOBALS['discount_by_money']) {
585 $amount = sprintf('%01.2f', trim($_POST['form_discount']));
587 $amount = sprintf('%01.2f', trim($_POST['form_discount']) * $form_amount / 100);
589 $memo = xl('Discount');
590 $time = date('Y-m-d H:i:s');
592 $sequence_no = sqlQuery("SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array($form_pid, $form_encounter));
593 $query = "INSERT INTO ar_activity ( " .
594 "pid, encounter, sequence_no, code, modifier, payer_type, post_user, post_time, " .
595 "session_id, memo, adj_amount " .
609 sqlStatement($query, array($form_pid,$form_encounter,$sequence_no['increment'],$_SESSION['authUserID'],$time,$memo,$amount));
614 if ($_POST['form_amount']) {
615 $amount = sprintf('%01.2f', trim($_POST['form_amount']));
616 $form_source = trim($_POST['form_source']);
617 $paydesc = trim($_POST['form_method']);
618 //Fetching the existing code and modifier
619 $ResultSearchNew = sqlStatement(
620 "SELECT * FROM billing LEFT JOIN code_types ON billing.code_type=code_types.ct_key ".
621 "WHERE code_types.ct_fee=1 AND billing.activity!=0 AND billing.pid =? AND encounter=? ORDER BY billing.code,billing.modifier",
622 array($form_pid,$form_encounter)
624 if ($RowSearch = sqlFetchArray($ResultSearchNew)) {
625 $Codetype=$RowSearch['code_type'];
626 $Code=$RowSearch['code'];
627 $Modifier=$RowSearch['modifier'];
633 $session_id=sqlInsert(
634 "INSERT INTO ar_session (payer_id,user_id,reference,check_date,deposit_date,pay_total,".
635 " global_amount,payment_type,description,patient_id,payment_method,adjustment_code,post_to_date) ".
636 " VALUES ('0',?,?,now(),?,?,'','patient','COPAY',?,?,'patient_payment',now())",
637 array($_SESSION['authId'],$form_source,$dosdate,$amount,$form_pid,$paydesc)
641 $sequence_no = sqlQuery("SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array($form_pid, $form_encounter));
643 "INSERT INTO ar_activity (pid,encounter,sequence_no,code_type,code,modifier,payer_type,post_time,post_user,session_id,pay_amount,account_code)".
644 " VALUES (?,?,?,?,?,?,0,?,?,?,?,'PCP')",
645 array($form_pid,$form_encounter,$sequence_no['increment'],$Codetype,$Code,$Modifier,$dosdate,$_SESSION['authId'],$session_id,$amount)
650 // If applicable, set the invoice reference number.
652 if (isset($_POST['form_irnumber'])) {
653 $invoice_refno = trim($_POST['form_irnumber']);
655 $invoice_refno = updateInvoiceRefNumber();
657 if ($invoice_refno) {
658 sqlStatement("UPDATE form_encounter " .
659 "SET invoice_refno = ? " .
660 "WHERE pid = ? AND encounter = ?", array($invoice_refno,$form_pid,$form_encounter));
663 generate_receipt($form_pid, $form_encounter);
667 // If an encounter ID was given, then we must generate a receipt.
669 if (!empty($_GET['enc'])) {
670 generate_receipt($patient_id, $_GET['enc']);
674 // Get the unbilled billing table items for this patient.
675 $query = "SELECT id, date, code_type, code, modifier, code_text, " .
676 "provider_id, payer_id, units, fee, encounter " .
677 "FROM billing WHERE pid = ? AND activity = 1 AND " .
678 "billed = 0 AND code_type != 'TAX' " .
679 "ORDER BY encounter DESC, id ASC";
680 $bres = sqlStatement($query, array($patient_id));
682 // Get the product sales for this patient.
683 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, " .
684 "s.quantity, s.encounter, s.drug_id, d.name, r.provider_id " .
685 "FROM drug_sales AS s " .
686 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
687 "LEFT OUTER JOIN prescriptions AS r ON r.id = s.prescription_id " .
688 "WHERE s.pid = ? AND s.billed = 0 " .
689 "ORDER BY s.encounter DESC, s.sale_id ASC";
690 $dres = sqlStatement($query, array($patient_id));
692 // If there are none, just redisplay the last receipt and exit.
694 if (sqlNumRows($bres) == 0 && sqlNumRows($dres) == 0) {
695 generate_receipt($patient_id);
699 // Get the valid practitioners, including those not active.
700 $arr_users = array();
701 $ures = sqlStatement("SELECT id, username FROM users WHERE " .
702 "( authorized = 1 OR info LIKE '%provider%' ) AND username != ''");
703 while ($urow = sqlFetchArray($ures)) {
704 $arr_users[$urow['id']] = '1';
707 // Now write a data entry form:
708 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
709 // List unbilled product sales for the patient.
710 // Present an editable dollar amount for each line item, a total
711 // which is also the default value of the input payment amount,
712 // and OK and Cancel buttons.
716 <?php Header
::setupHeader(['datetime-picker']);?
>
717 <script language
="JavaScript">
718 var mypcc
= '<?php echo $GLOBALS['phone_country_code
'] ?>';
720 <?php
require($GLOBALS['srcdir'] . "/restoreSession.php"); ?
>
722 // This clears the tax line items in preparation for recomputing taxes.
723 function clearTax(visible
) {
724 var f
= document
.forms
[0];
725 for (var lino
= 0; true; ++lino
) {
726 var pfx
= 'line[' + lino +
']';
727 if (! f
[pfx +
'[code_type]']) break;
728 if (f
[pfx +
'[code_type]'].value
!= 'TAX') continue;
729 f
[pfx +
'[price]'].value
= '0.00';
730 if (visible
) f
[pfx +
'[amount]'].value
= '0.00';
734 // For a given tax ID and amount, compute the tax on that amount and add it
735 // to the "price" (same as "amount") of the corresponding tax line item.
736 // Note the tax line items include their "taxrate" to make this easy.
737 function addTax(rateid
, amount
, visible
) {
738 if (rateid
.length
== 0) return 0;
739 var f
= document
.forms
[0];
740 for (var lino
= 0; true; ++lino
) {
741 var pfx
= 'line[' + lino +
']';
742 if (! f
[pfx +
'[code_type]']) break;
743 if (f
[pfx +
'[code_type]'].value
!= 'TAX') continue;
744 if (f
[pfx +
'[code]'].value
!= rateid
) continue;
745 var tax
= amount
* parseFloat(f
[pfx +
'[taxrates]'].value
);
746 tax
= parseFloat(tax
.toFixed(<?php
echo $currdecimals ?
>));
747 var cumtax
= parseFloat(f
[pfx +
'[price]'].value
) + tax
;
748 f
[pfx +
'[price]'].value
= cumtax
.toFixed(<?php
echo $currdecimals ?
>); // requires JS 1.5
749 if (visible
) f
[pfx +
'[amount]'].value
= cumtax
.toFixed(<?php
echo $currdecimals ?
>); // requires JS 1.5
750 if (isNaN(tax
)) alert('Tax rate not numeric at line ' + lino
);
756 // This mess recomputes the invoice total and optionally applies a discount.
757 function computeDiscountedTotals(discount
, visible
) {
759 var f
= document
.forms
[0];
761 for (var lino
= 0; f
['line[' + lino +
'][code_type]']; ++lino
) {
762 var code_type
= f
['line[' + lino +
'][code_type]'].value
;
763 // price is price per unit when the form was originally generated.
764 // By contrast, amount is the dynamically-generated discounted line total.
765 var price
= parseFloat(f
['line[' + lino +
'][price]'].value
);
766 if (isNaN(price
)) alert('Price not numeric at line ' + lino
);
767 if (code_type
== 'COPAY' || code_type
== 'TAX') {
768 // This works because the tax lines come last.
769 total +
= parseFloat(price
.toFixed(<?php
echo $currdecimals ?
>));
772 var units
= f
['line[' + lino +
'][units]'].value
;
773 var amount
= price
* units
;
774 amount
= parseFloat(amount
.toFixed(<?php
echo $currdecimals ?
>));
775 if (visible
) f
['line[' + lino +
'][amount]'].value
= amount
.toFixed(<?php
echo $currdecimals ?
>);
777 var taxrates
= f
['line[' + lino +
'][taxrates]'].value
;
778 var taxids
= taxrates
.split(':');
779 for (var j
= 0; j
< taxids
.length
; ++j
) {
780 addTax(taxids
[j
], amount
, visible
);
783 return total
- discount
;
786 // Recompute displayed amounts with any discount applied.
787 function computeTotals() {
788 var f
= document
.forms
[0];
789 var discount
= parseFloat(f
.form_discount
.value
);
790 if (isNaN(discount
)) discount
= 0;
791 <?php
if (!$GLOBALS['discount_by_money']) { ?
>
792 // This site discounts by percentage, so convert it to a money amount.
793 if (discount
> 100) discount
= 100;
794 if (discount
< 0 ) discount
= 0;
795 discount
= 0.01 * discount
* computeDiscountedTotals(0, false);
797 var total
= computeDiscountedTotals(discount
, true);
798 f
.form_amount
.value
= total
.toFixed(<?php
echo $currdecimals ?
>);
802 $
(document
).ready(function() {
803 $
('.datepicker').datetimepicker({
804 <?php
$datetimepicker_timepicker = false; ?
>
805 <?php
$datetimepicker_showseconds = false; ?
>
806 <?php
$datetimepicker_formatInput = false; ?
>
807 <?php
require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?
>
808 <?php
// can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
814 @media only screen
and (max
-width
: 768px
) {
817 text
-align
:left
!Important
;
822 width
: 90%
!important
;
824 @media
(min
-width
: 992px
){
826 width
: 1000px
!Important
;
830 <title
><?php
echo xlt('Patient Checkout'); ?
></title
>
833 <div
class="container">
835 <div
class="page-header">
836 <h2
><?php
echo xlt('Patient Checkout for '); ?
><?php
echo text($patdata['fname']) . " " .
837 text($patdata['lname']) . " (" . text($patdata['pubpid']) . ")" ?
></h2
>
841 <form action
='pos_checkout.php' method
='post'>
842 <input name
='form_pid' type
='hidden' value
='<?php echo attr($patient_id) ?>'>
844 <legend
><?php
echo xlt('Item Details'); ?
></legend
>
845 <div
class= "table-responsive">
846 <table
class = "table">
848 <td
><b
><?php
echo xlt('Date'); ?
></b
></td
>
849 <td
><b
><?php
echo xlt('Description'); ?
></b
></td
>
850 <td align
='right'><b
><?php
echo xlt('Qty'); ?
></b
></td
>
851 <td align
='right'><b
><?php
echo xlt('Amount'); ?
></b
></td
>
857 $gcac_related_visit = false;
858 $gcac_service_provided = false;
860 // Process billing table items.
861 // Items that are not allowed to have a fee are skipped.
863 while ($brow = sqlFetchArray($bres)) {
864 // Skip all but the most recent encounter.
865 if ($inv_encounter && $brow['encounter'] != $inv_encounter) {
869 $thisdate = substr($brow['date'], 0, 10);
870 $code_type = $brow['code_type'];
872 // Collect tax rates, related code and provider ID.
875 $sqlBindArray = array();
876 if (!empty($code_types[$code_type]['fee'])) {
877 $query = "SELECT taxrates, related_code FROM codes WHERE code_type = ? " .
880 array_push($sqlBindArray, $code_types[$code_type]['id'], $brow['code']);
881 if ($brow['modifier']) {
882 $query .= "modifier = ?";
883 array_push($sqlBindArray, $brow['modifier']);
885 $query .= "(modifier IS NULL OR modifier = '')";
887 $query .= " LIMIT 1";
888 $tmp = sqlQuery($query, $sqlBindArray);
889 $taxrates = $tmp['taxrates'];
890 $related_code = $tmp['related_code'];
891 markTaxes($taxrates);
904 if (!$inv_encounter) {
905 $inv_encounter = $brow['encounter'];
907 $inv_payer = $brow['payer_id'];
908 if (!$inv_date ||
$inv_date < $thisdate) {
909 $inv_date = $thisdate;
912 // Custom logic for IPPF to determine if a GCAC issue applies.
913 if ($GLOBALS['ippf_specific'] && $related_code) {
914 $relcodes = explode(';', $related_code);
915 foreach ($relcodes as $codestring) {
916 if ($codestring === '') {
919 list($codetype, $code) = explode(':', $codestring);
920 if ($codetype !== 'IPPF') {
923 if (preg_match('/^25222/', $code)) {
924 $gcac_related_visit = true;
925 if (preg_match('/^25222[34]/', $code)) {
926 $gcac_service_provided = true;
935 $totalCopay = getPatientCopay($patient_id, $encounter);
936 if ($totalCopay < 0) {
937 write_form_line("COPAY", "", "", "", "", $totalCopay, "", "");
940 // Process drug sales / products.
942 while ($drow = sqlFetchArray($dres)) {
943 if ($inv_encounter && $drow['encounter'] && $drow['encounter'] != $inv_encounter) {
947 $thisdate = $drow['sale_date'];
948 if (!$inv_encounter) {
949 $inv_encounter = $drow['encounter'];
952 if (!$inv_provider && !empty($arr_users[$drow['provider_id']])) {
953 $inv_provider = $drow['provider_id'] +
0;
956 if (!$inv_date ||
$inv_date < $thisdate) {
957 $inv_date = $thisdate;
960 // Accumulate taxes for this product.
961 $tmp = sqlQuery("SELECT taxrates FROM drug_templates WHERE drug_id = ? " .
962 " ORDER BY selector LIMIT 1", array($drow['drug_id']));
963 // accumTaxes($drow['fee'], $tmp['taxrates']);
964 $taxrates = $tmp['taxrates'];
965 markTaxes($taxrates);
979 // Write a form line for each tax that has money, adding to $total.
980 foreach ($taxes as $key => $value) {
982 write_form_line('TAX', $key, $key, date('Y-m-d'), $value[0], 0, 1, $value[1]);
986 // Besides copays, do not collect any other information from ar_activity,
987 // since this is for appt checkout.
989 if ($inv_encounter) {
990 $erow = sqlQuery("SELECT provider_id FROM form_encounter WHERE " .
991 "pid = ? AND encounter = ? " .
992 "ORDER BY id DESC LIMIT 1", array($patient_id,$inv_encounter));
993 $inv_provider = $erow['provider_id'] +
0;
1000 <legend
><?php
echo xlt('Collect Payment'); ?
></legend
>
1001 <div
class="col-xs-12 oe-custom-line">
1002 <div
class="col-xs-3 col-lg-offset-3">
1003 <label
class="control-label" for="form_discount"><?php
echo $GLOBALS['discount_by_money'] ?
xlt('Discount Amount') : xlt('Discount Percentage'); ?
>:</label
>
1005 <div
class="col-xs-3">
1006 <input maxlength
='8' name
='form_discount' id
='form_discount' onkeyup
='computeTotals()' class= 'form-control' type
='text' value
=''>
1009 <div
class="col-xs-12 oe-custom-line">
1010 <div
class="col-xs-3 col-lg-offset-3">
1011 <label
class="control-label" for="form_method"><?php
echo xlt('Payment Method'); ?
>:</label
>
1013 <div
class="col-xs-3">
1014 <select name
='form_method' id
='form_method' class='form-control'>
1016 $query1112 = "SELECT * FROM list_options where list_id=? ORDER BY seq, title ";
1017 $bres1112 = sqlStatement($query1112, array('payment_method'));
1018 while ($brow1112 = sqlFetchArray($bres1112)) {
1019 if ($brow1112['option_id']=='electronic' ||
$brow1112['option_id']=='bank_draft') {
1022 echo "<option value='".attr($brow1112['option_id'])."'>".text(xl_list_label($brow1112['title']))."</option>";
1028 <div
class="col-xs-12 oe-custom-line">
1029 <div
class="col-xs-3 col-lg-offset-3">
1030 <label
class="control-label" for="form_source"><?php
echo xlt('Check/Reference Number'); ?
>:</label
>
1032 <div
class="col-xs-3">
1033 <input name
='form_source' id
='form_source' class= 'form-control' type
='text' value
=''>
1036 <div
class="col-xs-12 oe-custom-line">
1037 <div
class="col-xs-3 col-lg-offset-3">
1038 <label
class="control-label" for="form_amount"><?php
echo xlt('Amount Paid'); ?
>:</label
>
1040 <div
class="col-xs-3">
1041 <input name
='form_amount' id
='form_amount'class='form-control' type
='text' value
='0.00'>
1044 <div
class="col-xs-12 oe-custom-line">
1045 <div
class="col-xs-3 col-lg-offset-3">
1046 <label
class="control-label" for="form_date"><?php
echo xlt('Posting Date'); ?
>:</label
>
1048 <div
class="col-xs-3">
1049 <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) ?>'>
1053 // If this user has a non-empty irnpool assigned, show the pending
1054 // invoice reference number.
1055 $irnumber = getInvoiceRefNumber();
1056 if (!empty($irnumber)) {
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_tentative"><?php
echo xlt('Tentative Invoice Ref No'); ?
>:</label
>
1062 <div
class="col-xs-3">
1063 <div name
='form_source' id
='form_tentative' id
='form_tentative' class= 'form-control'><?php
echo text($irnumber); ?
></div
>
1067 } // Otherwise if there is an invoice reference number mask, ask for the refno.
1068 elseif (!empty($GLOBALS['gbl_mask_invoice_number'])) {
1070 <div
class="col-xs-12 oe-custom-line">
1071 <div
class="col-xs-3 col-lg-offset-3">
1072 <label
class="control-label" for="form_irnumber"><?php
echo xlt('Invoice Reference Number'); ?
>:</label
>
1074 <div
class="col-xs-3">
1075 <input type
='text' name
='form_irnumber' id
='form_irnumber' class='form-control' value
='' onkeyup
='maskkeyup(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number
']); ?>")' onblur
='maskblur(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number
']); ?>")' />
1082 <div
class="form-group">
1083 <div
class="col-sm-12 text-left position-override">
1084 <div
class="btn-group btn-group-pinch" role
="group">
1085 <!--<input type
='submit' class="btn btn-default btn-save" name
='form_save' id
='form_save' value
='<?php echo xla('Save
'); ?>' />-->
1086 <button type
='submit' class="btn btn-default btn-save" name
='form_save' id
='form_save' ><?php
echo xla('Save'); ?
></button
>
1087 <?php
if (empty($_GET['framed'])) { ?
>
1088 <button type
='button' class="btn btn-link btn-cancel btn-separate-left" onclick
='window.close()'><?php
echo xlt('Cancel'); ?
></button
>
1090 <input type
='hidden' name
='form_provider' value
='<?php echo attr($inv_provider) ?>' />
1091 <input type
='hidden' name
='form_payer' value
='<?php echo attr($inv_payer) ?>' />
1092 <input type
='hidden' name
='form_encounter' value
='<?php echo attr($inv_encounter) ?>' />
1098 <script language
='JavaScript'>
1101 if ($gcac_related_visit && !$gcac_service_provided) {
1102 // Skip this warning if the GCAC visit form is not allowed.
1103 $grow = sqlQuery("SELECT COUNT(*) AS count FROM layout_group_properties " .
1104 "WHERE grp_form_id = 'LBFgcac' grp_group_id = '' AND grp_activity = 1");
1105 if (!empty($grow['count'])) { // if gcac is used
1106 // Skip this warning if referral or abortion in TS.
1107 $grow = sqlQuery("SELECT COUNT(*) AS count FROM transactions " .
1108 "WHERE title = 'Referral' AND refer_date IS NOT NULL AND " .
1109 "refer_date = ? AND pid = ?", array($inv_date,$patient_id));
1110 if (empty($grow['count'])) { // if there is no referral
1111 $grow = sqlQuery("SELECT COUNT(*) AS count FROM forms " .
1112 "WHERE pid = ? AND encounter = ? AND " .
1113 "deleted = 0 AND formdir = 'LBFgcac'", array($patient_id,$inv_encounter));
1114 if (empty($grow['count'])) { // if there is no gcac form
1115 echo " alert('" . addslashes(xl('This visit will need a GCAC form, referral or procedure service.')) . "');\n";
1119 } // end if ($gcac_related_visit)
1122 </div
><!-- end of div container
-->