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');
282 // Get the most recent invoice data or that for the specified encounter.
284 if ($INTEGRATED_AR) {
286 $ferow = sqlQuery("SELECT id, date, encounter FROM form_encounter " .
287 "WHERE pid = '$patient_id' AND encounter = '$encounter'");
289 $ferow = sqlQuery("SELECT id, date, encounter FROM form_encounter " .
290 "WHERE pid = '$patient_id' " .
291 "ORDER BY id DESC LIMIT 1");
293 if (empty($ferow)) die(xl("This patient has no activity."));
294 $trans_id = $ferow['id'];
295 $encounter = $ferow['encounter'];
296 $svcdate = substr($ferow['date'], 0, 10);
301 $arres = SLQuery("SELECT * FROM ar WHERE " .
302 "invnumber LIKE '$patient_id.%' " .
303 "ORDER BY id DESC LIMIT 1");
304 if ($sl_err) die($sl_err);
305 if (!SLRowCount($arres)) die(xl("This patient has no activity."));
306 $arrow = SLGetRow($arres, 0);
308 $trans_id = $arrow['id'];
310 // Determine the date of service. An 8-digit encounter number is
311 // presumed to be a date of service imported during conversion or
312 // associated with prescriptions only. Otherwise look it up in the
313 // form_encounter table.
316 list($trash, $encounter) = explode(".", $arrow['invnumber']);
317 if (strlen($encounter) >= 8) {
318 $svcdate = substr($encounter, 0, 4) . "-" . substr($encounter, 4, 2) .
319 "-" . substr($encounter, 6, 2);
321 else if ($encounter) {
322 $tmp = sqlQuery("SELECT date FROM form_encounter WHERE " .
323 "encounter = $encounter");
324 $svcdate = substr($tmp['date'], 0, 10);
326 } // end not $INTEGRATED_AR
328 // Get invoice reference number.
329 $encrow = sqlQuery("SELECT invoice_refno FROM form_encounter WHERE " .
330 "pid = '$patient_id' AND encounter = '$encounter' LIMIT 1");
331 $invoice_refno = $encrow['invoice_refno'];
335 <?php
html_header_show(); ?
>
336 <link rel
='stylesheet' href
='<?php echo $css_header ?>' type
='text/css'>
337 <title
><?php
xl('Receipt for Payment','e'); ?
></title
>
338 <script type
="text/javascript" src
="../../library/dialog.js"></script
>
339 <script language
="JavaScript">
341 <?php
require($GLOBALS['srcdir'] . "/restoreSession.php"); ?
>
343 // Process click on Print button.
345 var divstyle
= document
.getElementById('hideonprint').style
;
346 divstyle
.display
= 'none';
351 // Process click on Delete button.
352 function deleteme() {
353 dlgopen('deleter.php?billing=<?php echo "$patient_id.$encounter"; ?>', '_blank', 500, 450);
357 // Called by the deleteme.php window on a successful delete.
358 function imdeleted() {
364 <body
class="body_top">
366 <p
><b
><?php
echo $frow['name'] ?
>
367 <br
><?php
echo $frow['street'] ?
>
368 <br
><?php
echo $frow['city'] . ', ' . $frow['state'] . ' ' . $frow['postal_code'] ?
>
369 <br
><?php
echo $frow['phone'] ?
>
373 echo xl("Receipt Generated") . date(' F j, Y');
374 if ($invoice_refno) echo " " . xl("for Invoice") . " $invoice_refno";
380 <?php
echo $patdata['fname'] . ' ' . $patdata['mname'] . ' ' . $patdata['lname'] ?
>
381 <br
><?php
echo $patdata['street'] ?
>
382 <br
><?php
echo $patdata['city'] . ', ' . $patdata['state'] . ' ' . $patdata['postal_code'] ?
>
386 <table cellpadding
='5'>
388 <td
><b
><?php
xl('Date','e'); ?
></b
></td
>
389 <td
><b
><?php
xl('Description','e'); ?
></b
></td
>
390 <td align
='right'><b
><?php
echo $details ?
xl('Price') : ' '; ?
></b
></td
>
391 <td align
='right'><b
><?php
echo $details ?
xl('Qty' ) : ' '; ?
></b
></td
>
392 <td align
='right'><b
><?php
xl('Total','e'); ?
></b
></td
>
398 if ($INTEGRATED_AR) {
400 $inres = sqlStatement("SELECT s.sale_id, s.sale_date, s.fee, " .
401 "s.quantity, s.drug_id, d.name " .
402 "FROM drug_sales AS s LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
403 // "WHERE s.pid = '$patient_id' AND s.encounter = '$encounter' AND s.fee != 0 " .
404 "WHERE s.pid = '$patient_id' AND s.encounter = '$encounter' " .
405 "ORDER BY s.sale_id");
406 while ($inrow = sqlFetchArray($inres)) {
407 $charges +
= sprintf('%01.2f', $inrow['fee']);
408 receiptDetailLine($inrow['sale_date'], $inrow['name'],
409 $inrow['fee'], $inrow['quantity']);
411 // Service and tax items
412 $inres = sqlStatement("SELECT * FROM billing WHERE " .
413 "pid = '$patient_id' AND encounter = '$encounter' AND " .
414 // "code_type != 'COPAY' AND activity = 1 AND fee != 0 " .
415 "code_type != 'COPAY' AND activity = 1 " .
417 while ($inrow = sqlFetchArray($inres)) {
418 $charges +
= sprintf('%01.2f', $inrow['fee']);
419 receiptDetailLine($svcdate, $inrow['code_text'],
420 $inrow['fee'], $inrow['units']);
423 $inres = sqlStatement("SELECT " .
424 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
425 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
426 "FROM ar_activity AS a " .
427 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
428 "a.pid = '$patient_id' AND a.encounter = '$encounter' AND " .
429 "a.adj_amount != 0 " .
430 "ORDER BY s.check_date, a.sequence_no");
431 while ($inrow = sqlFetchArray($inres)) {
432 $charges -= sprintf('%01.2f', $inrow['adj_amount']);
433 $payer = empty($inrow['payer_type']) ?
'Pt' : ('Ins' . $inrow['payer_type']);
434 receiptDetailLine($svcdate, $payer . ' ' . $inrow['memo'],
435 0 - $inrow['adj_amount'], 1);
437 } // end $INTEGRATED_AR
439 // Request all line items with money belonging to the invoice.
440 $inres = SLQuery("SELECT * FROM invoice WHERE " .
441 "trans_id = $trans_id AND sellprice != 0 ORDER BY id");
442 if ($sl_err) die($sl_err);
443 for ($irow = 0; $irow < SLRowCount($inres); ++
$irow) {
444 $row = SLGetRow($inres, $irow);
445 $amount = sprintf('%01.2f', $row['sellprice'] * $row['qty']);
447 $desc = preg_replace('/^.{1,6}:/', '', $row['description']);
448 receiptDetailLine($svcdate, $desc, $amount, $row['qty']);
450 } // end not $INTEGRATED_AR
454 <td colspan
='5'> 
;</td
>
457 <td
><?php
echo oeFormatShortDate($svcdispdate); ?
></td
>
458 <td
><b
><?php
xl('Total Charges','e'); ?
></b
></td
>
459 <td align
='right'> 
;</td
>
460 <td align
='right'> 
;</td
>
461 <td align
='right'><?php
echo oeFormatMoney($charges, true) ?
></td
>
464 <td colspan
='5'> 
;</td
>
468 if ($INTEGRATED_AR) {
470 $inres = sqlStatement("SELECT fee, code_text FROM billing WHERE " .
471 "pid = '$patient_id' AND encounter = '$encounter' AND " .
472 "code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
474 while ($inrow = sqlFetchArray($inres)) {
475 $charges +
= sprintf('%01.2f', $inrow['fee']);
476 receiptPaymentLine($svcdate, 0 - $inrow['fee'], $inrow['code_text']);
478 // Get other payments.
479 $inres = sqlStatement("SELECT " .
480 "a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, " .
481 "s.payer_id, s.reference, s.check_date, s.deposit_date " .
482 "FROM ar_activity AS a " .
483 "LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
484 "a.pid = '$patient_id' AND a.encounter = '$encounter' AND " .
485 "a.pay_amount != 0 " .
486 "ORDER BY s.check_date, a.sequence_no");
487 $payer = empty($inrow['payer_type']) ?
'Pt' : ('Ins' . $inrow['payer_type']);
488 while ($inrow = sqlFetchArray($inres)) {
489 $charges -= sprintf('%01.2f', $inrow['pay_amount']);
490 receiptPaymentLine($svcdate, $inrow['pay_amount'],
491 $payer . ' ' . $inrow['reference']);
493 } // end $INTEGRATED_AR
495 $chart_id_cash = SLQueryValue("select id from chart where accno = '$sl_cash_acc'");
496 if ($sl_err) die($sl_err);
497 if (! $chart_id_cash) die("There is no COA entry for cash account '$sl_cash_acc'");
499 // Request all cash entries belonging to the invoice.
500 $atres = SLQuery("SELECT * FROM acc_trans WHERE " .
501 "trans_id = $trans_id AND chart_id = $chart_id_cash ORDER BY transdate");
502 if ($sl_err) die($sl_err);
504 for ($irow = 0; $irow < SLRowCount($atres); ++
$irow) {
505 $row = SLGetRow($atres, $irow);
506 $amount = sprintf('%01.2f', $row['amount']); // negative
508 $rowsource = $row['source'];
509 if (strtolower($rowsource) == 'co-pay') $rowsource = '';
510 receiptPaymentLine($row['transdate'], 0 - $amount, $rowsource);
512 } // end not $INTEGRATED_AR
515 <td colspan
='5'> 
;</td
>
519 <td
><b
><?php
xl('Balance Due','e'); ?
></b
></td
>
520 <td colspan
='2'> 
;</td
>
521 <td align
='right'><?php
echo oeFormatMoney($charges, true) ?
></td
>
525 <div id
='hideonprint'>
528 <a href
='#' onclick
='return printme();'><?php
xl('Print','e'); ?
></a
>
529 <?php
if (acl_check('acct','disc')) { ?
>
530  
; 
; 
; 
; 
;
531 <a href
='#' onclick
='return deleteme();'><?php
xl('Undo Checkout','e'); ?
></a
>
533  
; 
; 
; 
; 
;
534 <?php
if ($details) { ?
>
535 <a href
='pos_checkout.php?details=0&ptid=<?php echo $patient_id; ?>&enc=<?php echo $encounter; ?>'><?php
xl('Hide Details','e'); ?
></a
>
537 <a href
='pos_checkout.php?details=1&ptid=<?php echo $patient_id; ?>&enc=<?php echo $encounter; ?>'><?php
xl('Show Details','e'); ?
></a
>
544 if (!$INTEGRATED_AR) SLClose();
545 } // end function generate_receipt()
547 // Function to output a line item for the input form.
550 function write_form_line($code_type, $code, $id, $date, $description,
551 $amount, $units, $taxrates) {
553 $amount = sprintf("%01.2f", $amount);
554 if (empty($units)) $units = 1;
555 $price = $amount / $units; // should be even cents, but ok here if not
556 if ($code_type == 'COPAY' && !$description) $description = xl('Payment');
558 echo " <td>" . oeFormatShortDate($date);
559 echo "<input type='hidden' name='line[$lino][code_type]' value='$code_type'>";
560 echo "<input type='hidden' name='line[$lino][code]' value='$code'>";
561 echo "<input type='hidden' name='line[$lino][id]' value='$id'>";
562 echo "<input type='hidden' name='line[$lino][description]' value='$description'>";
563 echo "<input type='hidden' name='line[$lino][taxrates]' value='$taxrates'>";
564 echo "<input type='hidden' name='line[$lino][price]' value='$price'>";
565 echo "<input type='hidden' name='line[$lino][units]' value='$units'>";
567 echo " <td>$description</td>";
568 echo " <td align='right'>$units</td>";
569 echo " <td align='right'><input type='text' name='line[$lino][amount]' " .
570 "value='$amount' size='6' maxlength='8'";
571 // Modifying prices requires the acct/disc permission.
572 // if ($code_type == 'TAX' || ($code_type != 'COPAY' && !acl_check('acct','disc')))
573 echo " style='text-align:right;background-color:transparent' readonly";
574 // else echo " style='text-align:right' onkeyup='computeTotals()'";
580 // Create the taxes array. Key is tax id, value is
581 // (description, rate, accumulated total).
583 $pres = sqlStatement("SELECT option_id, title, option_value " .
584 "FROM list_options WHERE list_id = 'taxrate' ORDER BY seq");
585 while ($prow = sqlFetchArray($pres)) {
586 $taxes[$prow['option_id']] = array($prow['title'], $prow['option_value'], 0);
589 // Mark the tax rates that are referenced in this invoice.
590 function markTaxes($taxrates) {
592 $arates = explode(':', $taxrates);
593 if (empty($arates)) return;
594 foreach ($arates as $value) {
595 if (!empty($taxes[$value])) $taxes[$value][2] = '1';
599 $payment_methods = array(
608 $alertmsg = ''; // anything here pops up in an alert box
610 // If the Save button was clicked...
612 if ($_POST['form_save']) {
614 // On a save, do the following:
615 // Flag drug_sales and billing items as billed.
616 // Post the corresponding invoice with its payment(s) to sql-ledger
617 // and be careful to use a unique invoice number.
618 // Call the generate-receipt function.
621 $form_pid = $_POST['form_pid'];
622 $form_encounter = $_POST['form_encounter'];
624 // Get the posting date from the form as yyyy-mm-dd.
625 $dosdate = date("Y-m-d");
626 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $_POST['form_date'], $matches)) {
627 $dosdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
630 // If there is no associated encounter (i.e. this invoice has only
631 // prescriptions) then assign an encounter number of the service
632 // date, with an optional suffix to ensure that it's unique.
634 if (! $form_encounter) {
635 $form_encounter = substr($dosdate,0,4) . substr($dosdate,5,2) . substr($dosdate,8,2);
637 if ($INTEGRATED_AR) {
639 $ferow = sqlQuery("SELECT id FROM form_encounter WHERE " .
640 "pid = '$form_pid' AND encounter = '$form_encounter$tmp'");
641 if (empty($ferow)) break;
642 $tmp = $tmp ?
$tmp +
1 : 1;
647 while (SLQueryValue("select id from ar where " .
648 "invnumber = '$form_pid.$form_encounter$tmp'")) {
649 $tmp = $tmp ?
$tmp +
1 : 1;
653 $form_encounter .= $tmp;
656 if ($INTEGRATED_AR) {
657 // Delete any TAX rows from billing because they will be recalculated.
658 sqlStatement("UPDATE billing SET activity = 0 WHERE " .
659 "pid = '$form_pid' AND encounter = '$form_encounter' AND " .
660 "code_type = 'TAX'");
663 // Initialize an array of invoice information for posting.
664 $invoice_info = array();
665 $msg = invoice_initialize($invoice_info, $form_pid,
666 $_POST['form_provider'], $_POST['form_payer'], $form_encounter, $dosdate);
670 $form_amount = $_POST['form_amount'];
671 $lines = $_POST['line'];
673 for ($lino = 0; $lines[$lino]['code_type']; ++
$lino) {
674 $line = $lines[$lino];
675 $code_type = $line['code_type'];
677 $amount = sprintf('%01.2f', trim($line['amount']));
679 if (!$INTEGRATED_AR) {
680 $msg = invoice_add_line_item($invoice_info, $code_type,
681 $line['code'], $line['description'], $amount, $line['units']);
685 if ($code_type == 'PROD') {
686 // Product sales. The fee and encounter ID may have changed.
687 $query = "update drug_sales SET fee = '$amount', " .
688 "encounter = '$form_encounter', billed = 1 WHERE " .
692 else if ($code_type == 'TAX') {
693 // In the SL case taxes show up on the invoice as line items.
694 // Otherwise we gotta save them somewhere, and in the billing
695 // table with a code type of TAX seems easiest.
696 // They will have to be stripped back out when building this
697 // script's input form.
698 addBilling($form_encounter, 'TAX', 'TAX', 'Taxes', $form_pid, 0, 0,
699 '', '', $amount, '', '', 1);
702 // Because there is no insurance here, there is no need for a claims
703 // table entry and so we do not call updateClaim(). Note we should not
704 // eliminate billed and bill_date from the billing table!
705 $query = "UPDATE billing SET fee = '$amount', billed = 1, " .
706 "bill_date = NOW() WHERE id = '$id'";
712 if ($_POST['form_discount']) {
713 if ($GLOBALS['discount_by_money']) {
714 $amount = sprintf('%01.2f', trim($_POST['form_discount']));
717 $amount = sprintf('%01.2f', trim($_POST['form_discount']) * $form_amount / 100);
719 $memo = xl('Discount');
720 if ($INTEGRATED_AR) {
721 $time = date('Y-m-d H:i:s');
722 $query = "INSERT INTO ar_activity ( " .
723 "pid, encounter, code, modifier, payer_type, post_user, post_time, " .
724 "session_id, memo, adj_amount " .
727 "'$form_encounter', " .
731 "'" . $_SESSION['authUserID'] . "', " .
737 sqlStatement($query);
740 $msg = invoice_add_line_item($invoice_info, 'DISCOUNT',
741 '', $memo, 0 - $amount);
747 if ($_POST['form_amount']) {
748 $amount = sprintf('%01.2f', trim($_POST['form_amount']));
749 $form_source = trim($_POST['form_source']);
750 $paydesc = trim($_POST['form_method']);
751 if ($INTEGRATED_AR) {
752 // Post the payment as a billed copay into the billing table.
753 // Maybe this should even be done for the SL case.
754 if (!empty($form_source)) $paydesc .= " $form_source";
755 # jason forced auth line to 1 here
756 addBilling($form_encounter, 'COPAY', $amount, $paydesc, $form_pid,
757 1, 0, '', '', 0 - $amount, '', '', 1);
760 $msg = invoice_add_line_item($invoice_info, 'COPAY',
761 $paydesc, $form_source, 0 - $amount);
766 if (!$INTEGRATED_AR) {
767 $msg = invoice_post($invoice_info);
771 // If applicable, set the invoice reference number.
773 if (isset($_POST['form_irnumber'])) {
774 $invoice_refno = formData('form_irnumber', 'P', true);
777 $invoice_refno = add_escape_custom(updateInvoiceRefNumber());
779 if ($invoice_refno) {
780 sqlStatement("UPDATE form_encounter " .
781 "SET invoice_refno = '$invoice_refno' " .
782 "WHERE pid = '$form_pid' AND encounter = '$form_encounter'");
785 generate_receipt($form_pid, $form_encounter);
789 // If an encounter ID was given, then we must generate a receipt.
791 if (!empty($_GET['enc'])) {
792 generate_receipt($patient_id, $_GET['enc']);
796 // Get the unbilled billing table items and product sales for
799 $query = "SELECT id, date, code_type, code, modifier, code_text, " .
800 "provider_id, payer_id, units, fee, encounter " .
801 "FROM billing WHERE pid = '$patient_id' AND activity = 1 AND " .
802 "billed = 0 AND code_type != 'TAX' " .
803 "ORDER BY encounter DESC, id ASC";
804 $bres = sqlStatement($query);
806 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, " .
807 "s.quantity, s.encounter, s.drug_id, d.name, r.provider_id " .
808 "FROM drug_sales AS s " .
809 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
810 "LEFT OUTER JOIN prescriptions AS r ON r.id = s.prescription_id " .
811 "WHERE s.pid = '$patient_id' AND s.billed = 0 " .
812 "ORDER BY s.encounter DESC, s.sale_id ASC";
813 $dres = sqlStatement($query);
815 // If there are none, just redisplay the last receipt and exit.
817 if (mysql_num_rows($bres) == 0 && mysql_num_rows($dres) == 0) {
818 generate_receipt($patient_id);
822 // Get the valid practitioners, including those not active.
823 $arr_users = array();
824 $ures = sqlStatement("SELECT id, username FROM users WHERE " .
825 "( authorized = 1 OR info LIKE '%provider%' ) AND username != ''");
826 while ($urow = sqlFetchArray($ures)) {
827 $arr_users[$urow['id']] = '1';
830 // Now write a data entry form:
831 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
832 // List unbilled product sales for the patient.
833 // Present an editable dollar amount for each line item, a total
834 // which is also the default value of the input payment amount,
835 // and OK and Cancel buttons.
839 <link rel
='stylesheet' href
='<?php echo $css_header ?>' type
='text/css'>
840 <title
><?php
xl('Patient Checkout','e'); ?
></title
>
843 <style type
="text/css">@import
url(../../library
/dynarch_calendar
.css
);</style
>
844 <script type
="text/javascript" src
="../../library/textformat.js"></script
>
845 <script type
="text/javascript" src
="../../library/dynarch_calendar.js"></script
>
846 <?php
include_once("{$GLOBALS['srcdir']}/dynarch_calendar_en.inc.php"); ?
>
847 <script type
="text/javascript" src
="../../library/dynarch_calendar_setup.js"></script
>
848 <script type
="text/javascript" src
="../../library/dialog.js"></script
>
849 <script type
="text/javascript" src
="../../library/js/jquery-1.2.2.min.js"></script
>
850 <script language
="JavaScript">
851 var mypcc
= '<?php echo $GLOBALS['phone_country_code
'] ?>';
853 <?php
require($GLOBALS['srcdir'] . "/restoreSession.php"); ?
>
855 // This clears the tax line items in preparation for recomputing taxes.
856 function clearTax(visible
) {
857 var f
= document
.forms
[0];
858 for (var lino
= 0; true; ++lino
) {
859 var pfx
= 'line[' + lino +
']';
860 if (! f
[pfx +
'[code_type]']) break;
861 if (f
[pfx +
'[code_type]'].value
!= 'TAX') continue;
862 f
[pfx +
'[price]'].value
= '0.00';
863 if (visible
) f
[pfx +
'[amount]'].value
= '0.00';
867 // For a given tax ID and amount, compute the tax on that amount and add it
868 // to the "price" (same as "amount") of the corresponding tax line item.
869 // Note the tax line items include their "taxrate" to make this easy.
870 function addTax(rateid
, amount
, visible
) {
871 if (rateid
.length
== 0) return 0;
872 var f
= document
.forms
[0];
873 for (var lino
= 0; true; ++lino
) {
874 var pfx
= 'line[' + lino +
']';
875 if (! f
[pfx +
'[code_type]']) break;
876 if (f
[pfx +
'[code_type]'].value
!= 'TAX') continue;
877 if (f
[pfx +
'[code]'].value
!= rateid
) continue;
878 var tax
= amount
* parseFloat(f
[pfx +
'[taxrates]'].value
);
879 tax
= parseFloat(tax
.toFixed(<?php
echo $currdecimals ?
>));
880 var cumtax
= parseFloat(f
[pfx +
'[price]'].value
) + tax
;
881 f
[pfx +
'[price]'].value
= cumtax
.toFixed(<?php
echo $currdecimals ?
>); // requires JS 1.5
882 if (visible
) f
[pfx +
'[amount]'].value
= cumtax
.toFixed(<?php
echo $currdecimals ?
>); // requires JS 1.5
883 if (isNaN(tax
)) alert('Tax rate not numeric at line ' + lino
);
889 // This mess recomputes the invoice total and optionally applies a discount.
890 function computeDiscountedTotals(discount
, visible
) {
892 var f
= document
.forms
[0];
894 for (var lino
= 0; f
['line[' + lino +
'][code_type]']; ++lino
) {
895 var code_type
= f
['line[' + lino +
'][code_type]'].value
;
896 // price is price per unit when the form was originally generated.
897 // By contrast, amount is the dynamically-generated discounted line total.
898 var price
= parseFloat(f
['line[' + lino +
'][price]'].value
);
899 if (isNaN(price
)) alert('Price not numeric at line ' + lino
);
900 if (code_type
== 'COPAY' || code_type
== 'TAX') {
901 // This works because the tax lines come last.
902 total +
= parseFloat(price
.toFixed(<?php
echo $currdecimals ?
>));
905 var units
= f
['line[' + lino +
'][units]'].value
;
906 var amount
= price
* units
;
907 amount
= parseFloat(amount
.toFixed(<?php
echo $currdecimals ?
>));
908 if (visible
) f
['line[' + lino +
'][amount]'].value
= amount
.toFixed(<?php
echo $currdecimals ?
>);
910 var taxrates
= f
['line[' + lino +
'][taxrates]'].value
;
911 var taxids
= taxrates
.split(':');
912 for (var j
= 0; j
< taxids
.length
; ++j
) {
913 addTax(taxids
[j
], amount
, visible
);
916 return total
- discount
;
919 // Recompute displayed amounts with any discount applied.
920 function computeTotals() {
921 var f
= document
.forms
[0];
922 var discount
= parseFloat(f
.form_discount
.value
);
923 if (isNaN(discount
)) discount
= 0;
924 <?php
if (!$GLOBALS['discount_by_money']) { ?
>
925 // This site discounts by percentage, so convert it to a money amount.
926 if (discount
> 100) discount
= 100;
927 if (discount
< 0 ) discount
= 0;
928 discount
= 0.01 * discount
* computeDiscountedTotals(0, false);
930 var total
= computeDiscountedTotals(discount
, true);
931 f
.form_amount
.value
= total
.toFixed(<?php
echo $currdecimals ?
>);
938 <body
class="body_top">
940 <form method
='post' action
='pos_checkout.php'>
941 <input type
='hidden' name
='form_pid' value
='<?php echo $patient_id ?>' />
946 <table cellspacing
='5'>
948 <td colspan
='3' align
='center'>
949 <b
><?php
xl('Patient Checkout for ','e'); ?
><?php
echo $patdata['fname'] . " " .
950 $patdata['lname'] . " (" . $patdata['pubpid'] . ")" ?
></b
>
954 <td
><b
><?php
xl('Date','e'); ?
></b
></td
>
955 <td
><b
><?php
xl('Description','e'); ?
></b
></td
>
956 <td align
='right'><b
><?php
xl('Qty','e'); ?
></b
></td
>
957 <td align
='right'><b
><?php
xl('Amount','e'); ?
></b
></td
>
964 $gcac_related_visit = false;
965 $gcac_service_provided = false;
967 // Process billing table items. Note this includes co-pays.
968 // Items that are not allowed to have a fee are skipped.
970 while ($brow = sqlFetchArray($bres)) {
971 // Skip all but the most recent encounter.
972 if ($inv_encounter && $brow['encounter'] != $inv_encounter) continue;
974 $thisdate = substr($brow['date'], 0, 10);
975 $code_type = $brow['code_type'];
977 // Collect tax rates, related code and provider ID.
980 if (!empty($code_types[$code_type]['fee'])) {
981 $query = "SELECT taxrates, related_code FROM codes WHERE code_type = '" .
982 $code_types[$code_type]['id'] . "' AND " .
983 "code = '" . $brow['code'] . "' AND ";
984 if ($brow['modifier']) {
985 $query .= "modifier = '" . $brow['modifier'] . "'";
987 $query .= "(modifier IS NULL OR modifier = '')";
989 $query .= " LIMIT 1";
990 $tmp = sqlQuery($query);
991 $taxrates = $tmp['taxrates'];
992 $related_code = $tmp['related_code'];
993 markTaxes($taxrates);
996 write_form_line($code_type, $brow['code'], $brow['id'], $thisdate,
997 ucfirst(strtolower($brow['code_text'])), $brow['fee'], $brow['units'],
999 if (!$inv_encounter) $inv_encounter = $brow['encounter'];
1000 $inv_payer = $brow['payer_id'];
1001 if (!$inv_date ||
$inv_date < $thisdate) $inv_date = $thisdate;
1003 // Custom logic for IPPF to determine if a GCAC issue applies.
1004 if ($GLOBALS['ippf_specific'] && $related_code) {
1005 $relcodes = explode(';', $related_code);
1006 foreach ($relcodes as $codestring) {
1007 if ($codestring === '') continue;
1008 list($codetype, $code) = explode(':', $codestring);
1009 if ($codetype !== 'IPPF') continue;
1010 if (preg_match('/^25222/', $code)) {
1011 $gcac_related_visit = true;
1012 if (preg_match('/^25222[34]/', $code))
1013 $gcac_service_provided = true;
1019 // Process drug sales / products.
1021 while ($drow = sqlFetchArray($dres)) {
1022 if ($inv_encounter && $drow['encounter'] && $drow['encounter'] != $inv_encounter) continue;
1024 $thisdate = $drow['sale_date'];
1025 if (!$inv_encounter) $inv_encounter = $drow['encounter'];
1027 if (!$inv_provider && !empty($arr_users[$drow['provider_id']]))
1028 $inv_provider = $drow['provider_id'] +
0;
1030 if (!$inv_date ||
$inv_date < $thisdate) $inv_date = $thisdate;
1032 // Accumulate taxes for this product.
1033 $tmp = sqlQuery("SELECT taxrates FROM drug_templates WHERE drug_id = '" .
1034 $drow['drug_id'] . "' ORDER BY selector LIMIT 1");
1035 // accumTaxes($drow['fee'], $tmp['taxrates']);
1036 $taxrates = $tmp['taxrates'];
1037 markTaxes($taxrates);
1039 write_form_line('PROD', $drow['drug_id'], $drow['sale_id'],
1040 $thisdate, $drow['name'], $drow['fee'], $drow['quantity'], $taxrates);
1043 // Write a form line for each tax that has money, adding to $total.
1044 foreach ($taxes as $key => $value) {
1046 write_form_line('TAX', $key, $key, date('Y-m-d'), $value[0], 0, 1, $value[1]);
1050 // Note that we don't try to get anything from the ar_activity table. Since
1051 // this is the checkout, nothing should be there yet for this invoice.
1053 if ($inv_encounter) {
1054 $erow = sqlQuery("SELECT provider_id FROM form_encounter WHERE " .
1055 "pid = '$patient_id' AND encounter = '$inv_encounter' " .
1056 "ORDER BY id DESC LIMIT 1");
1057 $inv_provider = $erow['provider_id'] +
0;
1063 <table border
='0' cellspacing
='4'>
1067 <?php
echo $GLOBALS['discount_by_money'] ?
xl('Discount Amount') : xl('Discount Percentage'); ?
>:
1070 <input type
='text' name
='form_discount' size
='6' maxlength
='8' value
=''
1071 style
='text-align:right' onkeyup
='computeTotals()'>
1077 <?php
xl('Payment Method','e'); ?
>:
1080 <select name
='form_method'>
1083 foreach ($payment_methods as $value) {
1084 echo " <option value='$value'";
1085 echo ">$value</option>\n";
1094 <?php
xl('Check/Reference Number','e'); ?
>:
1097 <input type
='text' name
='form_source' size
='10' value
=''>
1103 <?php
xl('Amount Paid','e'); ?
>:
1106 <input type
='text' name
='form_amount' size
='10' value
='0.00'>
1112 <?php
xl('Posting Date','e'); ?
>:
1115 <input type
='text' size
='10' name
='form_date' id
='form_date'
1116 value
='<?php echo $inv_date ?>'
1117 title
='yyyy-mm-dd date of service'
1118 onkeyup
='datekeyup(this,mypcc)' onblur
='dateblur(this,mypcc)' />
1119 <img src
='../pic/show_calendar.gif' align
='absbottom' width
='24' height
='22'
1120 id
='img_date' border
='0' alt
='[?]' style
='cursor:pointer'
1121 title
='Click here to choose a date'>
1126 // If this user has a non-empty irnpool assigned, show the pending
1127 // invoice reference number.
1128 $irnumber = getInvoiceRefNumber();
1129 if (!empty($irnumber)) {
1133 <?php
xl('Tentative Invoice Ref No','e'); ?
>:
1136 <?php
echo $irnumber; ?
>
1141 // Otherwise if there is an invoice reference number mask, ask for the refno.
1142 else if (!empty($GLOBALS['gbl_mask_invoice_number'])) {
1146 <?php
xl('Invoice Reference Number','e'); ?
>:
1149 <input type
='text' name
='form_irnumber' size
='10' value
=''
1150 onkeyup
='maskkeyup(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number
']); ?>")'
1151 onblur
='maskblur(this,"<?php echo addslashes($GLOBALS['gbl_mask_invoice_number
']); ?>")'
1160 <td colspan
='2' align
='center'>
1162 <input type
='submit' name
='form_save' value
='<?php xl('Save
','e
'); ?>' />  
;
1163 <?php
if (empty($_GET['framed'])) { ?
>
1164 <input type
='button' value
='Cancel' onclick
='window.close()' />
1166 <input type
='hidden' name
='form_provider' value
='<?php echo $inv_provider ?>' />
1167 <input type
='hidden' name
='form_payer' value
='<?php echo $inv_payer ?>' />
1168 <input type
='hidden' name
='form_encounter' value
='<?php echo $inv_encounter ?>' />
1177 <script language
='JavaScript'>
1178 Calendar
.setup({inputField
:"form_date", ifFormat
:"%Y-%m-%d", button
:"img_date"});
1181 // The following is removed, perhaps temporarily, because gcac reporting
1182 // no longer depends on gcac issues. -- Rod 2009-08-11
1183 /*********************************************************************
1184 // Custom code for IPPF. Try to make sure that a GCAC issue is linked to this
1185 // visit if it contains GCAC-related services.
1186 if ($gcac_related_visit) {
1187 $grow = sqlQuery("SELECT l.id, l.title, l.begdate, ie.pid " .
1188 "FROM lists AS l " .
1189 "LEFT JOIN issue_encounter AS ie ON ie.pid = l.pid AND " .
1190 "ie.encounter = '$inv_encounter' AND ie.list_id = l.id " .
1191 "WHERE l.pid = '$pid' AND " .
1192 "l.activity = 1 AND l.type = 'ippf_gcac' " .
1193 "ORDER BY ie.pid DESC, l.begdate DESC LIMIT 1");
1194 // Note that reverse-ordering by ie.pid is a trick for sorting
1195 // issues linked to the encounter (non-null values) first.
1196 if (empty($grow['pid'])) { // if there is no linked GCAC issue
1197 if (!empty($grow)) { // there is one that is not linked
1198 echo " if (confirm('" . xl('OK to link the GCAC issue dated') . " " .
1199 $grow['begdate'] . " " . xl('to this visit?') . "')) {\n";
1200 echo " $.getScript('link_issue_to_encounter.php?issue=" . $grow['id'] .
1201 "&thisenc=$inv_encounter');\n";
1204 echo " if (confirm('" . xl('Are you prepared to complete a new GCAC issue for this visit?') . "')) {\n";
1205 echo " dlgopen('summary/add_edit_issue.php?thisenc=$inv_encounter" .
1206 "&thistype=ippf_gcac', '_blank', 700, 600);\n";
1208 echo " $.getScript('link_issue_to_encounter.php?thisenc=$inv_encounter');\n";
1211 } // end if ($gcac_related_visit)
1212 *********************************************************************/
1214 if ($gcac_related_visit && !$gcac_service_provided) {
1215 // Skip this warning if the GCAC visit form is not allowed.
1216 $grow = sqlQuery("SELECT COUNT(*) AS count FROM list_options " .
1217 "WHERE list_id = 'lbfnames' AND option_id = 'LBFgcac'");
1218 if (!empty($grow['count'])) { // if gcac is used
1219 // Skip this warning if referral or abortion in TS.
1220 $grow = sqlQuery("SELECT COUNT(*) AS count FROM transactions " .
1221 "WHERE title = 'Referral' AND refer_date IS NOT NULL AND " .
1222 "refer_date = '$inv_date' AND pid = '$patient_id'");
1223 if (empty($grow['count'])) { // if there is no referral
1224 $grow = sqlQuery("SELECT COUNT(*) AS count FROM forms " .
1225 "WHERE pid = '$patient_id' AND encounter = '$inv_encounter' AND " .
1226 "deleted = 0 AND formdir = 'LBFgcac'");
1227 if (empty($grow['count'])) { // if there is no gcac form
1228 echo " alert('" . xl('This visit will need a GCAC form, referral or procedure service.') . "');\n";
1232 } // end if ($gcac_related_visit)