added support for optional custom pdf logic
[openemr.git] / interface / patient_file / pos_checkout.php
blobaa510c08f1c8b8dd2cd0e25b93910f4e47ecc448
1 <?php
2 // Copyright (C) 2006-2009 Rod Roark <rod@sunsetsystems.com>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
9 // This module supports a popup window to handle patient checkout
10 // as a point-of-sale transaction. Support for in-house drug sales
11 // is included.
13 // Important notes about system design:
15 // (1) Drug sales may or may not be associated with an encounter;
16 // they are if they are paid for concurrently with an encounter, or
17 // if they are "product" (non-prescription) sales via the Fee Sheet.
18 // (2) Drug sales without an encounter will have 20YYMMDD, possibly
19 // with a suffix, as the encounter-number portion of their invoice
20 // number.
21 // (3) Payments are saved as AR only, don't mess with the billing table.
22 // See library/classes/WSClaim.class.php for posting code.
23 // (4) On checkout, the billing and drug_sales table entries are marked
24 // as billed and so become unavailable for further billing.
25 // (5) Receipt printing must be a separate operation from payment,
26 // and repeatable.
28 require_once("../globals.php");
29 require_once("$srcdir/acl.inc");
30 require_once("$srcdir/patient.inc");
31 require_once("$srcdir/billing.inc");
32 require_once("$srcdir/sql-ledger.inc");
33 require_once("$srcdir/freeb/xmlrpc.inc");
34 require_once("$srcdir/freeb/xmlrpcs.inc");
35 require_once("../../custom/code_types.inc.php");
37 $INTEGRATED_AR = $GLOBALS['oer_config']['ws_accounting']['enabled'] === 2;
39 $details = empty($_GET['details']) ? 0 : 1;
41 // Get the patient's name and chart number.
42 $patdata = getPatientData($pid, 'fname,mname,lname,pubpid,street,city,state,postal_code');
44 //////////////////////////////////////////////////////////////////////
45 // The following functions are inline here temporarily, and should be
46 // moved to an includable module for common use. In particular
47 // WSClaim.class.php should be rewritten to use them.
48 //////////////////////////////////////////////////////////////////////
50 // Initialize the array of invoice information for posting to the
51 // accounting system.
53 function invoice_initialize(& $invoice_info, $patient_id, $provider_id,
54 $payer_id = 0, $encounter = 0, $dosdate = '')
56 $db = $GLOBALS['adodb']['db'];
58 // Get foreign ID (customer) for patient.
59 $sql = "SELECT foreign_id from integration_mapping as im " .
60 "LEFT JOIN patient_data as pd on im.local_id=pd.id " .
61 "where pd.pid = '" .
62 $patient_id .
63 "' and im.local_table='patient_data' and im.foreign_table='customer'";
64 $result = $db->Execute($sql);
65 if($result && !$result->EOF) {
66 $foreign_patient_id = $result->fields['foreign_id'];
68 else {
69 return "Patient '" . $patient_id . "' has not yet been posted to the accounting system.";
72 // Get foreign ID (salesman) for provider.
73 $sql = "SELECT foreign_id from integration_mapping WHERE " .
74 "local_id = $provider_id AND local_table='users' and foreign_table='salesman'";
75 $result = $db->Execute($sql);
76 if($result && !$result->EOF) {
77 $foreign_provider_id = $result->fields['foreign_id'];
79 else {
80 return "Provider '" . $provider_id . "' has not yet been posted to the accounting system.";
83 // Get foreign ID (customer) for insurance payer.
84 if ($payer_id && ! $GLOBALS['insurance_companies_are_not_customers']) {
85 $sql = "SELECT foreign_id from integration_mapping WHERE " .
86 "local_id = $payer_id AND local_table = 'insurance_companies' AND foreign_table='customer'";
87 $result = $db->Execute($sql);
88 if($result && !$result->EOF) {
89 $foreign_payer_id = $result->fields['foreign_id'];
91 else {
92 return "Payer '" . $payer_id . "' has not yet been posted to the accounting system.";
94 } else {
95 $foreign_payer_id = $payer_id;
98 // Create invoice notes for the new invoice that list the patient's
99 // insurance plans. This is so that when payments are posted, the user
100 // can easily see if a secondary claim needs to be submitted.
102 $insnotes = "";
103 $insno = 0;
104 foreach (array("primary", "secondary", "tertiary") as $instype) {
105 ++$insno;
106 $sql = "SELECT insurance_companies.name " .
107 "FROM insurance_data, insurance_companies WHERE " .
108 "insurance_data.pid = $patient_id AND " .
109 "insurance_data.type = '$instype' AND " .
110 "insurance_companies.id = insurance_data.provider " .
111 "ORDER BY insurance_data.date DESC LIMIT 1";
112 $result = $db->Execute($sql);
113 if ($result && !$result->EOF && $result->fields['name']) {
114 if ($insnotes) $insnotes .= "\n";
115 $insnotes .= "Ins$insno: " . $result->fields['name'];
118 $invoice_info['notes'] = $insnotes;
120 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $dosdate, $matches)) {
121 $dosdate = $matches[2] . '-' . $matches[3] . '-' . $matches[1];
122 } else {
123 $dosdate = date("m-d-Y");
126 $invoice_info['salesman'] = $foreign_provider_id;
127 $invoice_info['customerid'] = $foreign_patient_id;
128 $invoice_info['payer_id'] = $foreign_payer_id;
129 $invoice_info['invoicenumber'] = $patient_id . "." . $encounter;
130 $invoice_info['dosdate'] = $dosdate;
131 $invoice_info['items'] = array();
132 $invoice_info['total'] = '0.00';
134 return '';
137 function invoice_add_line_item(& $invoice_info, $code_type, $code,
138 $code_text, $amount, $units=1)
140 $units = max(1, intval(trim($units)));
141 $amount = sprintf("%01.2f", $amount);
142 $price = $amount / $units;
143 $tmp = sprintf("%01.2f", $price);
144 if (abs($price - $tmp) < 0.000001) $price = $tmp;
145 $tii = array();
146 $tii['maincode'] = $code;
147 $tii['itemtext'] = "$code_type:$code";
148 if ($code_text) $tii['itemtext'] .= " $code_text";
149 // $tii['qty'] = 1;
150 // $tii['price'] = sprintf("%01.2f", $amount);
151 $tii['qty'] = $units;
152 $tii['price'] = $price;
153 $tii['glaccountid'] = $GLOBALS['oer_config']['ws_accounting']['income_acct'];
154 // $invoice_info['total'] = sprintf("%01.2f", $invoice_info['total'] + $tii['price']);
155 $invoice_info['total'] = sprintf("%01.2f", $invoice_info['total'] + $amount);
156 $invoice_info['items'][] = $tii;
157 return '';
160 function invoice_post(& $invoice_info)
162 $function['ezybiz.add_invoice'] = array(new xmlrpcval($invoice_info, "struct"));
164 list($name, $var) = each($function);
165 $f = new xmlrpcmsg($name, $var);
167 $c = new xmlrpc_client($GLOBALS['oer_config']['ws_accounting']['url'],
168 $GLOBALS['oer_config']['ws_accounting']['server'],
169 $GLOBALS['oer_config']['ws_accounting']['port']);
171 $c->setCredentials($GLOBALS['oer_config']['ws_accounting']['username'],
172 $GLOBALS['oer_config']['ws_accounting']['password']);
174 $r = $c->send($f);
175 if (!$r) return "XMLRPC send failed";
177 // We are not doing anything with the return value yet... should we?
178 $tv = $r->value();
179 if (is_object($tv)) {
180 $value = $tv->getval();
182 else {
183 $value = null;
186 if ($r->faultCode()) {
187 return "Fault: Code: " . $r->faultCode() . " Reason '" . $r->faultString() . "'";
190 return '';
193 ///////////// End of SQL-Ledger invoice posting functions ////////////
195 // Output HTML for an invoice line item.
197 $prevsvcdate = '';
198 function receiptDetailLine($svcdate, $description, $amount, $quantity) {
199 global $prevsvcdate, $details;
200 if (!$details) return;
201 $amount = sprintf('%01.2f', $amount);
202 if (empty($quantity)) $quantity = 1;
203 $price = sprintf('%01.4f', $amount / $quantity);
204 $tmp = sprintf('%01.2f', $price);
205 if ($price == $tmp) $price = $tmp;
206 echo " <tr>\n";
207 echo " <td>" . ($svcdate == $prevsvcdate ? '&nbsp;' : $svcdate) . "</td>\n";
208 echo " <td>$description</td>\n";
209 echo " <td align='right'>$price</td>\n";
210 echo " <td align='right'>$quantity</td>\n";
211 echo " <td align='right'>$amount</td>\n";
212 echo " </tr>\n";
213 $prevsvcdate = $svcdate;
216 // Output HTML for an invoice payment.
218 function receiptPaymentLine($paydate, $amount, $description='') {
219 $amount = sprintf('%01.2f', 0 - $amount); // make it negative
220 echo " <tr>\n";
221 echo " <td>$paydate</td>\n";
222 echo " <td>" . xl('Payment') . " $description</td>\n";
223 echo " <td colspan='2'>&nbsp;</td>\n";
224 echo " <td align='right'>$amount</td>\n";
225 echo " </tr>\n";
228 // Generate a receipt from the last-billed invoice for this patient,
229 // or for the encounter specified as a GET parameter.
231 function generate_receipt($patient_id, $encounter=0) {
232 global $sl_err, $sl_cash_acc, $css_header, $details, $INTEGRATED_AR;
234 // Get details for what we guess is the primary facility.
235 $frow = sqlQuery("SELECT * FROM facility " .
236 "ORDER BY billing_location DESC, accepts_assignment DESC, id LIMIT 1");
238 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
240 // Get the most recent invoice data or that for the specified encounter.
242 if ($INTEGRATED_AR) {
243 if ($encounter) {
244 $ferow = sqlQuery("SELECT id, date, encounter FROM form_encounter " .
245 "WHERE pid = '$patient_id' AND encounter = '$encounter'");
246 } else {
247 $ferow = sqlQuery("SELECT id, date, encounter FROM form_encounter " .
248 "WHERE pid = '$patient_id' " .
249 "ORDER BY id DESC LIMIT 1");
251 if (empty($ferow)) die(xl("This patient has no activity."));
252 $trans_id = $ferow['id'];
253 $encounter = $ferow['encounter'];
254 $svcdate = substr($ferow['date'], 0, 10);
256 else {
257 SLConnect();
259 $arres = SLQuery("SELECT * FROM ar WHERE " .
260 "invnumber LIKE '$patient_id.%' " .
261 "ORDER BY id DESC LIMIT 1");
262 if ($sl_err) die($sl_err);
263 if (!SLRowCount($arres)) die(xl("This patient has no activity."));
264 $arrow = SLGetRow($arres, 0);
266 $trans_id = $arrow['id'];
268 // Determine the date of service. An 8-digit encounter number is
269 // presumed to be a date of service imported during conversion or
270 // associated with prescriptions only. Otherwise look it up in the
271 // form_encounter table.
273 $svcdate = "";
274 list($trash, $encounter) = explode(".", $arrow['invnumber']);
275 if (strlen($encounter) >= 8) {
276 $svcdate = substr($encounter, 0, 4) . "-" . substr($encounter, 4, 2) .
277 "-" . substr($encounter, 6, 2);
279 else if ($encounter) {
280 $tmp = sqlQuery("SELECT date FROM form_encounter WHERE " .
281 "encounter = $encounter");
282 $svcdate = substr($tmp['date'], 0, 10);
284 } // end not $INTEGRATED_AR
286 <html>
287 <head>
288 <?php html_header_show(); ?>
289 <link rel='stylesheet' href='<?php echo $css_header ?>' type='text/css'>
290 <title><?php xl('Receipt for Payment','e'); ?></title>
291 <script type="text/javascript" src="../../library/dialog.js"></script>
292 <script language="JavaScript">
294 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
296 // Process click on Print button.
297 function printme() {
298 var divstyle = document.getElementById('hideonprint').style;
299 divstyle.display = 'none';
300 window.print();
301 return false;
304 // Process click on Delete button.
305 function deleteme() {
306 dlgopen('deleter.php?billing=<?php echo "$patient_id.$encounter"; ?>', '_blank', 500, 450);
307 return false;
310 // Called by the deleteme.php window on a successful delete.
311 function imdeleted() {
312 window.close();
315 </script>
316 </head>
317 <body class="body_top">
318 <center>
319 <p><b><?php echo $frow['name'] ?>
320 <br><?php echo $frow['street'] ?>
321 <br><?php echo $frow['city'] . ', ' . $frow['state'] . ' ' . $frow['postal_code'] ?>
322 <br><?php echo $frow['phone'] ?>
323 <br>&nbsp;
324 <br><?php echo xl("Receipt Generated") . date(' F j, Y') ?>
325 <br>&nbsp;
326 </b></p>
327 </center>
329 <?php echo $patdata['fname'] . ' ' . $patdata['mname'] . ' ' . $patdata['lname'] ?>
330 <br><?php echo $patdata['street'] ?>
331 <br><?php echo $patdata['city'] . ', ' . $patdata['state'] . ' ' . $patdata['postal_code'] ?>
332 <br>&nbsp;
333 </p>
334 <center>
335 <table cellpadding='5'>
336 <tr>
337 <td><b><?php xl('Date','e'); ?></b></td>
338 <td><b><?php xl('Description','e'); ?></b></td>
339 <td align='right'><b><?php echo $details ? xl('Price') : '&nbsp;'; ?></b></td>
340 <td align='right'><b><?php echo $details ? xl('Qty' ) : '&nbsp;'; ?></b></td>
341 <td align='right'><b><?php xl('Total','e'); ?></b></td>
342 </tr>
344 <?php
345 $charges = 0.00;
347 if ($INTEGRATED_AR) {
348 // Product sales
349 $inres = sqlStatement("SELECT s.sale_id, s.sale_date, s.fee, " .
350 "s.quantity, s.drug_id, d.name " .
351 "FROM drug_sales AS s LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
352 "WHERE s.pid = '$patient_id' AND s.encounter = '$encounter' AND s.fee != 0 " .
353 "ORDER BY s.sale_id");
354 while ($inrow = sqlFetchArray($inres)) {
355 $charges += sprintf('%01.2f', $inrow['fee']);
356 receiptDetailLine($inrow['sale_date'], $inrow['name'],
357 $inrow['fee'], $inrow['quantity']);
359 // Service and tax items
360 $inres = sqlStatement("SELECT * FROM billing WHERE " .
361 "pid = '$patient_id' AND encounter = '$encounter' AND " .
362 "code_type != 'COPAY' AND activity = 1 AND fee != 0 " .
363 "ORDER BY id");
364 while ($inrow = sqlFetchArray($inres)) {
365 $charges += sprintf('%01.2f', $inrow['fee']);
366 receiptDetailLine($svcdate, $inrow['code_text'],
367 $inrow['fee'], $inrow['units']);
369 // Adjustments.
370 $inres = sqlStatement("SELECT " .
371 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
372 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
373 "FROM ar_activity AS a " .
374 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
375 "a.pid = '$patient_id' AND a.encounter = '$encounter' AND " .
376 "a.adj_amount != 0 " .
377 "ORDER BY s.check_date, a.sequence_no");
378 while ($inrow = sqlFetchArray($inres)) {
379 $charges -= sprintf('%01.2f', $inrow['adj_amount']);
380 $payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
381 receiptDetailLine($svcdate, $payer . ' ' . $inrow['memo'],
382 0 - $inrow['adj_amount'], 1);
384 } // end $INTEGRATED_AR
385 else {
386 // Request all line items with money belonging to the invoice.
387 $inres = SLQuery("SELECT * FROM invoice WHERE " .
388 "trans_id = $trans_id AND sellprice != 0 ORDER BY id");
389 if ($sl_err) die($sl_err);
390 for ($irow = 0; $irow < SLRowCount($inres); ++$irow) {
391 $row = SLGetRow($inres, $irow);
392 $amount = sprintf('%01.2f', $row['sellprice'] * $row['qty']);
393 $charges += $amount;
394 $desc = preg_replace('/^.{1,6}:/', '', $row['description']);
395 receiptDetailLine($svcdate, $desc, $amount, $row['qty']);
397 } // end not $INTEGRATED_AR
400 <tr>
401 <td colspan='5'>&nbsp;</td>
402 </tr>
403 <tr>
404 <td><?php echo $svcdispdate; ?></td>
405 <td><b><?php xl('Total Charges','e'); ?></b></td>
406 <td align='right'>&nbsp;</td>
407 <td align='right'>&nbsp;</td>
408 <td align='right'><?php echo sprintf('%01.2f', $charges) ?></td>
409 </tr>
410 <tr>
411 <td colspan='5'>&nbsp;</td>
412 </tr>
414 <?php
415 if ($INTEGRATED_AR) {
416 // Get co-pays.
417 $inres = sqlStatement("SELECT fee, code_text FROM billing WHERE " .
418 "pid = '$patient_id' AND encounter = '$encounter' AND " .
419 "code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
420 "ORDER BY id");
421 while ($inrow = sqlFetchArray($inres)) {
422 $charges += sprintf('%01.2f', $inrow['fee']);
423 receiptPaymentLine($svcdate, 0 - $inrow['fee'], $inrow['code_text']);
425 // Get other payments.
426 $inres = sqlStatement("SELECT " .
427 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
428 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
429 "FROM ar_activity AS a " .
430 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
431 "a.pid = '$patient_id' AND a.encounter = '$encounter' AND " .
432 "a.pay_amount != 0 " .
433 "ORDER BY s.check_date, a.sequence_no");
434 $payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
435 while ($inrow = sqlFetchArray($inres)) {
436 $charges -= sprintf('%01.2f', $inrow['pay_amount']);
437 receiptPaymentLine($svcdate, $inrow['pay_amount'],
438 $payer . ' ' . $inrow['reference']);
440 } // end $INTEGRATED_AR
441 else {
442 $chart_id_cash = SLQueryValue("select id from chart where accno = '$sl_cash_acc'");
443 if ($sl_err) die($sl_err);
444 if (! $chart_id_cash) die("There is no COA entry for cash account '$sl_cash_acc'");
446 // Request all cash entries belonging to the invoice.
447 $atres = SLQuery("SELECT * FROM acc_trans WHERE " .
448 "trans_id = $trans_id AND chart_id = $chart_id_cash ORDER BY transdate");
449 if ($sl_err) die($sl_err);
451 for ($irow = 0; $irow < SLRowCount($atres); ++$irow) {
452 $row = SLGetRow($atres, $irow);
453 $amount = sprintf('%01.2f', $row['amount']); // negative
454 $charges += $amount;
455 $rowsource = $row['source'];
456 if (strtolower($rowsource) == 'co-pay') $rowsource = '';
457 receiptPaymentLine($row['transdate'], 0 - $amount, $rowsource);
459 } // end not $INTEGRATED_AR
461 <tr>
462 <td colspan='5'>&nbsp;</td>
463 </tr>
464 <tr>
465 <td>&nbsp;</td>
466 <td><b><?php xl('Balance Due','e'); ?></b></td>
467 <td colspan='2'>&nbsp;</td>
468 <td align='right'><?php echo sprintf('%01.2f', $charges) ?></td>
469 </tr>
470 </table>
471 </center>
472 <div id='hideonprint'>
474 &nbsp;
475 <a href='#' onclick='return printme();'><?php xl('Print','e'); ?></a>
476 <?php if (acl_check('acct','disc')) { ?>
477 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
478 <a href='#' onclick='return deleteme();'><?php xl('Undo Checkout','e'); ?></a>
479 <?php } ?>
480 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
481 <?php if ($details) { ?>
482 <a href='pos_checkout.php?details=0&enc=<?php echo $encounter; ?>'><?php xl('Hide Details','e'); ?></a>
483 <?php } else { ?>
484 <a href='pos_checkout.php?details=1&enc=<?php echo $encounter; ?>'><?php xl('Show Details','e'); ?></a>
485 <?php } ?>
486 </p>
487 </div>
488 </body>
489 </html>
490 <?php
491 if (!$INTEGRATED_AR) SLClose();
492 } // end function generate_receipt()
494 // Function to output a line item for the input form.
496 $lino = 0;
497 function write_form_line($code_type, $code, $id, $date, $description,
498 $amount, $units, $taxrates) {
499 global $lino;
500 $amount = sprintf("%01.2f", $amount);
501 if (empty($units)) $units = 1;
502 $price = $amount / $units; // should be even cents, but ok here if not
503 if ($code_type == 'COPAY' && !$description) $description = xl('Payment');
504 echo " <tr>\n";
505 echo " <td>$date";
506 echo "<input type='hidden' name='line[$lino][code_type]' value='$code_type'>";
507 echo "<input type='hidden' name='line[$lino][code]' value='$code'>";
508 echo "<input type='hidden' name='line[$lino][id]' value='$id'>";
509 echo "<input type='hidden' name='line[$lino][description]' value='$description'>";
510 echo "<input type='hidden' name='line[$lino][taxrates]' value='$taxrates'>";
511 echo "<input type='hidden' name='line[$lino][price]' value='$price'>";
512 echo "<input type='hidden' name='line[$lino][units]' value='$units'>";
513 echo "</td>\n";
514 echo " <td>$description</td>";
515 echo " <td align='right'>$units</td>";
516 echo " <td align='right'><input type='text' name='line[$lino][amount]' " .
517 "value='$amount' size='6' maxlength='8'";
518 // Modifying prices requires the acct/disc permission.
519 // if ($code_type == 'TAX' || ($code_type != 'COPAY' && !acl_check('acct','disc')))
520 echo " style='text-align:right;background-color:transparent' readonly";
521 // else echo " style='text-align:right' onkeyup='computeTotals()'";
522 echo "></td>\n";
523 echo " </tr>\n";
524 ++$lino;
527 // Create the taxes array. Key is tax id, value is
528 // (description, rate, accumulated total).
529 $taxes = array();
530 $pres = sqlStatement("SELECT option_id, title, option_value " .
531 "FROM list_options WHERE list_id = 'taxrate' ORDER BY seq");
532 while ($prow = sqlFetchArray($pres)) {
533 $taxes[$prow['option_id']] = array($prow['title'], $prow['option_value'], 0);
536 // Mark the tax rates that are referenced in this invoice.
537 function markTaxes($taxrates) {
538 global $taxes;
539 $arates = explode(':', $taxrates);
540 if (empty($arates)) return;
541 foreach ($arates as $value) {
542 if (!empty($taxes[$value])) $taxes[$value][2] = '1';
546 $payment_methods = array(
547 'Cash',
548 'Check',
549 'MC',
550 'VISA',
551 'AMEX',
552 'DISC',
553 'Other');
555 $alertmsg = ''; // anything here pops up in an alert box
557 // If the Save button was clicked...
559 if ($_POST['form_save']) {
561 // On a save, do the following:
562 // Flag drug_sales and billing items as billed.
563 // Post the corresponding invoice with its payment(s) to sql-ledger
564 // and be careful to use a unique invoice number.
565 // Call the generate-receipt function.
566 // Exit.
568 $form_pid = $_POST['form_pid'];
569 $form_encounter = $_POST['form_encounter'];
571 // Get the posting date from the form as yyyy-mm-dd.
572 $dosdate = date("Y-m-d");
573 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $_POST['form_date'], $matches)) {
574 $dosdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
577 // If there is no associated encounter (i.e. this invoice has only
578 // prescriptions) then assign an encounter number of the service
579 // date, with an optional suffix to ensure that it's unique.
581 if (! $form_encounter) {
582 $form_encounter = substr($dosdate,0,4) . substr($dosdate,5,2) . substr($dosdate,8,2);
583 $tmp = '';
584 if ($INTEGRATED_AR) {
585 while (true) {
586 $ferow = sqlQuery("SELECT id FROM form_encounter WHERE " .
587 "pid = '$form_pid' AND encounter = '$form_encounter$tmp'");
588 if (empty($ferow)) break;
589 $tmp = $tmp ? $tmp + 1 : 1;
592 else {
593 SLConnect();
594 while (SLQueryValue("select id from ar where " .
595 "invnumber = '$form_pid.$form_encounter$tmp'")) {
596 $tmp = $tmp ? $tmp + 1 : 1;
598 SLClose();
600 $form_encounter .= $tmp;
603 if ($INTEGRATED_AR) {
604 // Delete any TAX rows from billing because they will be recalculated.
605 sqlStatement("UPDATE billing SET activity = 0 WHERE " .
606 "pid = '$form_pid' AND encounter = '$form_encounter' AND " .
607 "code_type = 'TAX'");
609 else {
610 // Initialize an array of invoice information for posting.
611 $invoice_info = array();
612 $msg = invoice_initialize($invoice_info, $form_pid,
613 $_POST['form_provider'], $_POST['form_payer'], $form_encounter, $dosdate);
614 if ($msg) die($msg);
617 $form_amount = $_POST['form_amount'];
618 $lines = $_POST['line'];
620 for ($lino = 0; $lines[$lino]['code_type']; ++$lino) {
621 $line = $lines[$lino];
622 $code_type = $line['code_type'];
623 $id = $line['id'];
624 $amount = sprintf('%01.2f', trim($line['amount']));
626 if (!$INTEGRATED_AR) {
627 $msg = invoice_add_line_item($invoice_info, $code_type,
628 $line['code'], $line['description'], $amount, $line['units']);
629 if ($msg) die($msg);
632 if ($code_type == 'PROD') {
633 // Product sales. The fee and encounter ID may have changed.
634 $query = "update drug_sales SET fee = '$amount', " .
635 "encounter = '$form_encounter', billed = 1 WHERE " .
636 "sale_id = '$id'";
637 sqlQuery($query);
639 else if ($code_type == 'TAX') {
640 // In the SL case taxes show up on the invoice as line items.
641 // Otherwise we gotta save them somewhere, and in the billing
642 // table with a code type of TAX seems easiest.
643 // They will have to be stripped back out when building this
644 // script's input form.
645 addBilling($form_encounter, 'TAX', 'TAX', 'Taxes', $form_pid, 0, 0,
646 '', '', $amount, '', '', 1);
648 else {
649 // Because there is no insurance here, there is no need for a claims
650 // table entry and so we do not call updateClaim(). Note we should not
651 // eliminate billed and bill_date from the billing table!
652 $query = "UPDATE billing SET fee = '$amount', billed = 1, " .
653 "bill_date = NOW() WHERE id = '$id'";
654 sqlQuery($query);
658 // Post discount.
659 if ($_POST['form_discount']) {
660 $amount = sprintf('%01.2f', trim($_POST['form_discount']));
661 $memo = xl('Discount');
662 if ($INTEGRATED_AR) {
663 $time = date('Y-m-d H:i:s');
664 $query = "INSERT INTO ar_activity ( " .
665 "pid, encounter, code, modifier, payer_type, post_user, post_time, " .
666 "session_id, memo, adj_amount " .
667 ") VALUES ( " .
668 "'$form_pid', " .
669 "'$form_encounter', " .
670 "'', " .
671 "'', " .
672 "'0', " .
673 "'" . $_SESSION['authUserID'] . "', " .
674 "'$time', " .
675 "'0', " .
676 "'$memo', " .
677 "'$amount' " .
678 ")";
679 sqlStatement($query);
681 else {
682 $msg = invoice_add_line_item($invoice_info, 'DISCOUNT',
683 '', $memo, 0 - $amount);
684 if ($msg) die($msg);
688 // Post payment.
689 if ($_POST['form_amount']) {
690 $amount = sprintf('%01.2f', trim($_POST['form_amount']));
691 $form_source = trim($_POST['form_source']);
692 $paydesc = trim($_POST['form_method']);
693 if ($INTEGRATED_AR) {
694 // Post the payment as a billed copay into the billing table.
695 // Maybe this should even be done for the SL case.
696 if (!empty($form_source)) $paydesc .= " $form_source";
697 addBilling($form_encounter, 'COPAY', $amount, $paydesc, $form_pid,
698 0, 0, '', '', 0 - $amount, '', '', 1);
700 else {
701 $msg = invoice_add_line_item($invoice_info, 'COPAY',
702 $paydesc, $form_source, 0 - $amount);
703 if ($msg) die($msg);
707 if (!$INTEGRATED_AR) {
708 $msg = invoice_post($invoice_info);
709 if ($msg) die($msg);
712 generate_receipt($form_pid, $form_encounter);
713 exit();
716 // If an encounter ID was given, then we must generate a receipt.
718 if (!empty($_GET['enc'])) {
719 generate_receipt($pid, $_GET['enc']);
720 exit();
723 // Get the unbilled billing table items and product sales for
724 // this patient.
726 $query = "SELECT id, date, code_type, code, modifier, code_text, " .
727 "provider_id, payer_id, units, fee, encounter " .
728 "FROM billing WHERE pid = '$pid' AND activity = 1 AND " .
729 "billed = 0 AND code_type != 'TAX' " .
730 "ORDER BY encounter DESC, id ASC";
731 $bres = sqlStatement($query);
733 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, " .
734 "s.quantity, s.encounter, s.drug_id, d.name, r.provider_id " .
735 "FROM drug_sales AS s " .
736 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
737 "LEFT OUTER JOIN prescriptions AS r ON r.id = s.prescription_id " .
738 "WHERE s.pid = '$pid' AND s.billed = 0 " .
739 "ORDER BY s.encounter DESC, s.sale_id ASC";
740 $dres = sqlStatement($query);
742 // If there are none, just redisplay the last receipt and exit.
744 if (mysql_num_rows($bres) == 0 && mysql_num_rows($dres) == 0) {
745 generate_receipt($pid);
746 exit();
749 // Get the valid practitioners, including those not active.
750 $arr_users = array();
751 $ures = sqlStatement("SELECT id, username FROM users WHERE " .
752 "( authorized = 1 OR info LIKE '%provider%' ) AND username != ''");
753 while ($urow = sqlFetchArray($ures)) {
754 $arr_users[$urow['id']] = '1';
757 // Now write a data entry form:
758 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
759 // List unbilled product sales for the patient.
760 // Present an editable dollar amount for each line item, a total
761 // which is also the default value of the input payment amount,
762 // and OK and Cancel buttons.
764 <html>
765 <head>
766 <link rel='stylesheet' href='<?php echo $css_header ?>' type='text/css'>
767 <title><?php xl('Patient Checkout','e'); ?></title>
768 <style>
769 </style>
770 <style type="text/css">@import url(../../library/dynarch_calendar.css);</style>
771 <script type="text/javascript" src="../../library/textformat.js"></script>
772 <script type="text/javascript" src="../../library/dynarch_calendar.js"></script>
773 <script type="text/javascript" src="../../library/dynarch_calendar_en.js"></script>
774 <script type="text/javascript" src="../../library/dynarch_calendar_setup.js"></script>
775 <script type="text/javascript" src="../../library/dialog.js"></script>
776 <script type="text/javascript" src="../../library/js/jquery-1.2.2.min.js"></script>
777 <script language="JavaScript">
778 var mypcc = '<?php echo $GLOBALS['phone_country_code'] ?>';
780 <?php require($GLOBALS['srcdir'] . "/restoreSession.php"); ?>
782 // This clears the tax line items in preparation for recomputing taxes.
783 function clearTax(visible) {
784 var f = document.forms[0];
785 for (var lino = 0; true; ++lino) {
786 var pfx = 'line[' + lino + ']';
787 if (! f[pfx + '[code_type]']) break;
788 if (f[pfx + '[code_type]'].value != 'TAX') continue;
789 f[pfx + '[price]'].value = '0.00';
790 if (visible) f[pfx + '[amount]'].value = '0.00';
794 // For a given tax ID and amount, compute the tax on that amount and add it
795 // to the "price" (same as "amount") of the corresponding tax line item.
796 // Note the tax line items include their "taxrate" to make this easy.
797 function addTax(rateid, amount, visible) {
798 if (rateid.length == 0) return 0;
799 var f = document.forms[0];
800 for (var lino = 0; true; ++lino) {
801 var pfx = 'line[' + lino + ']';
802 if (! f[pfx + '[code_type]']) break;
803 if (f[pfx + '[code_type]'].value != 'TAX') continue;
804 if (f[pfx + '[code]'].value != rateid) continue;
805 var tax = amount * parseFloat(f[pfx + '[taxrates]'].value);
806 tax = parseFloat(tax.toFixed(2));
807 var cumtax = parseFloat(f[pfx + '[price]'].value) + tax;
808 f[pfx + '[price]'].value = cumtax.toFixed(2); // requires JS 1.5
809 if (visible) f[pfx + '[amount]'].value = cumtax.toFixed(2); // requires JS 1.5
810 if (isNaN(tax)) alert('Tax rate not numeric at line ' + lino);
811 return tax;
813 return 0;
816 // This mess recomputes the invoice total and optionally applies a discount.
817 function computeDiscountedTotals(discount, visible) {
818 clearTax(visible);
819 var f = document.forms[0];
820 var total = 0.00;
821 for (var lino = 0; f['line[' + lino + '][code_type]']; ++lino) {
822 var code_type = f['line[' + lino + '][code_type]'].value;
823 // price is price per unit when the form was originally generated.
824 // By contrast, amount is the dynamically-generated discounted line total.
825 var price = parseFloat(f['line[' + lino + '][price]'].value);
826 if (isNaN(price)) alert('Price not numeric at line ' + lino);
827 if (code_type == 'COPAY' || code_type == 'TAX') {
828 // This works because the tax lines come last.
829 total += parseFloat(price.toFixed(2));
830 continue;
832 var units = f['line[' + lino + '][units]'].value;
833 var amount = price * units;
834 /******************************************************************
835 if (discount > 0) {
836 // Force a discounted amount based on a whole number of cents per unit.
837 amount = ((amount * (100 - discount) / 100) / units).toFixed(2) * units;
839 ******************************************************************/
840 amount = parseFloat(amount.toFixed(2));
841 if (visible) f['line[' + lino + '][amount]'].value = amount.toFixed(2);
842 total += amount;
843 var taxrates = f['line[' + lino + '][taxrates]'].value;
844 var taxids = taxrates.split(':');
845 for (var j = 0; j < taxids.length; ++j) {
846 addTax(taxids[j], amount, visible);
849 return total - discount;
852 // Recompute displayed amounts with any discount applied.
853 function computeTotals() {
854 var f = document.forms[0];
855 var discount = parseFloat(f.form_discount.value);
856 if (isNaN(discount)) discount = 0;
857 /*******************************************************************
858 <?php if ($GLOBALS['discount_by_money']) { ?>
859 // This site discounts by dollars instead of percentage.
860 // We first compute an undiscounted total and then convert
861 // the dollar discount to a percentage of the total, This might
862 // introduce a bit of round-off error.
863 discount = 100 * discount / computeDiscountedTotals(0, false);
864 <?php } ?>
865 if (isNaN(discount) || discount > 1000) discount = 0;
866 if (discount > 100) discount = 100;
867 if (discount < 0 ) discount = 0;
868 *******************************************************************/
869 var total = computeDiscountedTotals(discount, true);
870 f.form_amount.value = total.toFixed(2);
871 return true;
874 </script>
875 </head>
877 <body class="body_top">
879 <form method='post' action='pos_checkout.php'>
880 <input type='hidden' name='form_pid' value='<?php echo $pid ?>' />
882 <center>
885 <table cellspacing='5'>
886 <tr>
887 <td colspan='3' align='center'>
888 <b><?php xl('Patient Checkout for ','e'); ?><?php echo $patdata['fname'] . " " .
889 $patdata['lname'] . " (" . $patdata['pubpid'] . ")" ?></b>
890 </td>
891 </tr>
892 <tr>
893 <td><b><?php xl('Date','e'); ?></b></td>
894 <td><b><?php xl('Description','e'); ?></b></td>
895 <td align='right'><b><?php xl('Qty','e'); ?></b></td>
896 <td align='right'><b><?php xl('Amount','e'); ?></b></td>
897 </tr>
898 <?php
899 $inv_encounter = '';
900 $inv_date = '';
901 $inv_provider = 0;
902 $inv_payer = 0;
903 $gcac_related_visit = false;
905 // Process billing table items. Note this includes co-pays.
906 // Items that are not allowed to have a fee are skipped.
908 while ($brow = sqlFetchArray($bres)) {
909 // Skip all but the most recent encounter.
910 if ($inv_encounter && $brow['encounter'] != $inv_encounter) continue;
912 $thisdate = substr($brow['date'], 0, 10);
913 $code_type = $brow['code_type'];
915 // Collect tax rates, related code and provider ID.
916 $taxrates = '';
917 $related_code = '';
918 if (!empty($code_types[$code_type]['fee'])) {
919 $query = "SELECT taxrates, related_code FROM codes WHERE code_type = '" .
920 $code_types[$code_type]['id'] . "' AND " .
921 "code = '" . $brow['code'] . "' AND ";
922 if ($brow['modifier']) {
923 $query .= "modifier = '" . $brow['modifier'] . "'";
924 } else {
925 $query .= "(modifier IS NULL OR modifier = '')";
927 $query .= " LIMIT 1";
928 $tmp = sqlQuery($query);
929 $taxrates = $tmp['taxrates'];
930 $related_code = $tmp['related_code'];
931 markTaxes($taxrates);
932 /*****************************************************************
933 // catch provider id from non-copay items if we do not have it yet.
934 if (!$inv_provider && !empty($arr_users[$brow['provider_id']]))
935 $inv_provider = $brow['provider_id'] + 0;
936 *****************************************************************/
939 write_form_line($code_type, $brow['code'], $brow['id'], $thisdate,
940 ucfirst(strtolower($brow['code_text'])), $brow['fee'], $brow['units'],
941 $taxrates);
942 if (!$inv_encounter) $inv_encounter = $brow['encounter'];
943 $inv_payer = $brow['payer_id'];
944 if (!$inv_date || $inv_date < $thisdate) $inv_date = $thisdate;
946 // Custom logic for IPPF to determine if a GCAC issue applies.
947 if ($GLOBALS['ippf_specific'] && $related_code) {
948 $relcodes = explode(';', $related_code);
949 foreach ($relcodes as $codestring) {
950 if ($codestring === '') continue;
951 list($codetype, $code) = explode(':', $codestring);
952 if ($codetype !== 'IPPF') continue;
953 if (preg_match('/^25222/', $code)) $gcac_related_visit = true;
958 // Process drug sales / products.
960 while ($drow = sqlFetchArray($dres)) {
961 if ($inv_encounter && $drow['encounter'] && $drow['encounter'] != $inv_encounter) continue;
963 $thisdate = $drow['sale_date'];
964 if (!$inv_encounter) $inv_encounter = $drow['encounter'];
966 if (!$inv_provider && !empty($arr_users[$drow['provider_id']]))
967 $inv_provider = $drow['provider_id'] + 0;
969 if (!$inv_date || $inv_date < $thisdate) $inv_date = $thisdate;
971 // Accumulate taxes for this product.
972 $tmp = sqlQuery("SELECT taxrates FROM drug_templates WHERE drug_id = '" .
973 $drow['drug_id'] . "' ORDER BY selector LIMIT 1");
974 // accumTaxes($drow['fee'], $tmp['taxrates']);
975 $taxrates = $tmp['taxrates'];
976 markTaxes($taxrates);
978 write_form_line('PROD', $drow['drug_id'], $drow['sale_id'],
979 $thisdate, $drow['name'], $drow['fee'], $drow['quantity'], $taxrates);
982 // Write a form line for each tax that has money, adding to $total.
983 foreach ($taxes as $key => $value) {
984 if ($value[2]) {
985 write_form_line('TAX', $key, $key, date('Y-m-d'), $value[0], 0, 1, $value[1]);
989 // Note that we don't try to get anything from the ar_activity table. Since
990 // this is the checkout, nothing should be there yet for this invoice.
992 /*********************************************************************
993 // If there were only products without prescriptions then there is no
994 // provider yet, so get it from the encounter.
995 if ($inv_encounter && !$inv_provider) {
996 $erow = sqlQuery("SELECT users.id FROM forms, users WHERE " .
997 "forms.encounter = '$inv_encounter' AND " .
998 "forms.formdir = 'newpatient' AND " .
999 "users.username = forms.user LIMIT 1");
1000 $inv_provider = $erow['id'] + 0;
1002 *********************************************************************/
1003 if ($inv_encounter) {
1004 $erow = sqlQuery("SELECT provider_id FROM form_encounter WHERE " .
1005 "pid = '$pid' AND encounter = '$inv_encounter' " .
1006 "ORDER BY id DESC LIMIT 1");
1007 $inv_provider = $erow['provider_id'] + 0;
1010 </table>
1013 <table border='0' cellspacing='8'>
1015 <tr>
1016 <td>
1017 <?php xl('Discount Amount','e'); ?>:
1018 </td>
1019 <td>
1020 <input type='text' name='form_discount' size='6' maxlength='8' value=''
1021 style='text-align:right' onkeyup='computeTotals()'>
1022 </td>
1023 </tr>
1025 <tr>
1026 <td>
1027 <?php xl('Payment Method','e'); ?>:
1028 </td>
1029 <td>
1030 <select name='form_method'>
1032 foreach ($payment_methods as $value) {
1033 echo " <option value='$value'";
1034 echo ">$value</option>\n";
1037 </select>
1038 </td>
1039 </tr>
1041 <tr>
1042 <td>
1043 <?php xl('Check/Reference Number','e'); ?>:
1044 </td>
1045 <td>
1046 <input type='text' name='form_source' size='10' value=''>
1047 </td>
1048 </tr>
1050 <tr>
1051 <td>
1052 <?php xl('Amount Paid','e'); ?>:
1053 </td>
1054 <td>
1055 <input type='text' name='form_amount' size='10' value='0.00'>
1056 </td>
1057 </tr>
1059 <tr>
1060 <td>
1061 <?php xl('Posting Date','e'); ?>:
1062 </td>
1063 <td>
1064 <input type='text' size='10' name='form_date' id='form_date'
1065 value='<?php echo $inv_date ?>'
1066 title='yyyy-mm-dd date of service'
1067 onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)' />
1068 <img src='../pic/show_calendar.gif' align='absbottom' width='24' height='22'
1069 id='img_date' border='0' alt='[?]' style='cursor:pointer'
1070 title='Click here to choose a date'>
1071 </td>
1072 </tr>
1074 <tr>
1075 <td colspan='2' align='center'>
1076 &nbsp;<br>
1077 <input type='submit' name='form_save' value='<?php xl('Save','e'); ?>' /> &nbsp;
1078 <?php if (empty($_GET['framed'])) { ?>
1079 <input type='button' value='Cancel' onclick='window.close()' />
1080 <?php } ?>
1081 <input type='hidden' name='form_provider' value='<?php echo $inv_provider ?>' />
1082 <input type='hidden' name='form_payer' value='<?php echo $inv_payer ?>' />
1083 <input type='hidden' name='form_encounter' value='<?php echo $inv_encounter ?>' />
1084 </td>
1085 </tr>
1087 </table>
1088 </center>
1090 </form>
1092 <script language='JavaScript'>
1093 Calendar.setup({inputField:"form_date", ifFormat:"%Y-%m-%d", button:"img_date"});
1094 computeTotals();
1095 <?php
1096 // The following is removed, perhaps temporarily, because gcac reporting
1097 // no longer depends on gcac issues. -- Rod 2009-08-11
1098 /*********************************************************************
1099 // Custom code for IPPF. Try to make sure that a GCAC issue is linked to this
1100 // visit if it contains GCAC-related services.
1101 if ($gcac_related_visit) {
1102 $grow = sqlQuery("SELECT l.id, l.title, l.begdate, ie.pid " .
1103 "FROM lists AS l " .
1104 "LEFT JOIN issue_encounter AS ie ON ie.pid = l.pid AND " .
1105 "ie.encounter = '$inv_encounter' AND ie.list_id = l.id " .
1106 "WHERE l.pid = '$pid' AND " .
1107 "l.activity = 1 AND l.type = 'ippf_gcac' " .
1108 "ORDER BY ie.pid DESC, l.begdate DESC LIMIT 1");
1109 // Note that reverse-ordering by ie.pid is a trick for sorting
1110 // issues linked to the encounter (non-null values) first.
1111 if (empty($grow['pid'])) { // if there is no linked GCAC issue
1112 if (!empty($grow)) { // there is one that is not linked
1113 echo " if (confirm('" . xl('OK to link the GCAC issue dated') . " " .
1114 $grow['begdate'] . " " . xl('to this visit?') . "')) {\n";
1115 echo " $.getScript('link_issue_to_encounter.php?issue=" . $grow['id'] .
1116 "&thisenc=$inv_encounter');\n";
1117 echo " } else";
1119 echo " if (confirm('" . xl('Are you prepared to complete a new GCAC issue for this visit?') . "')) {\n";
1120 echo " dlgopen('summary/add_edit_issue.php?thisenc=$inv_encounter" .
1121 "&thistype=ippf_gcac', '_blank', 700, 600);\n";
1122 echo " } else {\n";
1123 echo " $.getScript('link_issue_to_encounter.php?thisenc=$inv_encounter');\n";
1124 echo " }\n";
1126 } // end if ($gcac_related_visit)
1127 *********************************************************************/
1128 if ($gcac_related_visit) {
1129 $grow = sqlQuery("SELECT COUNT(*) AS count FROM forms " .
1130 "WHERE pid = '$pid' AND encounter = '$inv_encounter' AND " .
1131 "deleted = 0 AND formdir = 'LBFgcac'");
1132 if (empty($grow['count'])) { // if there is no gcac form
1133 echo " alert('" . xl('A GCAC visit form should be added to this visit.') . "');\n";
1135 } // end if ($gcac_related_visit)
1137 </script>
1139 </body>
1140 </html>