2 // Copyright (C) 2006-2010 Rod Roark <rod@sunsetsystems.com>
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
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
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,
30 // If this user has 'irnpool' set
31 // on display of checkout form
32 // show pending next invoice number
33 // on applying checkout
34 // save next invoice number to form_encounter
35 // compute new next invoice number
37 // show invoice number
39 require_once("../globals.php");
40 require_once("$srcdir/acl.inc");
41 require_once("$srcdir/patient.inc");
42 require_once("$srcdir/billing.inc");
43 require_once("$srcdir/sql-ledger.inc");
44 require_once("$srcdir/freeb/xmlrpc.inc");
45 require_once("$srcdir/freeb/xmlrpcs.inc");
46 require_once("$srcdir/formatting.inc.php");
47 require_once("$srcdir/formdata.inc.php");
48 require_once("../../custom/code_types.inc.php");
50 $currdecimals = $GLOBALS['currency_decimals'];
52 $INTEGRATED_AR = $GLOBALS['oer_config']['ws_accounting']['enabled'] === 2;
54 $details = empty($_GET['details']) ?
0 : 1;
56 $patient_id = empty($_GET['ptid']) ?
$pid : 0 +
$_GET['ptid'];
58 // Get the patient's name and chart number.
59 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
61 // Get the "next invoice reference number" from this user's pool.
63 function getInvoiceRefNumber() {
64 $trow = sqlQuery("SELECT lo.notes " .
65 "FROM users AS u, list_options AS lo " .
66 "WHERE u.username = '" . $_SESSION['authUser'] . "' AND " .
67 "lo.list_id = 'irnpool' AND lo.option_id = u.irnpool LIMIT 1");
68 return empty($trow['notes']) ?
'' : $trow['notes'];
71 // Increment the "next invoice reference number" of this user's pool.
72 // This identifies the "digits" portion of that number and adds 1 to it.
73 // If it contains more than one string of digits, the last is used.
75 function updateInvoiceRefNumber() {
76 $irnumber = getInvoiceRefNumber();
77 // Here "?" specifies a minimal match, to get the most digits possible:
78 if (preg_match('/^(.*?)(\d+)(\D*)$/', $irnumber, $matches)) {
79 $newdigs = sprintf('%0' . strlen($matches[2]) . 'd', $matches[2] +
1);
80 $newnumber = add_escape_custom($matches[1] . $newdigs . $matches[3]);
81 sqlStatement("UPDATE users AS u, list_options AS lo " .
82 "SET lo.notes = '$newnumber' WHERE " .
83 "u.username = '" . $_SESSION['authUser'] . "' AND " .
84 "lo.list_id = 'irnpool' AND lo.option_id = u.irnpool");
89 //////////////////////////////////////////////////////////////////////
90 // The following functions are inline here temporarily, and should be
91 // moved to an includable module for common use. In particular
92 // WSClaim.class.php should be rewritten to use them.
93 //////////////////////////////////////////////////////////////////////
95 // Initialize the array of invoice information for posting to the
98 function invoice_initialize(& $invoice_info, $patient_id, $provider_id,
99 $payer_id = 0, $encounter = 0, $dosdate = '')
101 $db = $GLOBALS['adodb']['db'];
103 // Get foreign ID (customer) for patient.
104 $sql = "SELECT foreign_id from integration_mapping as im " .
105 "LEFT JOIN patient_data as pd on im.local_id=pd.id " .
108 "' and im.local_table='patient_data' and im.foreign_table='customer'";
109 $result = $db->Execute($sql);
110 if($result && !$result->EOF
) {
111 $foreign_patient_id = $result->fields
['foreign_id'];
114 return "Patient '" . $patient_id . "' has not yet been posted to the accounting system.";
117 // Get foreign ID (salesman) for provider.
118 $sql = "SELECT foreign_id from integration_mapping WHERE " .
119 "local_id = $provider_id AND local_table='users' and foreign_table='salesman'";
120 $result = $db->Execute($sql);
121 if($result && !$result->EOF
) {
122 $foreign_provider_id = $result->fields
['foreign_id'];
125 return "Provider '" . $provider_id . "' has not yet been posted to the accounting system.";
128 // Get foreign ID (customer) for insurance payer.
129 if ($payer_id && ! $GLOBALS['insurance_companies_are_not_customers']) {
130 $sql = "SELECT foreign_id from integration_mapping WHERE " .
131 "local_id = $payer_id AND local_table = 'insurance_companies' AND foreign_table='customer'";
132 $result = $db->Execute($sql);
133 if($result && !$result->EOF
) {
134 $foreign_payer_id = $result->fields
['foreign_id'];
137 return "Payer '" . $payer_id . "' has not yet been posted to the accounting system.";
140 $foreign_payer_id = $payer_id;
143 // Create invoice notes for the new invoice that list the patient's
144 // insurance plans. This is so that when payments are posted, the user
145 // can easily see if a secondary claim needs to be submitted.
149 foreach (array("primary", "secondary", "tertiary") as $instype) {
151 $sql = "SELECT insurance_companies.name " .
152 "FROM insurance_data, insurance_companies WHERE " .
153 "insurance_data.pid = $patient_id AND " .
154 "insurance_data.type = '$instype' AND " .
155 "insurance_companies.id = insurance_data.provider " .
156 "ORDER BY insurance_data.date DESC LIMIT 1";
157 $result = $db->Execute($sql);
158 if ($result && !$result->EOF
&& $result->fields
['name']) {
159 if ($insnotes) $insnotes .= "\n";
160 $insnotes .= "Ins$insno: " . $result->fields
['name'];
163 $invoice_info['notes'] = $insnotes;
165 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $dosdate, $matches)) {
166 $dosdate = $matches[2] . '-' . $matches[3] . '-' . $matches[1];
168 $dosdate = date("m-d-Y");
171 $invoice_info['salesman'] = $foreign_provider_id;
172 $invoice_info['customerid'] = $foreign_patient_id;
173 $invoice_info['payer_id'] = $foreign_payer_id;
174 $invoice_info['invoicenumber'] = $patient_id . "." . $encounter;
175 $invoice_info['dosdate'] = $dosdate;
176 $invoice_info['items'] = array();
177 $invoice_info['total'] = '0.00';
182 function invoice_add_line_item(& $invoice_info, $code_type, $code,
183 $code_text, $amount, $units=1)
185 $units = max(1, intval(trim($units)));
186 $amount = sprintf("%01.2f", $amount);
187 $price = $amount / $units;
188 $tmp = sprintf("%01.2f", $price);
189 if (abs($price - $tmp) < 0.000001) $price = $tmp;
191 $tii['maincode'] = $code;
192 $tii['itemtext'] = "$code_type:$code";
193 if ($code_text) $tii['itemtext'] .= " $code_text";
194 $tii['qty'] = $units;
195 $tii['price'] = $price;
196 $tii['glaccountid'] = $GLOBALS['oer_config']['ws_accounting']['income_acct'];
197 $invoice_info['total'] = sprintf("%01.2f", $invoice_info['total'] +
$amount);
198 $invoice_info['items'][] = $tii;
202 function invoice_post(& $invoice_info)
204 $function['ezybiz.add_invoice'] = array(new xmlrpcval($invoice_info, "struct"));
206 list($name, $var) = each($function);
207 $f = new xmlrpcmsg($name, $var);
209 $c = new xmlrpc_client($GLOBALS['oer_config']['ws_accounting']['url'],
210 $GLOBALS['oer_config']['ws_accounting']['server'],
211 $GLOBALS['oer_config']['ws_accounting']['port']);
213 $c->setCredentials($GLOBALS['oer_config']['ws_accounting']['username'],
214 $GLOBALS['oer_config']['ws_accounting']['password']);
217 if (!$r) return "XMLRPC send failed";
219 // We are not doing anything with the return value yet... should we?
221 if (is_object($tv)) {
222 $value = $tv->getval();
228 if ($r->faultCode()) {
229 return "Fault: Code: " . $r->faultCode() . " Reason '" . $r->faultString() . "'";
235 ///////////// End of SQL-Ledger invoice posting functions ////////////
237 // Output HTML for an invoice line item.
240 function receiptDetailLine($svcdate, $description, $amount, $quantity) {
241 global $prevsvcdate, $details;
242 if (!$details) return;
243 $amount = sprintf('%01.2f', $amount);
244 if (empty($quantity)) $quantity = 1;
245 $price = sprintf('%01.4f', $amount / $quantity);
246 $tmp = sprintf('%01.2f', $price);
247 if ($price == $tmp) $price = $tmp;
249 echo " <td>" . ($svcdate == $prevsvcdate ?
' ' : oeFormatShortDate($svcdate)) . "</td>\n";
250 echo " <td>$description</td>\n";
251 echo " <td align='right'>" . oeFormatMoney($price) . "</td>\n";
252 echo " <td align='right'>$quantity</td>\n";
253 echo " <td align='right'>" . oeFormatMoney($amount) . "</td>\n";
255 $prevsvcdate = $svcdate;
258 // Output HTML for an invoice payment.
260 function receiptPaymentLine($paydate, $amount, $description='') {
261 $amount = sprintf('%01.2f', 0 - $amount); // make it negative
263 echo " <td>" . oeFormatShortDate($paydate) . "</td>\n";
264 echo " <td>" . xl('Payment') . " $description</td>\n";
265 echo " <td colspan='2'> </td>\n";
266 echo " <td align='right'>" . oeFormatMoney($amount) . "</td>\n";
270 // Generate a receipt from the last-billed invoice for this patient,
271 // or for the encounter specified as a GET parameter.
273 function generate_receipt($patient_id, $encounter=0) {
274 global $sl_err, $sl_cash_acc, $css_header, $details, $INTEGRATED_AR;
276 // Get details for what we guess is the primary facility.
277 $frow = sqlQuery("SELECT * FROM facility " .
278 "ORDER BY billing_location DESC, accepts_assignment DESC, id LIMIT 1");
280 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code,providerID');
282 // Get the most recent invoice data or that for the specified encounter.
284 // Adding a provider check so that their info can be displayed on receipts
285 if ($INTEGRATED_AR) {
287 $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " .
288 "WHERE pid = '$patient_id' AND encounter = '$encounter'");
290 $ferow = sqlQuery("SELECT id, date, encounter, provider_id FROM form_encounter " .
291 "WHERE pid = '$patient_id' " .
292 "ORDER BY id DESC LIMIT 1");
294 if (empty($ferow)) die(xl("This patient has no activity."));
295 $trans_id = $ferow['id'];
296 $encounter = $ferow['encounter'];
297 $svcdate = substr($ferow['date'], 0, 10);
299 if ($GLOBALS['receipts_by_provider']){
300 if (isset($ferow['provider_id']) ) {
301 $encprovider = $ferow['provider_id'];
302 } else if (isset($patdata['providerID'])){
303 $encprovider = $patdata['providerID'];
304 } else { $encprovider = -1; }
308 $providerrow = sqlQuery("SELECT fname, mname, lname, title, street, streetb, " .
309 "city, state, zip, phone, fax FROM users WHERE id = $encprovider");
315 $arres = SLQuery("SELECT * FROM ar WHERE " .
316 "invnumber LIKE '$patient_id.%' " .
317 "ORDER BY id DESC LIMIT 1");
318 if ($sl_err) die($sl_err);
319 if (!SLRowCount($arres)) die(xl("This patient has no activity."));
320 $arrow = SLGetRow($arres, 0);
322 $trans_id = $arrow['id'];
324 // Determine the date of service. An 8-digit encounter number is
325 // presumed to be a date of service imported during conversion or
326 // associated with prescriptions only. Otherwise look it up in the
327 // form_encounter table.
330 list($trash, $encounter) = explode(".", $arrow['invnumber']);
331 if (strlen($encounter) >= 8) {
332 $svcdate = substr($encounter, 0, 4) . "-" . substr($encounter, 4, 2) .
333 "-" . substr($encounter, 6, 2);
335 else if ($encounter) {
336 $tmp = sqlQuery("SELECT date FROM form_encounter WHERE " .
337 "encounter = $encounter");
338 $svcdate = substr($tmp['date'], 0, 10);
340 } // end not $INTEGRATED_AR
342 // Get invoice reference number.
343 $encrow = sqlQuery("SELECT invoice_refno FROM form_encounter WHERE " .
344 "pid = '$patient_id' AND encounter = '$encounter' LIMIT 1");
345 $invoice_refno = $encrow['invoice_refno'];
349 <?php
html_header_show(); ?
>
350 <link rel
='stylesheet' href
='<?php echo $css_header ?>' type
='text/css'>
351 <title
><?php
xl('Receipt for Payment','e'); ?
></title
>
352 <script type
="text/javascript" src
="../../library/dialog.js"></script
>
353 <script language
="JavaScript">
355 <?php
require($GLOBALS['srcdir'] . "/restoreSession.php"); ?
>
357 // Process click on Print button.
359 var divstyle
= document
.getElementById('hideonprint').style
;
360 divstyle
.display
= 'none';
365 // Process click on Delete button.
366 function deleteme() {
367 dlgopen('deleter.php?billing=<?php echo "$patient_id.$encounter"; ?>', '_blank', 500, 450);
371 // Called by the deleteme.php window on a successful delete.
372 function imdeleted() {
378 <body
class="body_top">
381 if ( $GLOBALS['receipts_by_provider'] && !empty($providerrow) ) { printProviderHeader($providerrow); }
382 else { printFacilityHeader($frow); }
385 echo xl("Receipt Generated") . ":" . date(' F j, Y');
386 if ($invoice_refno) echo " " . xl("Invoice Number") . ": " . $invoice_refno . " " . xl("Service Date") . ": " . $svcdate;
392 <?php
echo $patdata['fname'] . ' ' . $patdata['mname'] . ' ' . $patdata['lname'] ?
>
393 <br
><?php
echo $patdata['street'] ?
>
394 <br
><?php
echo $patdata['city'] . ', ' . $patdata['state'] . ' ' . $patdata['postal_code'] ?
>
398 <table cellpadding
='5'>
400 <td
><b
><?php
xl('Date','e'); ?
></b
></td
>
401 <td
><b
><?php
xl('Description','e'); ?
></b
></td
>
402 <td align
='right'><b
><?php
echo $details ?
xl('Price') : ' '; ?
></b
></td
>
403 <td align
='right'><b
><?php
echo $details ?
xl('Qty' ) : ' '; ?
></b
></td
>
404 <td align
='right'><b
><?php
xl('Total','e'); ?
></b
></td
>
410 if ($INTEGRATED_AR) {
412 $inres = sqlStatement("SELECT s.sale_id, s.sale_date, s.fee, " .
413 "s.quantity, s.drug_id, d.name " .
414 "FROM drug_sales AS s LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
415 // "WHERE s.pid = '$patient_id' AND s.encounter = '$encounter' AND s.fee != 0 " .
416 "WHERE s.pid = '$patient_id' AND s.encounter = '$encounter' " .
417 "ORDER BY s.sale_id");
418 while ($inrow = sqlFetchArray($inres)) {
419 $charges +
= sprintf('%01.2f', $inrow['fee']);
420 receiptDetailLine($inrow['sale_date'], $inrow['name'],
421 $inrow['fee'], $inrow['quantity']);
423 // Service and tax items
424 $inres = sqlStatement("SELECT * FROM billing WHERE " .
425 "pid = '$patient_id' AND encounter = '$encounter' AND " .
426 // "code_type != 'COPAY' AND activity = 1 AND fee != 0 " .
427 "code_type != 'COPAY' AND activity = 1 " .
429 while ($inrow = sqlFetchArray($inres)) {
430 $charges +
= sprintf('%01.2f', $inrow['fee']);
431 receiptDetailLine($svcdate, $inrow['code_text'],
432 $inrow['fee'], $inrow['units']);
435 $inres = sqlStatement("SELECT " .
436 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
437 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
438 "FROM ar_activity AS a " .
439 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
440 "a.pid = '$patient_id' AND a.encounter = '$encounter' AND " .
441 "a.adj_amount != 0 " .
442 "ORDER BY s.check_date, a.sequence_no");
443 while ($inrow = sqlFetchArray($inres)) {
444 $charges -= sprintf('%01.2f', $inrow['adj_amount']);
445 $payer = empty($inrow['payer_type']) ?
'Pt' : ('Ins' . $inrow['payer_type']);
446 receiptDetailLine($svcdate, $payer . ' ' . $inrow['memo'],
447 0 - $inrow['adj_amount'], 1);
449 } // end $INTEGRATED_AR
451 // Request all line items with money belonging to the invoice.
452 $inres = SLQuery("SELECT * FROM invoice WHERE " .
453 "trans_id = $trans_id AND sellprice != 0 ORDER BY id");
454 if ($sl_err) die($sl_err);
455 for ($irow = 0; $irow < SLRowCount($inres); ++
$irow) {
456 $row = SLGetRow($inres, $irow);
457 $amount = sprintf('%01.2f', $row['sellprice'] * $row['qty']);
459 $desc = preg_replace('/^.{1,6}:/', '', $row['description']);
460 receiptDetailLine($svcdate, $desc, $amount, $row['qty']);
462 } // end not $INTEGRATED_AR
466 <td colspan
='5'> 
;</td
>
469 <td
><?php
echo oeFormatShortDate($svcdispdate); ?
></td
>
470 <td
><b
><?php
xl('Total Charges','e'); ?
></b
></td
>
471 <td align
='right'> 
;</td
>
472 <td align
='right'> 
;</td
>
473 <td align
='right'><?php
echo oeFormatMoney($charges, true) ?
></td
>
476 <td colspan
='5'> 
;</td
>
480 if ($INTEGRATED_AR) {
482 $inres = sqlStatement("SELECT fee, code_text FROM billing WHERE " .
483 "pid = '$patient_id' AND encounter = '$encounter' AND " .
484 "code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
486 while ($inrow = sqlFetchArray($inres)) {
487 $charges +
= sprintf('%01.2f', $inrow['fee']);
488 receiptPaymentLine($svcdate, 0 - $inrow['fee'], $inrow['code_text']);
490 // Get other payments.
491 $inres = sqlStatement("SELECT " .
492 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
493 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
494 "FROM ar_activity AS a " .
495 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
496 "a.pid = '$patient_id' AND a.encounter = '$encounter' AND " .
497 "a.pay_amount != 0 " .
498 "ORDER BY s.check_date, a.sequence_no");
499 $payer = empty($inrow['payer_type']) ?
'Pt' : ('Ins' . $inrow['payer_type']);
500 while ($inrow = sqlFetchArray($inres)) {
501 $charges -= sprintf('%01.2f', $inrow['pay_amount']);
502 receiptPaymentLine($svcdate, $inrow['pay_amount'],
503 $payer . ' ' . $inrow['reference']);
505 } // end $INTEGRATED_AR
507 $chart_id_cash = SLQueryValue("select id from chart where accno = '$sl_cash_acc'");
508 if ($sl_err) die($sl_err);
509 if (! $chart_id_cash) die("There is no COA entry for cash account '$sl_cash_acc'");
511 // Request all cash entries belonging to the invoice.
512 $atres = SLQuery("SELECT * FROM acc_trans WHERE " .
513 "trans_id = $trans_id AND chart_id = $chart_id_cash ORDER BY transdate");
514 if ($sl_err) die($sl_err);
516 for ($irow = 0; $irow < SLRowCount($atres); ++
$irow) {
517 $row = SLGetRow($atres, $irow);
518 $amount = sprintf('%01.2f', $row['amount']); // negative
520 $rowsource = $row['source'];
521 if (strtolower($rowsource) == 'co-pay') $rowsource = '';
522 receiptPaymentLine($row['transdate'], 0 - $amount, $rowsource);
524 } // end not $INTEGRATED_AR
527 <td colspan
='5'> 
;</td
>
531 <td
><b
><?php
xl('Balance Due','e'); ?
></b
></td
>
532 <td colspan
='2'> 
;</td
>
533 <td align
='right'><?php
echo oeFormatMoney($charges, true) ?
></td
>
537 <div id
='hideonprint'>
540 <a href
='#' onclick
='return printme();'><?php
xl('Print','e'); ?
></a
>
541 <?php
if (acl_check('acct','disc')) { ?
>
542  
; 
; 
; 
; 
;
543 <a href
='#' onclick
='return deleteme();'><?php
xl('Undo Checkout','e'); ?
></a
>
545  
; 
; 
; 
; 
;
546 <?php
if ($details) { ?
>
547 <a href
='pos_checkout.php?details=0&ptid=<?php echo $patient_id; ?>&enc=<?php echo $encounter; ?>'><?php
xl('Hide Details','e'); ?
></a
>
549 <a href
='pos_checkout.php?details=1&ptid=<?php echo $patient_id; ?>&enc=<?php echo $encounter; ?>'><?php
xl('Show Details','e'); ?
></a
>
556 if (!$INTEGRATED_AR) SLClose();
557 } // end function generate_receipt()
559 // Function to output a line item for the input form.
562 function write_form_line($code_type, $code, $id, $date, $description,
563 $amount, $units, $taxrates) {
565 $amount = sprintf("%01.2f", $amount);
566 if (empty($units)) $units = 1;
567 $price = $amount / $units; // should be even cents, but ok here if not
568 if ($code_type == 'COPAY' && !$description) $description = xl('Payment');
570 echo " <td>" . oeFormatShortDate($date);
571 echo "<input type='hidden' name='line[$lino][code_type]' value='$code_type'>";
572 echo "<input type='hidden' name='line[$lino][code]' value='$code'>";
573 echo "<input type='hidden' name='line[$lino][id]' value='$id'>";
574 echo "<input type='hidden' name='line[$lino][description]' value='$description'>";
575 echo "<input type='hidden' name='line[$lino][taxrates]' value='$taxrates'>";
576 echo "<input type='hidden' name='line[$lino][price]' value='$price'>";
577 echo "<input type='hidden' name='line[$lino][units]' value='$units'>";
579 echo " <td>$description</td>";
580 echo " <td align='right'>$units</td>";
581 echo " <td align='right'><input type='text' name='line[$lino][amount]' " .
582 "value='$amount' size='6' maxlength='8'";
583 // Modifying prices requires the acct/disc permission.
584 // if ($code_type == 'TAX' || ($code_type != 'COPAY' && !acl_check('acct','disc')))
585 echo " style='text-align:right;background-color:transparent' readonly";
586 // else echo " style='text-align:right' onkeyup='computeTotals()'";
592 // Create the taxes array. Key is tax id, value is
593 // (description, rate, accumulated total).
595 $pres = sqlStatement("SELECT option_id, title, option_value " .
596 "FROM list_options WHERE list_id = 'taxrate' ORDER BY seq");
597 while ($prow = sqlFetchArray($pres)) {
598 $taxes[$prow['option_id']] = array($prow['title'], $prow['option_value'], 0);
601 // Print receipt header for facility
602 function printFacilityHeader($frow){
603 echo "<p><b>" . $frow['name'] .
604 "<br>" . $frow['street'] .
605 "<br>" . $frow['city'] . ', ' . $frow['state'] . ' ' . $frow['postal_code'] .
606 "<br>" . $frow['phone'] .
611 // Pring receipt header for Provider
612 function printProviderHeader($pvdrow){
613 echo "<p><b>" . $pvdrow['title'] . " " . $pvdrow['fname'] . " " . $pvdrow['mname'] . " " . $pvdrow['lname'] . " " .
614 "<br>" . $pvdrow['street'] .
615 "<br>" . $pvdrow['city'] . ', ' . $pvdrow['state'] . ' ' . $pvdrow['postal_code'] .
616 "<br>" . $pvdrow['phone'] .
621 // Mark the tax rates that are referenced in this invoice.
622 function markTaxes($taxrates) {
624 $arates = explode(':', $taxrates);
625 if (empty($arates)) return;
626 foreach ($arates as $value) {
627 if (!empty($taxes[$value])) $taxes[$value][2] = '1';
631 $payment_methods = array(
640 $alertmsg = ''; // anything here pops up in an alert box
642 // If the Save button was clicked...
644 if ($_POST['form_save']) {
646 // On a save, do the following:
647 // Flag drug_sales and billing items as billed.
648 // Post the corresponding invoice with its payment(s) to sql-ledger
649 // and be careful to use a unique invoice number.
650 // Call the generate-receipt function.
653 $form_pid = $_POST['form_pid'];
654 $form_encounter = $_POST['form_encounter'];
656 // Get the posting date from the form as yyyy-mm-dd.
657 $dosdate = date("Y-m-d");
658 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $_POST['form_date'], $matches)) {
659 $dosdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
662 // If there is no associated encounter (i.e. this invoice has only
663 // prescriptions) then assign an encounter number of the service
664 // date, with an optional suffix to ensure that it's unique.
666 if (! $form_encounter) {
667 $form_encounter = substr($dosdate,0,4) . substr($dosdate,5,2) . substr($dosdate,8,2);
669 if ($INTEGRATED_AR) {
671 $ferow = sqlQuery("SELECT id FROM form_encounter WHERE " .
672 "pid = '$form_pid' AND encounter = '$form_encounter$tmp'");
673 if (empty($ferow)) break;
674 $tmp = $tmp ?
$tmp +
1 : 1;
679 while (SLQueryValue("select id from ar where " .
680 "invnumber = '$form_pid.$form_encounter$tmp'")) {
681 $tmp = $tmp ?
$tmp +
1 : 1;
685 $form_encounter .= $tmp;
688 if ($INTEGRATED_AR) {
689 // Delete any TAX rows from billing because they will be recalculated.
690 sqlStatement("UPDATE billing SET activity = 0 WHERE " .
691 "pid = '$form_pid' AND encounter = '$form_encounter' AND " .
692 "code_type = 'TAX'");
695 // Initialize an array of invoice information for posting.
696 $invoice_info = array();
697 $msg = invoice_initialize($invoice_info, $form_pid,
698 $_POST['form_provider'], $_POST['form_payer'], $form_encounter, $dosdate);
702 $form_amount = $_POST['form_amount'];
703 $lines = $_POST['line'];
705 for ($lino = 0; $lines[$lino]['code_type']; ++
$lino) {
706 $line = $lines[$lino];
707 $code_type = $line['code_type'];
709 $amount = sprintf('%01.2f', trim($line['amount']));
711 if (!$INTEGRATED_AR) {
712 $msg = invoice_add_line_item($invoice_info, $code_type,
713 $line['code'], $line['description'], $amount, $line['units']);
717 if ($code_type == 'PROD') {
718 // Product sales. The fee and encounter ID may have changed.
719 $query = "update drug_sales SET fee = '$amount', " .
720 "encounter = '$form_encounter', billed = 1 WHERE " .
724 else if ($code_type == 'TAX') {
725 // In the SL case taxes show up on the invoice as line items.
726 // Otherwise we gotta save them somewhere, and in the billing
727 // table with a code type of TAX seems easiest.
728 // They will have to be stripped back out when building this
729 // script's input form.
730 addBilling($form_encounter, 'TAX', 'TAX', 'Taxes', $form_pid, 0, 0,
731 '', '', $amount, '', '', 1);
734 // Because there is no insurance here, there is no need for a claims
735 // table entry and so we do not call updateClaim(). Note we should not
736 // eliminate billed and bill_date from the billing table!
737 $query = "UPDATE billing SET fee = '$amount', billed = 1, " .
738 "bill_date = NOW() WHERE id = '$id'";
744 if ($_POST['form_discount']) {
745 if ($GLOBALS['discount_by_money']) {
746 $amount = sprintf('%01.2f', trim($_POST['form_discount']));
749 $amount = sprintf('%01.2f', trim($_POST['form_discount']) * $form_amount / 100);
751 $memo = xl('Discount');
752 if ($INTEGRATED_AR) {
753 $time = date('Y-m-d H:i:s');
754 $query = "INSERT INTO ar_activity ( " .
755 "pid, encounter, code, modifier, payer_type, post_user, post_time, " .
756 "session_id, memo, adj_amount " .
759 "'$form_encounter', " .
763 "'" . $_SESSION['authUserID'] . "', " .
769 sqlStatement($query);
772 $msg = invoice_add_line_item($invoice_info, 'DISCOUNT',
773 '', $memo, 0 - $amount);
779 if ($_POST['form_amount']) {
780 $amount = sprintf('%01.2f', trim($_POST['form_amount']));
781 $form_source = trim($_POST['form_source']);
782 $paydesc = trim($_POST['form_method']);
783 if ($INTEGRATED_AR) {
784 // Post the payment as a billed copay into the billing table.
785 // Maybe this should even be done for the SL case.
786 if (!empty($form_source)) $paydesc .= " $form_source";
787 # jason forced auth line to 1 here
788 addBilling($form_encounter, 'COPAY', $amount, $paydesc, $form_pid,
789 1, 0, '', '', 0 - $amount, '', '', 1);
792 $msg = invoice_add_line_item($invoice_info, 'COPAY',
793 $paydesc, $form_source, 0 - $amount);
798 if (!$INTEGRATED_AR) {
799 $msg = invoice_post($invoice_info);
803 // If applicable, set the invoice reference number.
805 if (isset($_POST['form_irnumber'])) {
806 $invoice_refno = formData('form_irnumber', 'P', true);
809 $invoice_refno = add_escape_custom(updateInvoiceRefNumber());
811 if ($invoice_refno) {
812 sqlStatement("UPDATE form_encounter " .
813 "SET invoice_refno = '$invoice_refno' " .
814 "WHERE pid = '$form_pid' AND encounter = '$form_encounter'");
817 generate_receipt($form_pid, $form_encounter);
821 // If an encounter ID was given, then we must generate a receipt.
823 if (!empty($_GET['enc'])) {
824 generate_receipt($patient_id, $_GET['enc']);
828 // Get the unbilled billing table items and product sales for
831 $query = "SELECT id, date, code_type, code, modifier, code_text, " .
832 "provider_id, payer_id, units, fee, encounter " .
833 "FROM billing WHERE pid = '$patient_id' AND activity = 1 AND " .
834 "billed = 0 AND code_type != 'TAX' " .
835 "ORDER BY encounter DESC, id ASC";
836 $bres = sqlStatement($query);
838 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, " .
839 "s.quantity, s.encounter, s.drug_id, d.name, r.provider_id " .
840 "FROM drug_sales AS s " .
841 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
842 "LEFT OUTER JOIN prescriptions AS r ON r.id = s.prescription_id " .
843 "WHERE s.pid = '$patient_id' AND s.billed = 0 " .
844 "ORDER BY s.encounter DESC, s.sale_id ASC";
845 $dres = sqlStatement($query);
847 // If there are none, just redisplay the last receipt and exit.
849 if (mysql_num_rows($bres) == 0 && mysql_num_rows($dres) == 0) {
850 generate_receipt($patient_id);
854 // Get the valid practitioners, including those not active.
855 $arr_users = array();
856 $ures = sqlStatement("SELECT id, username FROM users WHERE " .
857 "( authorized = 1 OR info LIKE '%provider%' ) AND username != ''");
858 while ($urow = sqlFetchArray($ures)) {
859 $arr_users[$urow['id']] = '1';
862 // Now write a data entry form:
863 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
864 // List unbilled product sales for the patient.
865 // Present an editable dollar amount for each line item, a total
866 // which is also the default value of the input payment amount,
867 // and OK and Cancel buttons.
871 <link rel
='stylesheet' href
='<?php echo $css_header ?>' type
='text/css'>
872 <title
><?php
xl('Patient Checkout','e'); ?
></title
>
875 <style type
="text/css">@import
url(../../library
/dynarch_calendar
.css
);</style
>
876 <script type
="text/javascript" src
="../../library/textformat.js"></script
>
877 <script type
="text/javascript" src
="../../library/dynarch_calendar.js"></script
>
878 <?php
include_once("{$GLOBALS['srcdir']}/dynarch_calendar_en.inc.php"); ?
>
879 <script type
="text/javascript" src
="../../library/dynarch_calendar_setup.js"></script
>
880 <script type
="text/javascript" src
="../../library/dialog.js"></script
>
881 <script type
="text/javascript" src
="../../library/js/jquery-1.2.2.min.js"></script
>
882 <script language
="JavaScript">
883 var mypcc
= '<?php echo $GLOBALS['phone_country_code
'] ?>';
885 <?php
require($GLOBALS['srcdir'] . "/restoreSession.php"); ?
>
887 // This clears the tax line items in preparation for recomputing taxes.
888 function clearTax(visible
) {
889 var f
= document
.forms
[0];
890 for (var lino
= 0; true; ++lino
) {
891 var pfx
= 'line[' + lino +
']';
892 if (! f
[pfx +
'[code_type]']) break;
893 if (f
[pfx +
'[code_type]'].value
!= 'TAX') continue;
894 f
[pfx +
'[price]'].value
= '0.00';
895 if (visible
) f
[pfx +
'[amount]'].value
= '0.00';
899 // For a given tax ID and amount, compute the tax on that amount and add it
900 // to the "price" (same as "amount") of the corresponding tax line item.
901 // Note the tax line items include their "taxrate" to make this easy.
902 function addTax(rateid
, amount
, visible
) {
903 if (rateid
.length
== 0) return 0;
904 var f
= document
.forms
[0];
905 for (var lino
= 0; true; ++lino
) {
906 var pfx
= 'line[' + lino +
']';
907 if (! f
[pfx +
'[code_type]']) break;
908 if (f
[pfx +
'[code_type]'].value
!= 'TAX') continue;
909 if (f
[pfx +
'[code]'].value
!= rateid
) continue;
910 var tax
= amount
* parseFloat(f
[pfx +
'[taxrates]'].value
);
911 tax
= parseFloat(tax
.toFixed(<?php
echo $currdecimals ?
>));
912 var cumtax
= parseFloat(f
[pfx +
'[price]'].value
) + tax
;
913 f
[pfx +
'[price]'].value
= cumtax
.toFixed(<?php
echo $currdecimals ?
>); // requires JS 1.5
914 if (visible
) f
[pfx +
'[amount]'].value
= cumtax
.toFixed(<?php
echo $currdecimals ?
>); // requires JS 1.5
915 if (isNaN(tax
)) alert('Tax rate not numeric at line ' + lino
);
921 // This mess recomputes the invoice total and optionally applies a discount.
922 function computeDiscountedTotals(discount
, visible
) {
924 var f
= document
.forms
[0];
926 for (var lino
= 0; f
['line[' + lino +
'][code_type]']; ++lino
) {
927 var code_type
= f
['line[' + lino +
'][code_type]'].value
;
928 // price is price per unit when the form was originally generated.
929 // By contrast, amount is the dynamically-generated discounted line total.
930 var price
= parseFloat(f
['line[' + lino +
'][price]'].value
);
931 if (isNaN(price
)) alert('Price not numeric at line ' + lino
);
932 if (code_type
== 'COPAY' || code_type
== 'TAX') {
933 // This works because the tax lines come last.
934 total +
= parseFloat(price
.toFixed(<?php
echo $currdecimals ?
>));
937 var units
= f
['line[' + lino +
'][units]'].value
;
938 var amount
= price
* units
;
939 amount
= parseFloat(amount
.toFixed(<?php
echo $currdecimals ?
>));
940 if (visible
) f
['line[' + lino +
'][amount]'].value
= amount
.toFixed(<?php
echo $currdecimals ?
>);
942 var taxrates
= f
['line[' + lino +
'][taxrates]'].value
;
943 var taxids
= taxrates
.split(':');
944 for (var j
= 0; j
< taxids
.length
; ++j
) {
945 addTax(taxids
[j
], amount
, visible
);
948 return total
- discount
;
951 // Recompute displayed amounts with any discount applied.
952 function computeTotals() {
953 var f
= document
.forms
[0];
954 var discount
= parseFloat(f
.form_discount
.value
);
955 if (isNaN(discount
)) discount
= 0;
956 <?php
if (!$GLOBALS['discount_by_money']) { ?
>
957 // This site discounts by percentage, so convert it to a money amount.
958 if (discount
> 100) discount
= 100;
959 if (discount
< 0 ) discount
= 0;
960 discount
= 0.01 * discount
* computeDiscountedTotals(0, false);
962 var total
= computeDiscountedTotals(discount
, true);
963 f
.form_amount
.value
= total
.toFixed(<?php
echo $currdecimals ?
>);
970 <body
class="body_top">
972 <form method
='post' action
='pos_checkout.php'>
973 <input type
='hidden' name
='form_pid' value
='<?php echo $patient_id ?>' />
978 <table cellspacing
='5'>
980 <td colspan
='3' align
='center'>
981 <b
><?php
xl('Patient Checkout for ','e'); ?
><?php
echo $patdata['fname'] . " " .
982 $patdata['lname'] . " (" . $patdata['pubpid'] . ")" ?
></b
>
986 <td
><b
><?php
xl('Date','e'); ?
></b
></td
>
987 <td
><b
><?php
xl('Description','e'); ?
></b
></td
>
988 <td align
='right'><b
><?php
xl('Qty','e'); ?
></b
></td
>
989 <td align
='right'><b
><?php
xl('Amount','e'); ?
></b
></td
>
996 $gcac_related_visit = false;
997 $gcac_service_provided = false;
999 // Process billing table items. Note this includes co-pays.
1000 // Items that are not allowed to have a fee are skipped.
1002 while ($brow = sqlFetchArray($bres)) {
1003 // Skip all but the most recent encounter.
1004 if ($inv_encounter && $brow['encounter'] != $inv_encounter) continue;
1006 $thisdate = substr($brow['date'], 0, 10);
1007 $code_type = $brow['code_type'];
1009 // Collect tax rates, related code and provider ID.
1012 if (!empty($code_types[$code_type]['fee'])) {
1013 $query = "SELECT taxrates, related_code FROM codes WHERE code_type = '" .
1014 $code_types[$code_type]['id'] . "' AND " .
1015 "code = '" . $brow['code'] . "' AND ";
1016 if ($brow['modifier']) {
1017 $query .= "modifier = '" . $brow['modifier'] . "'";
1019 $query .= "(modifier IS NULL OR modifier = '')";
1021 $query .= " LIMIT 1";
1022 $tmp = sqlQuery($query);
1023 $taxrates = $tmp['taxrates'];
1024 $related_code = $tmp['related_code'];
1025 markTaxes($taxrates);
1028 write_form_line($code_type, $brow['code'], $brow['id'], $thisdate,
1029 ucfirst(strtolower($brow['code_text'])), $brow['fee'], $brow['units'],
1031 if (!$inv_encounter) $inv_encounter = $brow['encounter'];
1032 $inv_payer = $brow['payer_id'];
1033 if (!$inv_date ||
$inv_date < $thisdate) $inv_date = $thisdate;
1035 // Custom logic for IPPF to determine if a GCAC issue applies.
1036 if ($GLOBALS['ippf_specific'] && $related_code) {
1037 $relcodes = explode(';', $related_code);
1038 foreach ($relcodes as $codestring) {
1039 if ($codestring === '') continue;
1040 list($codetype, $code) = explode(':', $codestring);
1041 if ($codetype !== 'IPPF') continue;
1042 if (preg_match('/^25222/', $code)) {
1043 $gcac_related_visit = true;
1044 if (preg_match('/^25222[34]/', $code))
1045 $gcac_service_provided = true;
1051 // Process drug sales / products.
1053 while ($drow = sqlFetchArray($dres)) {
1054 if ($inv_encounter && $drow['encounter'] && $drow['encounter'] != $inv_encounter) continue;
1056 $thisdate = $drow['sale_date'];
1057 if (!$inv_encounter) $inv_encounter = $drow['encounter'];
1059 if (!$inv_provider && !empty($arr_users[$drow['provider_id']]))
1060 $inv_provider = $drow['provider_id'] +
0;
1062 if (!$inv_date ||
$inv_date < $thisdate) $inv_date = $thisdate;
1064 // Accumulate taxes for this product.
1065 $tmp = sqlQuery("SELECT taxrates FROM drug_templates WHERE drug_id = '" .
1066 $drow['drug_id'] . "' ORDER BY selector LIMIT 1");
1067 // accumTaxes($drow['fee'], $tmp['taxrates']);
1068 $taxrates = $tmp['taxrates'];
1069 markTaxes($taxrates);
1071 write_form_line('PROD', $drow['drug_id'], $drow['sale_id'],
1072 $thisdate, $drow['name'], $drow['fee'], $drow['quantity'], $taxrates);
1075 // Write a form line for each tax that has money, adding to $total.
1076 foreach ($taxes as $key => $value) {
1078 write_form_line('TAX', $key, $key, date('Y-m-d'), $value[0], 0, 1, $value[1]);
1082 // Note that we don't try to get anything from the ar_activity table. Since
1083 // this is the checkout, nothing should be there yet for this invoice.
1085 if ($inv_encounter) {
1086 $erow = sqlQuery("SELECT provider_id FROM form_encounter WHERE " .
1087 "pid = '$patient_id' AND encounter = '$inv_encounter' " .
1088 "ORDER BY id DESC LIMIT 1");
1089 $inv_provider = $erow['provider_id'] +
0;
1095 <table border
='0' cellspacing
='4'>
1099 <?php
echo $GLOBALS['discount_by_money'] ?
xl('Discount Amount') : xl('Discount Percentage'); ?
>:
1102 <input type
='text' name
='form_discount' size
='6' maxlength
='8' value
=''
1103 style
='text-align:right' onkeyup
='computeTotals()'>
1109 <?php
xl('Payment Method','e'); ?
>:
1112 <select name
='form_method'>
1115 foreach ($payment_methods as $value) {
1116 echo " <option value='$value'";
1117 echo ">$value</option>\n";
1126 <?php
xl('Check/Reference Number','e'); ?
>:
1129 <input type
='text' name
='form_source' size
='10' value
=''>
1135 <?php
xl('Amount Paid','e'); ?
>:
1138 <input type
='text' name
='form_amount' size
='10' value
='0.00'>
1144 <?php
xl('Posting Date','e'); ?
>:
1147 <input type
='text' size
='10' name
='form_date' id
='form_date'
1148 value
='<?php echo $inv_date ?>'
1149 title
='yyyy-mm-dd date of service'
1150 onkeyup
='datekeyup(this,mypcc)' onblur
='dateblur(this,mypcc)' />
1151 <img src
='../pic/show_calendar.gif' align
='absbottom' width
='24' height
='22'
1152 id
='img_date' border
='0' alt
='[?]' style
='cursor:pointer'
1153 title
='Click here to choose a date'>
1158 // If this user has a non-empty irnpool assigned, show the pending
1159 // invoice reference number.
1160 $irnumber = getInvoiceRefNumber();
1161 if (!empty($irnumber)) {
1165 <?php
xl('Tentative Invoice Ref No','e'); ?
>:
1168 <?php
echo $irnumber; ?
>
1173 // Otherwise if there is an invoice reference number mask, ask for the refno.
1174 else if (!empty($GLOBALS['gbl_mask_invoice_number'])) {
1178 <?php
xl('Invoice Reference Number','e'); ?
>:
1181 <input type
='text' name
='form_irnumber' size
='10' value
=''
1182 onkeyup
='maskkeyup(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number
']); ?>")'
1183 onblur
='maskblur(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number
']); ?>")'
1192 <td colspan
='2' align
='center'>
1194 <input type
='submit' name
='form_save' value
='<?php xl('Save
','e
'); ?>' />  
;
1195 <?php
if (empty($_GET['framed'])) { ?
>
1196 <input type
='button' value
='Cancel' onclick
='window.close()' />
1198 <input type
='hidden' name
='form_provider' value
='<?php echo $inv_provider ?>' />
1199 <input type
='hidden' name
='form_payer' value
='<?php echo $inv_payer ?>' />
1200 <input type
='hidden' name
='form_encounter' value
='<?php echo $inv_encounter ?>' />
1209 <script language
='JavaScript'>
1210 Calendar
.setup({inputField
:"form_date", ifFormat
:"%Y-%m-%d", button
:"img_date"});
1213 // The following is removed, perhaps temporarily, because gcac reporting
1214 // no longer depends on gcac issues. -- Rod 2009-08-11
1215 /*********************************************************************
1216 // Custom code for IPPF. Try to make sure that a GCAC issue is linked to this
1217 // visit if it contains GCAC-related services.
1218 if ($gcac_related_visit) {
1219 $grow = sqlQuery("SELECT l.id, l.title, l.begdate, ie.pid " .
1220 "FROM lists AS l " .
1221 "LEFT JOIN issue_encounter AS ie ON ie.pid = l.pid AND " .
1222 "ie.encounter = '$inv_encounter' AND ie.list_id = l.id " .
1223 "WHERE l.pid = '$pid' AND " .
1224 "l.activity = 1 AND l.type = 'ippf_gcac' " .
1225 "ORDER BY ie.pid DESC, l.begdate DESC LIMIT 1");
1226 // Note that reverse-ordering by ie.pid is a trick for sorting
1227 // issues linked to the encounter (non-null values) first.
1228 if (empty($grow['pid'])) { // if there is no linked GCAC issue
1229 if (!empty($grow)) { // there is one that is not linked
1230 echo " if (confirm('" . xl('OK to link the GCAC issue dated') . " " .
1231 $grow['begdate'] . " " . xl('to this visit?') . "')) {\n";
1232 echo " $.getScript('link_issue_to_encounter.php?issue=" . $grow['id'] .
1233 "&thisenc=$inv_encounter');\n";
1236 echo " if (confirm('" . xl('Are you prepared to complete a new GCAC issue for this visit?') . "')) {\n";
1237 echo " dlgopen('summary/add_edit_issue.php?thisenc=$inv_encounter" .
1238 "&thistype=ippf_gcac', '_blank', 700, 600);\n";
1240 echo " $.getScript('link_issue_to_encounter.php?thisenc=$inv_encounter');\n";
1243 } // end if ($gcac_related_visit)
1244 *********************************************************************/
1246 if ($gcac_related_visit && !$gcac_service_provided) {
1247 // Skip this warning if the GCAC visit form is not allowed.
1248 $grow = sqlQuery("SELECT COUNT(*) AS count FROM list_options " .
1249 "WHERE list_id = 'lbfnames' AND option_id = 'LBFgcac'");
1250 if (!empty($grow['count'])) { // if gcac is used
1251 // Skip this warning if referral or abortion in TS.
1252 $grow = sqlQuery("SELECT COUNT(*) AS count FROM transactions " .
1253 "WHERE title = 'Referral' AND refer_date IS NOT NULL AND " .
1254 "refer_date = '$inv_date' AND pid = '$patient_id'");
1255 if (empty($grow['count'])) { // if there is no referral
1256 $grow = sqlQuery("SELECT COUNT(*) AS count FROM forms " .
1257 "WHERE pid = '$patient_id' AND encounter = '$inv_encounter' AND " .
1258 "deleted = 0 AND formdir = 'LBFgcac'");
1259 if (empty($grow['count'])) { // if there is no gcac form
1260 echo " alert('" . xl('This visit will need a GCAC form, referral or procedure service.') . "');\n";
1264 } // end if ($gcac_related_visit)