bug fix for note save return after fancybox replace. (#1320)
[openemr.git] / interface / patient_file / pos_checkout.php
blob660930c8009c4e1f05d0cf0f3e9b238f11608a98
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 * 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>;.
49 * @package OpenEMR
50 * @author Rod Roark <rod@sunsetsystems.com>
51 * @author Brady Miller <brady.g.miller@gmail.com>
52 * @link http://www.open-emr.org
59 require_once("../globals.php");
60 require_once("$srcdir/acl.inc");
61 require_once("$srcdir/patient.inc");
62 require_once("$srcdir/billing.inc");
63 require_once("../../custom/code_types.inc.php");
65 use OpenEMR\Services\FacilityService;
67 $facilityService = new FacilityService();
69 $currdecimals = $GLOBALS['currency_decimals'];
71 $details = empty($_GET['details']) ? 0 : 1;
73 $patient_id = empty($_GET['ptid']) ? $pid : 0 + $_GET['ptid'];
75 // Get the patient's name and chart number.
76 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
78 // Output HTML for an invoice line item.
80 $prevsvcdate = '';
81 function receiptDetailLine($svcdate, $description, $amount, $quantity)
83 global $prevsvcdate, $details;
84 if (!$details) {
85 return;
88 $amount = sprintf('%01.2f', $amount);
89 if (empty($quantity)) {
90 $quantity = 1;
93 $price = sprintf('%01.4f', $amount / $quantity);
94 $tmp = sprintf('%01.2f', $price);
95 if ($price == $tmp) {
96 $price = $tmp;
99 echo " <tr>\n";
100 echo " <td>" . ($svcdate == $prevsvcdate ? '&nbsp;' : text(oeFormatShortDate($svcdate))) . "</td>\n";
101 echo " <td>" . text($description) . "</td>\n";
102 echo " <td align='right'>" . text(oeFormatMoney($price)) . "</td>\n";
103 echo " <td align='right'>" . text($quantity) . "</td>\n";
104 echo " <td align='right'>" . text(oeFormatMoney($amount)) . "</td>\n";
105 echo " </tr>\n";
106 $prevsvcdate = $svcdate;
109 // Output HTML for an invoice payment.
111 function receiptPaymentLine($paydate, $amount, $description = '')
113 $amount = sprintf('%01.2f', 0 - $amount); // make it negative
114 echo " <tr>\n";
115 echo " <td>" . text(oeFormatShortDate($paydate)) . "</td>\n";
116 echo " <td>" . xlt('Payment') . " " . text($description) . "</td>\n";
117 echo " <td colspan='2'>&nbsp;</td>\n";
118 echo " <td align='right'>" . text(oeFormatMoney($amount)) . "</td>\n";
119 echo " </tr>\n";
122 // Generate a receipt from the last-billed invoice for this patient,
123 // or for the encounter specified as a GET parameter.
125 function generate_receipt($patient_id, $encounter = 0)
127 global $sl_err, $sl_cash_acc, $css_header, $details, $facilityService;
129 // Get details for what we guess is the primary facility.
130 $frow = $facilityService->getPrimaryBusinessEntity(array("useLegacyImplementation" => true));
132 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code,providerID');
134 // Get the most recent invoice data or that for the specified encounter.
136 // Adding a provider check so that their info can be displayed on receipts
137 if ($encounter) {
138 $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " .
139 "WHERE pid = ? AND encounter = ?", array($patient_id,$encounter));
140 } else {
141 $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " .
142 "WHERE pid = ? " .
143 "ORDER BY id DESC LIMIT 1", array($patient_id));
146 if (empty($ferow)) {
147 die(xlt("This patient has no activity."));
150 $trans_id = $ferow['id'];
151 $encounter = $ferow['encounter'];
152 $svcdate = substr($ferow['date'], 0, 10);
154 if ($GLOBALS['receipts_by_provider']) {
155 if (isset($ferow['provider_id'])) {
156 $encprovider = $ferow['provider_id'];
157 } else if (isset($patdata['providerID'])) {
158 $encprovider = $patdata['providerID'];
159 } else {
160 $encprovider = -1;
164 if ($encprovider) {
165 $providerrow = sqlQuery("SELECT fname, mname, lname, title, street, streetb, " .
166 "city, state, zip, phone, fax FROM users WHERE id = ?", array($encprovider));
169 // Get invoice reference number.
170 $encrow = sqlQuery("SELECT invoice_refno FROM form_encounter WHERE " .
171 "pid = ? AND encounter = ? LIMIT 1", array($patient_id,$encounter));
172 $invoice_refno = $encrow['invoice_refno'];
174 <html>
175 <head>
176 <?php html_header_show(); ?>
177 <link rel='stylesheet' href='<?php echo $css_header ?>' type='text/css'>
179 <title><?php echo xlt('Receipt for Payment'); ?></title>
181 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-min-3-1-1/index.js"></script>
182 <script type="text/javascript" src="../../library/dialog.js?v=<?php echo $v_js_includes; ?>"></script>
184 <script language="JavaScript">
186 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
188 $(document).ready(function() {
189 var win = top.printLogSetup ? top : opener.top;
190 win.printLogSetup(document.getElementById('printbutton'));
193 // Process click on Print button.
194 function printlog_before_print() {
195 var divstyle = document.getElementById('hideonprint').style;
196 divstyle.display = 'none';
199 // Process click on Delete button.
200 function deleteme() {
201 dlgopen('deleter.php?billing=<?php echo attr("$patient_id.$encounter"); ?>', '_blank', 500, 450);
202 return false;
205 // Called by the deleteme.php window on a successful delete.
206 function imdeleted() {
207 window.close();
210 </script>
211 </head>
212 <body class="body_top">
213 <center>
214 <?php
215 if ($GLOBALS['receipts_by_provider'] && !empty($providerrow)) {
216 printProviderHeader($providerrow);
217 } else {
218 printFacilityHeader($frow);
221 <?php
222 echo xlt("Receipt Generated") . ":" . text(date(' F j, Y'));
223 if ($invoice_refno) {
224 echo " " . xlt("Invoice Number") . ": " . text($invoice_refno) . " " . xlt("Service Date") . ": " . text($svcdate);
227 <br>&nbsp;
228 </b></p>
229 </center>
231 <?php echo text($patdata['fname']) . ' ' . text($patdata['mname']) . ' ' . text($patdata['lname']) ?>
232 <br><?php echo text($patdata['street']) ?>
233 <br><?php echo text($patdata['city']) . ', ' . text($patdata['state']) . ' ' . text($patdata['postal_code']) ?>
234 <br>&nbsp;
235 </p>
236 <center>
237 <table cellpadding='5'>
238 <tr>
239 <td><b><?php echo xlt('Date'); ?></b></td>
240 <td><b><?php echo xlt('Description'); ?></b></td>
241 <td align='right'><b><?php echo $details ? xlt('Price') : '&nbsp;'; ?></b></td>
242 <td align='right'><b><?php echo $details ? xlt('Qty') : '&nbsp;'; ?></b></td>
243 <td align='right'><b><?php echo xlt('Total'); ?></b></td>
244 </tr>
246 <?php
247 $charges = 0.00;
249 // Product sales
250 $inres = sqlStatement("SELECT s.sale_id, s.sale_date, s.fee, " .
251 "s.quantity, s.drug_id, d.name " .
252 "FROM drug_sales AS s LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
253 // "WHERE s.pid = '$patient_id' AND s.encounter = '$encounter' AND s.fee != 0 " .
254 "WHERE s.pid = ? AND s.encounter = ? " .
255 "ORDER BY s.sale_id", array($patient_id,$encounter));
256 while ($inrow = sqlFetchArray($inres)) {
257 $charges += sprintf('%01.2f', $inrow['fee']);
258 receiptDetailLine(
259 $inrow['sale_date'],
260 $inrow['name'],
261 $inrow['fee'],
262 $inrow['quantity']
266 // Service and tax items
267 $inres = sqlStatement("SELECT * FROM billing WHERE " .
268 "pid = ? AND encounter = ? AND " .
269 // "code_type != 'COPAY' AND activity = 1 AND fee != 0 " .
270 "code_type != 'COPAY' AND activity = 1 " .
271 "ORDER BY id", array($patient_id,$encounter));
272 while ($inrow = sqlFetchArray($inres)) {
273 $charges += sprintf('%01.2f', $inrow['fee']);
274 receiptDetailLine(
275 $svcdate,
276 $inrow['code_text'],
277 $inrow['fee'],
278 $inrow['units']
282 // Adjustments.
283 $inres = sqlStatement("SELECT " .
284 "a.code_type, a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
285 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
286 "FROM ar_activity AS a " .
287 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
288 "a.pid = ? AND a.encounter = ? AND " .
289 "a.adj_amount != 0 " .
290 "ORDER BY s.check_date, a.sequence_no", array($patient_id,$encounter));
291 while ($inrow = sqlFetchArray($inres)) {
292 $charges -= sprintf('%01.2f', $inrow['adj_amount']);
293 $payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
294 receiptDetailLine(
295 $svcdate,
296 $payer . ' ' . $inrow['memo'],
297 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 align='right'>&nbsp;</td>
310 <td align='right'>&nbsp;</td>
311 <td align='right'><?php echo text(oeFormatMoney($charges, true)) ?></td>
312 </tr>
313 <tr>
314 <td colspan='5'>&nbsp;</td>
315 </tr>
317 <?php
318 // Get co-pays.
319 $inres = sqlStatement("SELECT fee, code_text FROM billing WHERE " .
320 "pid = ? AND encounter = ? AND " .
321 "code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
322 "ORDER BY id", array($patient_id,$encounter));
323 while ($inrow = sqlFetchArray($inres)) {
324 $charges += sprintf('%01.2f', $inrow['fee']);
325 receiptPaymentLine($svcdate, 0 - $inrow['fee'], $inrow['code_text']);
328 // Get other payments.
329 $inres = sqlStatement("SELECT " .
330 "a.code_type, a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
331 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
332 "FROM ar_activity AS a " .
333 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
334 "a.pid = ? AND a.encounter = ? AND " .
335 "a.pay_amount != 0 " .
336 "ORDER BY s.check_date, a.sequence_no", array($patient_id,$encounter));
337 while ($inrow = sqlFetchArray($inres)) {
338 $payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
339 $charges -= sprintf('%01.2f', $inrow['pay_amount']);
340 receiptPaymentLine(
341 $svcdate,
342 $inrow['pay_amount'],
343 $payer . ' ' . $inrow['reference']
347 <tr>
348 <td colspan='5'>&nbsp;</td>
349 </tr>
350 <tr>
351 <td>&nbsp;</td>
352 <td><b><?php echo xlt('Balance Due'); ?></b></td>
353 <td colspan='2'>&nbsp;</td>
354 <td align='right'><?php echo text(oeFormatMoney($charges, true)) ?></td>
355 </tr>
356 </table>
357 </center>
358 <div id='hideonprint'>
360 &nbsp;
361 <a href='#' id='printbutton'><?php echo xlt('Print'); ?></a>
362 <?php if (acl_check('acct', 'disc')) { ?>
363 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
364 <a href='#' onclick='return deleteme();'><?php echo xlt('Undo Checkout'); ?></a>
365 <?php } ?>
366 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
367 <?php if ($details) { ?>
368 <a href='pos_checkout.php?details=0&ptid=<?php echo attr($patient_id); ?>&enc=<?php echo attr($encounter); ?>' onclick='top.restoreSession()'><?php echo xlt('Hide Details'); ?></a>
369 <?php } else { ?>
370 <a href='pos_checkout.php?details=1&ptid=<?php echo attr($patient_id); ?>&enc=<?php echo attr($encounter); ?>' onclick='top.restoreSession()'><?php echo xlt('Show Details'); ?></a>
371 <?php } ?>
372 </p>
373 </div>
374 </body>
375 </html>
376 <?php
377 } // end function generate_receipt()
379 // Function to output a line item for the input form.
381 $lino = 0;
382 function write_form_line(
383 $code_type,
384 $code,
385 $id,
386 $date,
387 $description,
388 $amount,
389 $units,
390 $taxrates
392 global $lino;
393 $amount = sprintf("%01.2f", $amount);
394 if (empty($units)) {
395 $units = 1;
398 $price = $amount / $units; // should be even cents, but ok here if not
399 if ($code_type == 'COPAY' && !$description) {
400 $description = xl('Payment');
403 echo " <tr>\n";
404 echo " <td>" . text(oeFormatShortDate($date));
405 echo "<input type='hidden' name='line[$lino][code_type]' value='" . attr($code_type) . "'>";
406 echo "<input type='hidden' name='line[$lino][code]' value='" . attr($code) . "'>";
407 echo "<input type='hidden' name='line[$lino][id]' value='" . attr($id) . "'>";
408 echo "<input type='hidden' name='line[$lino][description]' value='" . attr($description) . "'>";
409 echo "<input type='hidden' name='line[$lino][taxrates]' value='" . attr($taxrates) . "'>";
410 echo "<input type='hidden' name='line[$lino][price]' value='" . attr($price) . "'>";
411 echo "<input type='hidden' name='line[$lino][units]' value='" . attr($units) . "'>";
412 echo "</td>\n";
413 echo " <td>" . text($description) . "</td>";
414 echo " <td align='right'>" . text($units) . "</td>";
415 echo " <td align='right'><input type='text' name='line[$lino][amount]' " .
416 "value='" . attr($amount) . "' size='6' maxlength='8'";
417 // Modifying prices requires the acct/disc permission.
418 // if ($code_type == 'TAX' || ($code_type != 'COPAY' && !acl_check('acct','disc')))
419 echo " style='text-align:right;background-color:transparent' readonly";
420 // else echo " style='text-align:right' onkeyup='computeTotals()'";
421 echo "></td>\n";
422 echo " </tr>\n";
423 ++$lino;
426 // Create the taxes array. Key is tax id, value is
427 // (description, rate, accumulated total).
428 $taxes = array();
429 $pres = sqlStatement("SELECT option_id, title, option_value " .
430 "FROM list_options WHERE list_id = 'taxrate' AND activity = 1 ORDER BY seq, title, option_id");
431 while ($prow = sqlFetchArray($pres)) {
432 $taxes[$prow['option_id']] = array($prow['title'], $prow['option_value'], 0);
435 // Print receipt header for facility
436 function printFacilityHeader($frow)
438 echo "<p><b>" . text($frow['name']) .
439 "<br>" . text($frow['street']) .
440 "<br>" . text($frow['city']) . ', ' . text($frow['state']) . ' ' . text($frow['postal_code']) .
441 "<br>" . text($frow['phone']) .
442 "<br>&nbsp" .
443 "<br>";
446 // Pring receipt header for Provider
447 function printProviderHeader($pvdrow)
449 echo "<p><b>" . text($pvdrow['title']) . " " . text($pvdrow['fname']) . " " . text($pvdrow['mname']) . " " . text($pvdrow['lname']) . " " .
450 "<br>" . text($pvdrow['street']) .
451 "<br>" . text($pvdrow['city']) . ', ' . text($pvdrow['state']) . ' ' . text($pvdrow['postal_code']) .
452 "<br>" . text($pvdrow['phone']) .
453 "<br>&nbsp" .
454 "<br>";
457 // Mark the tax rates that are referenced in this invoice.
458 function markTaxes($taxrates)
460 global $taxes;
461 $arates = explode(':', $taxrates);
462 if (empty($arates)) {
463 return;
466 foreach ($arates as $value) {
467 if (!empty($taxes[$value])) {
468 $taxes[$value][2] = '1';
473 $payment_methods = array(
474 'Cash',
475 'Check',
476 'MC',
477 'VISA',
478 'AMEX',
479 'DISC',
480 'Other');
482 $alertmsg = ''; // anything here pops up in an alert box
484 // If the Save button was clicked...
486 if ($_POST['form_save']) {
487 // On a save, do the following:
488 // Flag drug_sales and billing items as billed.
489 // Post the corresponding invoice with its payment(s) to sql-ledger
490 // and be careful to use a unique invoice number.
491 // Call the generate-receipt function.
492 // Exit.
494 $form_pid = $_POST['form_pid'];
495 $form_encounter = $_POST['form_encounter'];
497 // Get the posting date from the form as yyyy-mm-dd.
498 $dosdate = date("Y-m-d");
499 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $_POST['form_date'], $matches)) {
500 $dosdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
503 // If there is no associated encounter (i.e. this invoice has only
504 // prescriptions) then assign an encounter number of the service
505 // date, with an optional suffix to ensure that it's unique.
507 if (! $form_encounter) {
508 $form_encounter = substr($dosdate, 0, 4) . substr($dosdate, 5, 2) . substr($dosdate, 8, 2);
509 $tmp = '';
510 while (true) {
511 $ferow = sqlQuery("SELECT id FROM form_encounter WHERE " .
512 "pid = ? AND encounter = ?", array($form_pid, $form_encounter.$tmp));
513 if (empty($ferow)) {
514 break;
517 $tmp = $tmp ? $tmp + 1 : 1;
520 $form_encounter .= $tmp;
523 // Delete any TAX rows from billing because they will be recalculated.
524 sqlStatement("UPDATE billing SET activity = 0 WHERE " .
525 "pid = ? AND encounter = ? AND " .
526 "code_type = 'TAX'", array($form_pid,$form_encounter));
528 $form_amount = $_POST['form_amount'];
529 $lines = $_POST['line'];
531 for ($lino = 0; $lines[$lino]['code_type']; ++$lino) {
532 $line = $lines[$lino];
533 $code_type = $line['code_type'];
534 $id = $line['id'];
535 $amount = sprintf('%01.2f', trim($line['amount']));
538 if ($code_type == 'PROD') {
539 // Product sales. The fee and encounter ID may have changed.
540 $query = "update drug_sales SET fee = ?, " .
541 "encounter = ?, billed = 1 WHERE " .
542 "sale_id = ?";
543 sqlQuery($query, array($amount,$form_encounter,$id));
544 } else if ($code_type == 'TAX') {
545 // In the SL case taxes show up on the invoice as line items.
546 // Otherwise we gotta save them somewhere, and in the billing
547 // table with a code type of TAX seems easiest.
548 // They will have to be stripped back out when building this
549 // script's input form.
550 addBilling(
551 $form_encounter,
552 'TAX',
553 'TAX',
554 'Taxes',
555 $form_pid,
560 $amount,
565 } else {
566 // Because there is no insurance here, there is no need for a claims
567 // table entry and so we do not call updateClaim(). Note we should not
568 // eliminate billed and bill_date from the billing table!
569 $query = "UPDATE billing SET fee = ?, billed = 1, " .
570 "bill_date = NOW() WHERE id = ?";
571 sqlQuery($query, array($amount,$id));
575 // Post discount.
576 if ($_POST['form_discount']) {
577 if ($GLOBALS['discount_by_money']) {
578 $amount = sprintf('%01.2f', trim($_POST['form_discount']));
579 } else {
580 $amount = sprintf('%01.2f', trim($_POST['form_discount']) * $form_amount / 100);
583 $memo = xl('Discount');
584 $time = date('Y-m-d H:i:s');
585 sqlBeginTrans();
586 $sequence_no = sqlQuery("SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array($form_pid, $form_encounter));
587 $query = "INSERT INTO ar_activity ( " .
588 "pid, encounter, sequence_no, code, modifier, payer_type, post_user, post_time, " .
589 "session_id, memo, adj_amount " .
590 ") VALUES ( " .
591 "?, " .
592 "?, " .
593 "?, " .
594 "'', " .
595 "'', " .
596 "'0', " .
597 "?, " .
598 "?, " .
599 "'0', " .
600 "?, " .
601 "? " .
602 ")";
603 sqlStatement($query, array($form_pid,$form_encounter,$sequence_no['increment'],$_SESSION['authUserID'],$time,$memo,$amount));
604 sqlCommitTrans();
607 // Post payment.
608 if ($_POST['form_amount']) {
609 $amount = sprintf('%01.2f', trim($_POST['form_amount']));
610 $form_source = trim($_POST['form_source']);
611 $paydesc = trim($_POST['form_method']);
612 //Fetching the existing code and modifier
613 $ResultSearchNew = sqlStatement(
614 "SELECT * FROM billing LEFT JOIN code_types ON billing.code_type=code_types.ct_key ".
615 "WHERE code_types.ct_fee=1 AND billing.activity!=0 AND billing.pid =? AND encounter=? ORDER BY billing.code,billing.modifier",
616 array($form_pid,$form_encounter)
618 if ($RowSearch = sqlFetchArray($ResultSearchNew)) {
619 $Codetype=$RowSearch['code_type'];
620 $Code=$RowSearch['code'];
621 $Modifier=$RowSearch['modifier'];
622 } else {
623 $Codetype='';
624 $Code='';
625 $Modifier='';
628 $session_id=sqlInsert(
629 "INSERT INTO ar_session (payer_id,user_id,reference,check_date,deposit_date,pay_total,".
630 " global_amount,payment_type,description,patient_id,payment_method,adjustment_code,post_to_date) ".
631 " VALUES ('0',?,?,now(),?,?,'','patient','COPAY',?,?,'patient_payment',now())",
632 array($_SESSION['authId'],$form_source,$dosdate,$amount,$form_pid,$paydesc)
635 sqlBeginTrans();
636 $sequence_no = sqlQuery("SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array($form_pid, $form_encounter));
637 $insrt_id=sqlInsert(
638 "INSERT INTO ar_activity (pid,encounter,sequence_no,code_type,code,modifier,payer_type,post_time,post_user,session_id,pay_amount,account_code)".
639 " VALUES (?,?,?,?,?,?,0,?,?,?,?,'PCP')",
640 array($form_pid,$form_encounter,$sequence_no['increment'],$Codetype,$Code,$Modifier,$dosdate,$_SESSION['authId'],$session_id,$amount)
642 sqlCommitTrans();
645 // If applicable, set the invoice reference number.
646 $invoice_refno = '';
647 if (isset($_POST['form_irnumber'])) {
648 $invoice_refno = trim($_POST['form_irnumber']);
649 } else {
650 $invoice_refno = updateInvoiceRefNumber();
653 if ($invoice_refno) {
654 sqlStatement("UPDATE form_encounter " .
655 "SET invoice_refno = ? " .
656 "WHERE pid = ? AND encounter = ?", array($invoice_refno,$form_pid,$form_encounter));
659 generate_receipt($form_pid, $form_encounter);
660 exit();
663 // If an encounter ID was given, then we must generate a receipt.
665 if (!empty($_GET['enc'])) {
666 generate_receipt($patient_id, $_GET['enc']);
667 exit();
670 // Get the unbilled billing table items for this patient.
671 $query = "SELECT id, date, code_type, code, modifier, code_text, " .
672 "provider_id, payer_id, units, fee, encounter " .
673 "FROM billing WHERE pid = ? AND activity = 1 AND " .
674 "billed = 0 AND code_type != 'TAX' " .
675 "ORDER BY encounter DESC, id ASC";
676 $bres = sqlStatement($query, array($patient_id));
678 // Get the product sales for this patient.
679 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, " .
680 "s.quantity, s.encounter, s.drug_id, d.name, r.provider_id " .
681 "FROM drug_sales AS s " .
682 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
683 "LEFT OUTER JOIN prescriptions AS r ON r.id = s.prescription_id " .
684 "WHERE s.pid = ? AND s.billed = 0 " .
685 "ORDER BY s.encounter DESC, s.sale_id ASC";
686 $dres = sqlStatement($query, array($patient_id));
688 // If there are none, just redisplay the last receipt and exit.
690 if (sqlNumRows($bres) == 0 && sqlNumRows($dres) == 0) {
691 generate_receipt($patient_id);
692 exit();
695 // Get the valid practitioners, including those not active.
696 $arr_users = array();
697 $ures = sqlStatement("SELECT id, username FROM users WHERE " .
698 "( authorized = 1 OR info LIKE '%provider%' ) AND username != ''");
699 while ($urow = sqlFetchArray($ures)) {
700 $arr_users[$urow['id']] = '1';
703 // Now write a data entry form:
704 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
705 // List unbilled product sales for the patient.
706 // Present an editable dollar amount for each line item, a total
707 // which is also the default value of the input payment amount,
708 // and OK and Cancel buttons.
710 <html>
711 <head>
712 <link rel='stylesheet' href='<?php echo $css_header ?>' type='text/css'>
713 <link rel="stylesheet" href="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.min.css">
715 <title><?php echo xlt('Patient Checkout'); ?></title>
716 <style>
717 </style>
719 <script type="text/javascript" src="../../library/textformat.js?v=<?php echo $v_js_includes; ?>"></script>
720 <script type="text/javascript" src="../../library/dialog.js?v=<?php echo $v_js_includes; ?>"></script>
721 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-min-3-1-1/index.js"></script>
722 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.full.min.js"></script>
724 <script language="JavaScript">
725 var mypcc = '<?php echo $GLOBALS['phone_country_code'] ?>';
727 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
729 // This clears the tax line items in preparation for recomputing taxes.
730 function clearTax(visible) {
731 var f = document.forms[0];
732 for (var lino = 0; true; ++lino) {
733 var pfx = 'line[' + lino + ']';
734 if (! f[pfx + '[code_type]']) break;
735 if (f[pfx + '[code_type]'].value != 'TAX') continue;
736 f[pfx + '[price]'].value = '0.00';
737 if (visible) f[pfx + '[amount]'].value = '0.00';
741 // For a given tax ID and amount, compute the tax on that amount and add it
742 // to the "price" (same as "amount") of the corresponding tax line item.
743 // Note the tax line items include their "taxrate" to make this easy.
744 function addTax(rateid, amount, visible) {
745 if (rateid.length == 0) return 0;
746 var f = document.forms[0];
747 for (var lino = 0; true; ++lino) {
748 var pfx = 'line[' + lino + ']';
749 if (! f[pfx + '[code_type]']) break;
750 if (f[pfx + '[code_type]'].value != 'TAX') continue;
751 if (f[pfx + '[code]'].value != rateid) continue;
752 var tax = amount * parseFloat(f[pfx + '[taxrates]'].value);
753 tax = parseFloat(tax.toFixed(<?php echo $currdecimals ?>));
754 var cumtax = parseFloat(f[pfx + '[price]'].value) + tax;
755 f[pfx + '[price]'].value = cumtax.toFixed(<?php echo $currdecimals ?>); // requires JS 1.5
756 if (visible) f[pfx + '[amount]'].value = cumtax.toFixed(<?php echo $currdecimals ?>); // requires JS 1.5
757 if (isNaN(tax)) alert('Tax rate not numeric at line ' + lino);
758 return tax;
760 return 0;
763 // This mess recomputes the invoice total and optionally applies a discount.
764 function computeDiscountedTotals(discount, visible) {
765 clearTax(visible);
766 var f = document.forms[0];
767 var total = 0.00;
768 for (var lino = 0; f['line[' + lino + '][code_type]']; ++lino) {
769 var code_type = f['line[' + lino + '][code_type]'].value;
770 // price is price per unit when the form was originally generated.
771 // By contrast, amount is the dynamically-generated discounted line total.
772 var price = parseFloat(f['line[' + lino + '][price]'].value);
773 if (isNaN(price)) alert('Price not numeric at line ' + lino);
774 if (code_type == 'COPAY' || code_type == 'TAX') {
775 // This works because the tax lines come last.
776 total += parseFloat(price.toFixed(<?php echo $currdecimals ?>));
777 continue;
779 var units = f['line[' + lino + '][units]'].value;
780 var amount = price * units;
781 amount = parseFloat(amount.toFixed(<?php echo $currdecimals ?>));
782 if (visible) f['line[' + lino + '][amount]'].value = amount.toFixed(<?php echo $currdecimals ?>);
783 total += amount;
784 var taxrates = f['line[' + lino + '][taxrates]'].value;
785 var taxids = taxrates.split(':');
786 for (var j = 0; j < taxids.length; ++j) {
787 addTax(taxids[j], amount, visible);
790 return total - discount;
793 // Recompute displayed amounts with any discount applied.
794 function computeTotals() {
795 var f = document.forms[0];
796 var discount = parseFloat(f.form_discount.value);
797 if (isNaN(discount)) discount = 0;
798 <?php if (!$GLOBALS['discount_by_money']) { ?>
799 // This site discounts by percentage, so convert it to a money amount.
800 if (discount > 100) discount = 100;
801 if (discount < 0 ) discount = 0;
802 discount = 0.01 * discount * computeDiscountedTotals(0, false);
803 <?php } ?>
804 var total = computeDiscountedTotals(discount, true);
805 f.form_amount.value = total.toFixed(<?php echo $currdecimals ?>);
806 return true;
809 $(document).ready(function() {
810 $('.datepicker').datetimepicker({
811 <?php $datetimepicker_timepicker = false; ?>
812 <?php $datetimepicker_showseconds = false; ?>
813 <?php $datetimepicker_formatInput = false; ?>
814 <?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?>
815 <?php // can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
819 </script>
820 </head>
822 <body class="body_top">
824 <form method='post' action='pos_checkout.php'>
825 <input type='hidden' name='form_pid' value='<?php echo attr($patient_id) ?>' />
827 <center>
830 <table cellspacing='5'>
831 <tr>
832 <td colspan='3' align='center'>
833 <b><?php echo xlt('Patient Checkout for '); ?><?php echo text($patdata['fname']) . " " .
834 text($patdata['lname']) . " (" . text($patdata['pubpid']) . ")" ?></b>
835 </td>
836 </tr>
837 <tr>
838 <td><b><?php echo xlt('Date'); ?></b></td>
839 <td><b><?php echo xlt('Description'); ?></b></td>
840 <td align='right'><b><?php echo xlt('Qty'); ?></b></td>
841 <td align='right'><b><?php echo xlt('Amount'); ?></b></td>
842 </tr>
843 <?php
844 $inv_encounter = '';
845 $inv_date = '';
846 $inv_provider = 0;
847 $inv_payer = 0;
848 $gcac_related_visit = false;
849 $gcac_service_provided = false;
851 // Process billing table items.
852 // Items that are not allowed to have a fee are skipped.
854 while ($brow = sqlFetchArray($bres)) {
855 // Skip all but the most recent encounter.
856 if ($inv_encounter && $brow['encounter'] != $inv_encounter) {
857 continue;
860 $thisdate = substr($brow['date'], 0, 10);
861 $code_type = $brow['code_type'];
863 // Collect tax rates, related code and provider ID.
864 $taxrates = '';
865 $related_code = '';
866 $sqlBindArray = array();
867 if (!empty($code_types[$code_type]['fee'])) {
868 $query = "SELECT taxrates, related_code FROM codes WHERE code_type = ? " .
869 " AND " .
870 "code = ? AND ";
871 array_push($sqlBindArray, $code_types[$code_type]['id'], $brow['code']);
872 if ($brow['modifier']) {
873 $query .= "modifier = ?";
874 array_push($sqlBindArray, $brow['modifier']);
875 } else {
876 $query .= "(modifier IS NULL OR modifier = '')";
879 $query .= " LIMIT 1";
880 $tmp = sqlQuery($query, $sqlBindArray);
881 $taxrates = $tmp['taxrates'];
882 $related_code = $tmp['related_code'];
883 markTaxes($taxrates);
886 write_form_line(
887 $code_type,
888 $brow['code'],
889 $brow['id'],
890 $thisdate,
891 $brow['code_text'],
892 $brow['fee'],
893 $brow['units'],
894 $taxrates
896 if (!$inv_encounter) {
897 $inv_encounter = $brow['encounter'];
900 $inv_payer = $brow['payer_id'];
901 if (!$inv_date || $inv_date < $thisdate) {
902 $inv_date = $thisdate;
905 // Custom logic for IPPF to determine if a GCAC issue applies.
906 if ($GLOBALS['ippf_specific'] && $related_code) {
907 $relcodes = explode(';', $related_code);
908 foreach ($relcodes as $codestring) {
909 if ($codestring === '') {
910 continue;
913 list($codetype, $code) = explode(':', $codestring);
914 if ($codetype !== 'IPPF') {
915 continue;
918 if (preg_match('/^25222/', $code)) {
919 $gcac_related_visit = true;
920 if (preg_match('/^25222[34]/', $code)) {
921 $gcac_service_provided = true;
928 // Process copays
930 $totalCopay = getPatientCopay($patient_id, $encounter);
931 if ($totalCopay < 0) {
932 write_form_line("COPAY", "", "", "", "", $totalCopay, "", "");
935 // Process drug sales / products.
937 while ($drow = sqlFetchArray($dres)) {
938 if ($inv_encounter && $drow['encounter'] && $drow['encounter'] != $inv_encounter) {
939 continue;
942 $thisdate = $drow['sale_date'];
943 if (!$inv_encounter) {
944 $inv_encounter = $drow['encounter'];
947 if (!$inv_provider && !empty($arr_users[$drow['provider_id']])) {
948 $inv_provider = $drow['provider_id'] + 0;
951 if (!$inv_date || $inv_date < $thisdate) {
952 $inv_date = $thisdate;
955 // Accumulate taxes for this product.
956 $tmp = sqlQuery("SELECT taxrates FROM drug_templates WHERE drug_id = ? " .
957 " ORDER BY selector LIMIT 1", array($drow['drug_id']));
958 // accumTaxes($drow['fee'], $tmp['taxrates']);
959 $taxrates = $tmp['taxrates'];
960 markTaxes($taxrates);
962 write_form_line(
963 'PROD',
964 $drow['drug_id'],
965 $drow['sale_id'],
966 $thisdate,
967 $drow['name'],
968 $drow['fee'],
969 $drow['quantity'],
970 $taxrates
974 // Write a form line for each tax that has money, adding to $total.
975 foreach ($taxes as $key => $value) {
976 if ($value[2]) {
977 write_form_line('TAX', $key, $key, date('Y-m-d'), $value[0], 0, 1, $value[1]);
981 // Besides copays, do not collect any other information from ar_activity,
982 // since this is for appt checkout.
984 if ($inv_encounter) {
985 $erow = sqlQuery("SELECT provider_id FROM form_encounter WHERE " .
986 "pid = ? AND encounter = ? " .
987 "ORDER BY id DESC LIMIT 1", array($patient_id,$inv_encounter));
988 $inv_provider = $erow['provider_id'] + 0;
991 </table>
994 <table border='0' cellspacing='4'>
996 <tr>
997 <td>
998 <?php echo $GLOBALS['discount_by_money'] ? xlt('Discount Amount') : xlt('Discount Percentage'); ?>:
999 </td>
1000 <td>
1001 <input type='text' name='form_discount' size='6' maxlength='8' value=''
1002 style='text-align:right' onkeyup='computeTotals()'>
1003 </td>
1004 </tr>
1006 <tr>
1007 <td>
1008 <?php echo xlt('Payment Method'); ?>:
1009 </td>
1010 <td>
1011 <select name='form_method'>
1012 <?php
1013 $query1112 = "SELECT * FROM list_options where list_id=? ORDER BY seq, title ";
1014 $bres1112 = sqlStatement($query1112, array('payment_method'));
1015 while ($brow1112 = sqlFetchArray($bres1112)) {
1016 if ($brow1112['option_id']=='electronic' || $brow1112['option_id']=='bank_draft') {
1017 continue;
1020 echo "<option value='".attr($brow1112['option_id'])."'>".text(xl_list_label($brow1112['title']))."</option>";
1023 </select>
1024 </td>
1025 </tr>
1027 <tr>
1028 <td>
1029 <?php echo xlt('Check/Reference Number'); ?>:
1030 </td>
1031 <td>
1032 <input type='text' name='form_source' size='10' value=''>
1033 </td>
1034 </tr>
1036 <tr>
1037 <td>
1038 <?php echo xlt('Amount Paid'); ?>:
1039 </td>
1040 <td>
1041 <input type='text' name='form_amount' size='10' value='0.00'>
1042 </td>
1043 </tr>
1045 <tr>
1046 <td>
1047 <?php echo xlt('Posting Date'); ?>:
1048 </td>
1049 <td>
1050 <input type='text' size='10' class='datepicker' name='form_date' id='form_date'
1051 value='<?php echo attr($inv_date) ?>'
1052 title='yyyy-mm-dd date of service' />
1053 </td>
1054 </tr>
1056 <?php
1057 // If this user has a non-empty irnpool assigned, show the pending
1058 // invoice reference number.
1059 $irnumber = getInvoiceRefNumber();
1060 if (!empty($irnumber)) {
1062 <tr>
1063 <td>
1064 <?php echo xlt('Tentative Invoice Ref No'); ?>:
1065 </td>
1066 <td>
1067 <?php echo text($irnumber); ?>
1068 </td>
1069 </tr>
1070 <?php
1071 } // Otherwise if there is an invoice reference number mask, ask for the refno.
1072 else if (!empty($GLOBALS['gbl_mask_invoice_number'])) {
1074 <tr>
1075 <td>
1076 <?php echo xlt('Invoice Reference Number'); ?>:
1077 </td>
1078 <td>
1079 <input type='text' name='form_irnumber' size='10' value=''
1080 onkeyup='maskkeyup(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number']); ?>")'
1081 onblur='maskblur(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number']); ?>")'
1083 </td>
1084 </tr>
1085 <?php
1089 <tr>
1090 <td colspan='2' align='center'>
1091 &nbsp;<br>
1092 <input type='submit' name='form_save' value='<?php echo xla('Save'); ?>' /> &nbsp;
1093 <?php if (empty($_GET['framed'])) { ?>
1094 <input type='button' value='<?php echo xla('Cancel'); ?>' onclick='window.close()' />
1095 <?php } ?>
1096 <input type='hidden' name='form_provider' value='<?php echo attr($inv_provider) ?>' />
1097 <input type='hidden' name='form_payer' value='<?php echo attr($inv_payer) ?>' />
1098 <input type='hidden' name='form_encounter' value='<?php echo attr($inv_encounter) ?>' />
1099 </td>
1100 </tr>
1102 </table>
1103 </center>
1105 </form>
1107 <script language='JavaScript'>
1108 computeTotals();
1110 <?php
1111 if ($gcac_related_visit && !$gcac_service_provided) {
1112 // Skip this warning if the GCAC visit form is not allowed.
1113 $grow = sqlQuery("SELECT COUNT(*) AS count FROM layout_group_properties " .
1114 "WHERE grp_form_id = 'LBFgcac' grp_group_id = '' AND grp_activity = 1");
1115 if (!empty($grow['count'])) { // if gcac is used
1116 // Skip this warning if referral or abortion in TS.
1117 $grow = sqlQuery("SELECT COUNT(*) AS count FROM transactions " .
1118 "WHERE title = 'Referral' AND refer_date IS NOT NULL AND " .
1119 "refer_date = ? AND pid = ?", array($inv_date,$patient_id));
1120 if (empty($grow['count'])) { // if there is no referral
1121 $grow = sqlQuery("SELECT COUNT(*) AS count FROM forms " .
1122 "WHERE pid = ? AND encounter = ? AND " .
1123 "deleted = 0 AND formdir = 'LBFgcac'", array($patient_id,$inv_encounter));
1124 if (empty($grow['count'])) { // if there is no gcac form
1125 echo " alert('" . addslashes(xl('This visit will need a GCAC form, referral or procedure service.')) . "');\n";
1129 } // end if ($gcac_related_visit)
1131 </script>
1133 </body>
1134 </html>