removed dangling </table>
[openemr.git] / interface / patient_file / pos_checkout.php
blobac39c6760cc8b2a0047ca4c365ce6d0c1abe982c
1 <?php
2 // Copyright (C) 2006 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.
17 // (2) Drug sales without an encounter will have 20YYMMDD, possibly
18 // with a suffix, as the encounter-number portion of their invoice
19 // number.
20 // (3) Payments are saved in SL only, don't mess with the billing table.
21 // See library/classes/WSClaim.class.php for posting code.
22 // (4) On checkout, the billing table entries are marked as billed and
23 // so become unavailable for further billing.
24 // (5) On checkout, drug_sales entries are marked as billed by having
25 // an invoice number assigned.
26 // (6) Receipt printing must be a separate operation from payment,
27 // and repeatable, therefore driven from the SL database and
28 // mirroring the contents of the invoice.
30 require_once("../globals.php");
31 require_once("$srcdir/patient.inc");
32 require_once("$srcdir/sql-ledger.inc");
33 require_once("$srcdir/freeb/xmlrpc.inc");
34 require_once("$srcdir/freeb/xmlrpcs.inc");
36 // Get the patient's name and chart number.
37 $patdata = getPatientData($pid, 'fname,mname,lname,pubpid,street,city,state,postal_code');
39 //////////////////////////////////////////////////////////////////////
40 // The following functions are inline here temporarily, and should be
41 // moved to an includable module for common use. In particular
42 // WSClaim.class.php should be rewritten to use them.
43 //////////////////////////////////////////////////////////////////////
45 // Initialize the array of invoice information for posting to the
46 // accounting system.
48 function invoice_initialize(& $invoice_info, $patient_id, $provider_id,
49 $payer_id = 0, $encounter = 0, $dosdate = '')
51 $db = $GLOBALS['adodb']['db'];
53 // Get foreign ID (customer) for patient.
54 $sql = "SELECT foreign_id from integration_mapping as im " .
55 "LEFT JOIN patient_data as pd on im.local_id=pd.id " .
56 "where pd.pid = '" .
57 $patient_id .
58 "' and im.local_table='patient_data' and im.foreign_table='customer'";
59 $result = $db->Execute($sql);
60 if($result && !$result->EOF) {
61 $foreign_patient_id = $result->fields['foreign_id'];
63 else {
64 return "Patient '" . $patient_id . "' has not yet been posted to the accounting system.";
67 // Get foreign ID (salesman) for provider.
68 $sql = "SELECT foreign_id from integration_mapping WHERE " .
69 "local_id = $provider_id AND local_table='users' and foreign_table='salesman'";
70 $result = $db->Execute($sql);
71 if($result && !$result->EOF) {
72 $foreign_provider_id = $result->fields['foreign_id'];
74 else {
75 return "Provider '" . $provider_id . "' has not yet been posted to the accounting system.";
78 // Get foreign ID (customer) for insurance payer.
79 if ($payer_id && ! $GLOBALS['insurance_companies_are_not_customers']) {
80 $sql = "SELECT foreign_id from integration_mapping WHERE " .
81 "local_id = $payer_id AND local_table = 'insurance_companies' AND foreign_table='customer'";
82 $result = $db->Execute($sql);
83 if($result && !$result->EOF) {
84 $foreign_payer_id = $result->fields['foreign_id'];
86 else {
87 return "Payer '" . $payer_id . "' has not yet been posted to the accounting system.";
89 } else {
90 $foreign_payer_id = $payer_id;
93 // Create invoice notes for the new invoice that list the patient's
94 // insurance plans. This is so that when payments are posted, the user
95 // can easily see if a secondary claim needs to be submitted.
97 $insnotes = "";
98 $insno = 0;
99 foreach (array("primary", "secondary", "tertiary") as $instype) {
100 ++$insno;
101 $sql = "SELECT insurance_companies.name " .
102 "FROM insurance_data, insurance_companies WHERE " .
103 "insurance_data.pid = $patient_id AND " .
104 "insurance_data.type = '$instype' AND " .
105 "insurance_companies.id = insurance_data.provider " .
106 "ORDER BY insurance_data.date DESC LIMIT 1";
107 $result = $db->Execute($sql);
108 if ($result && !$result->EOF && $result->fields['name']) {
109 if ($insnotes) $insnotes .= "\n";
110 $insnotes .= "Ins$insno: " . $result->fields['name'];
113 $invoice_info['notes'] = $insnotes;
115 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $dosdate, $matches)) {
116 $dosdate = $matches[2] . '-' . $matches[3] . '-' . $matches[1];
117 } else {
118 $dosdate = date("m-d-Y");
121 $invoice_info['salesman'] = $foreign_provider_id;
122 $invoice_info['customerid'] = $foreign_patient_id;
123 $invoice_info['payer_id'] = $foreign_payer_id;
124 $invoice_info['invoicenumber'] = $patient_id . "." . $encounter;
125 $invoice_info['dosdate'] = $dosdate;
126 $invoice_info['items'] = array();
127 $invoice_info['total'] = '0.00';
129 return '';
132 function invoice_add_line_item(& $invoice_info, $code_type, $code,
133 $code_text, $amount)
135 $tii = array();
136 $tii['maincode'] = $code;
137 $tii['itemtext'] = "$code_type:$code";
138 if ($code_text) $tii['itemtext'] .= " $code_text";
139 $tii['qty'] = 1;
140 $tii['price'] = sprintf("%01.2f", $amount);
141 $tii['glaccountid'] = $GLOBALS['oer_config']['ws_accounting']['income_acct'];
142 $invoice_info['total'] = sprintf("%01.2f", $invoice_info['total'] + $tii['price']);
143 $invoice_info['items'][] = $tii;
144 return '';
147 function invoice_post(& $invoice_info)
149 $function['ezybiz.add_invoice'] = array(new xmlrpcval($invoice_info, "struct"));
151 list($name, $var) = each($function);
152 $f = new xmlrpcmsg($name, $var);
154 $c = new xmlrpc_client($GLOBALS['oer_config']['ws_accounting']['url'],
155 $GLOBALS['oer_config']['ws_accounting']['server'],
156 $GLOBALS['oer_config']['ws_accounting']['port']);
158 $c->setCredentials($GLOBALS['oer_config']['ws_accounting']['username'],
159 $GLOBALS['oer_config']['ws_accounting']['password']);
161 $r = $c->send($f);
162 if (!$r) return "XMLRPC send failed";
164 // We are not doing anything with the return value yet... should we?
165 $tv = $r->value();
166 if (is_object($tv)) {
167 $value = $tv->getval();
169 else {
170 $value = null;
173 if ($r->faultCode()) {
174 return "Fault: Code: " . $r->faultCode() . " Reason '" . $r->faultString() . "'";
177 return '';
180 /////////////////// End of invoice posting functions /////////////////
182 // Generate a receipt from the last-billed invoice for this patient.
184 function generate_receipt($patient_id) {
185 global $sl_err, $sl_cash_acc, $css_header;
187 // Get details for what we guess is the primary facility.
188 $frow = sqlQuery("SELECT * FROM facility " .
189 "ORDER BY billing_location DESC, accepts_assignment DESC, id LIMIT 1");
191 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
193 SLConnect();
195 // Get the most recent invoice data into $arrow.
197 $arres = SLQuery("SELECT * FROM ar WHERE " .
198 "invnumber LIKE '$patient_id.%' " .
199 "ORDER BY id DESC LIMIT 1");
200 if ($sl_err) die($sl_err);
201 $arrow = SLGetRow($arres, 0);
202 if (! $arrow) die(xl("This patient has no activity."));
204 $trans_id = $arrow['id'];
206 // Determine the date of service. An 8-digit encounter number is
207 // presumed to be a date of service imported during conversion or
208 // associated with prescriptions only. Otherwise look it up in the
209 // form_encounter table.
211 $svcdate = "";
212 list($trash, $encounter) = explode(".", $arrow['invnumber']);
213 if (strlen($encounter) >= 8) {
214 $svcdate = substr($encounter, 0, 4) . "-" . substr($encounter, 4, 2) .
215 "-" . substr($encounter, 6, 2);
217 else if ($encounter) {
218 $tmp = sqlQuery("SELECT date FROM form_encounter WHERE " .
219 "encounter = $encounter");
220 $svcdate = substr($tmp['date'], 0, 10);
223 <html>
224 <head>
225 <link rel='stylesheet' href='<?php echo $css_header ?>' type='text/css'>
226 <title><? xl('Receipt for Payment','e'); ?></title>
227 </head>
228 <body <?echo $top_bg_line;?> leftmargin='0' topmargin='0' marginwidth='0' marginheight='0'>
229 <center>
230 <p><b><?php echo $frow['name'] ?>
231 <br><?php echo $frow['street'] ?>
232 <br><?php echo $frow['city'] . ', ' . $frow['state'] . ' ' . $frow['postal_code'] ?>
233 <br><?php echo $frow['phone'] ?>
234 <br>&nbsp;
235 <br><?php echo date('F j, Y') ?>
236 <br>&nbsp;
237 </b></p>
238 </center>
240 <?php echo $patdata['fname'] . ' ' . $patdata['mname'] . ' ' . $patdata['lname'] ?>
241 <br><?php echo $patdata['street'] ?>
242 <br><?php echo $patdata['city'] . ', ' . $patdata['state'] . ' ' . $patdata['postal_code'] ?>
243 <br>&nbsp;
244 </p>
245 <center>
246 <table>
247 <tr>
248 <td><b><?php xl('Date','e'); ?></b></td>
249 <td><b><?php xl('Description','e'); ?></b></td>
250 <td align='right'><b><?php xl('Amount','e'); ?></b></td>
251 </tr>
252 <?php
253 $charges = 0.00;
255 // Request all line items with money belonging to the invoice.
256 $inres = SLQuery("select * from invoice where trans_id = $trans_id and sellprice != 0");
257 if ($sl_err) die($sl_err);
259 for ($irow = 0; $irow < SLRowCount($inres); ++$irow) {
260 $row = SLGetRow($inres, $irow);
261 $amount = sprintf('%01.2f', $row['sellprice']);
262 $charges += $amount;
263 echo " <tr>\n";
264 echo " <td>$svcdate</td>\n";
265 echo " <td>" . $row['description'] . "</td>\n";
266 echo " <td align='right'>$amount</td>\n";
267 echo " </tr>\n";
270 $chart_id_cash = SLQueryValue("select id from chart where accno = '$sl_cash_acc'");
271 if ($sl_err) die($sl_err);
272 if (! $chart_id_cash) die("There is no COA entry for cash account '$sl_cash_acc'");
274 // Request all cash entries belonging to the invoice.
275 $atres = SLQuery("select * from acc_trans where trans_id = $trans_id and chart_id = $chart_id_cash");
276 if ($sl_err) die($sl_err);
278 for ($irow = 0; $irow < SLRowCount($atres); ++$irow) {
279 $row = SLGetRow($atres, $irow);
280 $amount = sprintf('%01.2f', $row['amount']); // negative
281 $charges += $amount;
282 $rowsource = $row['source'];
283 if (strtolower($rowsource) == 'co-pay') $rowsource = '';
284 echo " <tr>\n";
285 echo " <td>" . $row['transdate'] . "</td>\n";
286 echo " <td>Payment $rowsource</td>\n";
287 echo " <td align='right'>$amount</td>\n";
288 echo " </tr>\n";
291 <tr>
292 <td colspan='3'>&nbsp;</td>
293 </tr>
294 <tr>
295 <td>&nbsp;</td>
296 <td><b><?php xl('Balance Due','e'); ?></b></td>
297 <td align='right'><?php echo sprintf('%01.2f', $charges) ?></td>
298 </tr>
299 </table>
300 </center>
301 <p>&nbsp;<a href='' onclick='window.print(); return false;'><?php xl('Print','e'); ?></a></p>
302 </body>
303 </html>
304 <?php
305 SLClose();
306 } // end function
308 // Function to output a line item for the input form.
310 $lino = 0;
311 function write_form_line($code_type, $code, $id, $date, $description, $amount) {
312 global $lino;
313 $amount = sprintf("%01.2f", $amount);
314 echo " <tr>\n";
315 echo " <td>$date";
316 echo "<input type='hidden' name='line[$lino][code_type]' value='$code_type'>";
317 echo "<input type='hidden' name='line[$lino][code]' value='$code'>";
318 echo "<input type='hidden' name='line[$lino][id]' value='$id'>";
319 echo "<input type='hidden' name='line[$lino][description]' value='$description'>";
320 echo "</td>\n";
321 echo " <td>$description</td>";
322 echo " <td align='right'><input type='text' name='line[$lino][amount]' " .
323 "value='$amount' size='6' maxlength='8' style='text-align:right'></td>\n";
324 echo " </tr>\n";
325 ++$lino;
328 $payment_methods = array(
329 'Cash',
330 'Check',
331 'MC',
332 'VISA',
333 'AMEX',
334 'DISC',
335 'Other');
337 $alertmsg = ''; // anything here pops up in an alert box
339 // If the Save button was clicked...
341 if ($_POST['form_save']) {
343 // On a save, do the following:
344 // Flag drug_sales and billing items as billed.
345 // Post the corresponding invoice with its payment(s) to sql-ledger
346 // and be careful to use a unique invoice number.
347 // Call the generate-receipt function.
348 // Exit.
350 $form_pid = $_POST['form_pid'];
351 $form_encounter = $_POST['form_encounter'];
353 // Get the posting date from the form as yyyy-mm-dd.
354 $dosdate = date("Y-m-d");
355 if (preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $_POST['form_date'], $matches)) {
356 $dosdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
359 // If there is no associated encounter (i.e. this invoice has only
360 // prescriptions) then assign an encounter number of the service
361 // date, with an optional suffix to ensure that it's unique.
363 if (! $form_encounter) {
364 SLConnect();
365 $form_encounter = substr($dosdate,0,4) . substr($dosdate,5,2) . substr($dosdate,8,2);
366 $tmp = '';
367 while (SLQueryValue("select id from ar where " .
368 "invnumber = '$form_pid.$form_encounter$tmp'")) {
369 $tmp = $tmp ? $tmp + 1 : 1;
371 $form_encounter .= $tmp;
372 SLClose();
375 // Initialize an array of invoice information for posting.
377 $invoice_info = array();
378 $msg = invoice_initialize($invoice_info, $form_pid,
379 $_POST['form_provider'], $_POST['form_payer'], $form_encounter, $dosdate);
380 if ($msg) die($msg);
382 $form_amount = $_POST['form_amount'];
383 $lines = $_POST['line'];
385 for ($lino = 0; $lines[$lino]['code_type']; ++$lino) {
386 $line = $lines[$lino];
388 $code_type = $line['code_type'];
389 $id = $line['id'];
390 $amount = sprintf('%01.2f', trim($line['amount']));
392 $msg = invoice_add_line_item($invoice_info, $code_type,
393 $line['code'], $line['description'], $amount);
394 if ($msg) die($msg);
396 if ($code_type == 'MED') {
397 $query = "update drug_sales SET fee = '$amount', " .
398 "encounter = '$form_encounter' WHERE " .
399 "sale_id = '$id'";
400 sqlQuery($query);
402 else {
403 // Because there is no insurance here, there is no need for a claims
404 // table entry and so we do not call updateClaim(). Note we should not
405 // eliminate billed and bill_date from the billing table!
406 $query = "UPDATE billing SET billed = 1, bill_date = NOW() WHERE " .
407 "id = '$id'";
408 sqlQuery($query);
412 if ($_POST['form_amount']) {
413 $paydesc = $_POST['form_source'] ? $_POST['form_source'] : '';
414 $msg = invoice_add_line_item($invoice_info, 'COPAY',
415 $_POST['form_method'],
416 $paydesc,
417 $_POST['form_amount']);
418 if ($msg) die($msg);
421 $msg = invoice_post($invoice_info);
422 if ($msg) die($msg);
424 generate_receipt($_POST['form_pid']);
425 exit();
428 // Get the unbilled billing table items and prescription sales for
429 // this patient.
431 $query = "SELECT id, date, code_type, code, code_text, " .
432 "provider_id, payer_id, fee, encounter " .
433 "FROM billing " .
434 "WHERE pid = '$pid' AND activity = 1 AND billed = 0 " .
435 "ORDER BY encounter";
436 $bres = sqlStatement($query);
438 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, " .
439 "d.name, r.provider_id " .
440 "FROM drug_sales AS s " .
441 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
442 "LEFT JOIN prescriptions AS r ON r.id = s.prescription_id " .
443 "WHERE s.pid = '$pid' AND s.encounter = 0 " .
444 "ORDER BY s.sale_id";
445 $dres = sqlStatement($query);
447 // If there are none, just redisplay the last receipt and exit.
449 if (mysql_num_rows($bres) == 0 && mysql_num_rows($dres) == 0) {
450 generate_receipt($pid);
451 exit();
454 // Now write a data entry form:
455 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
456 // List unbilled prescription sales for the patient.
457 // Present an editable dollar amount for each line item, a total
458 // which is also the default value of the input payment amount,
459 // and OK and Cancel buttons.
461 <html>
462 <head>
463 <link rel='stylesheet' href='<?php echo $css_header ?>' type='text/css'>
464 <title><? xl('Patient Checkout','e'); ?></title>
465 <style>
466 </style>
467 <style type="text/css">@import url(../../library/dynarch_calendar.css);</style>
468 <script type="text/javascript" src="../../library/textformat.js"></script>
469 <script type="text/javascript" src="../../library/dynarch_calendar.js"></script>
470 <script type="text/javascript" src="../../library/dynarch_calendar_en.js"></script>
471 <script type="text/javascript" src="../../library/dynarch_calendar_setup.js"></script>
472 <script language="JavaScript">
473 var mypcc = '<? echo $GLOBALS['phone_country_code'] ?>';
474 </script>
475 </head>
477 <body <?echo $top_bg_line;?> leftmargin='0' topmargin='0' marginwidth='0'
478 marginheight='0'>
480 <form method='post' action='pos_checkout.php'>
481 <input type='hidden' name='form_pid' value='<?php echo $pid ?>' />
483 <center>
486 <table cellspacing='5'>
487 <tr>
488 <td colspan='3' align='center'>
489 <b><? xl('Patient Checkout for ','e'); ?><?php echo $patdata['fname'] . " " .
490 $patdata['lname'] . " (" . $patdata['pubpid'] . ")" ?></b>
491 </td>
492 </tr>
493 <tr>
494 <td><b><?php xl('Date','e'); ?></b></td>
495 <td><b><?php xl('Description','e'); ?></b></td>
496 <td align='right'><b><?php xl('Amount','e'); ?></b>&nbsp;</td>
497 </tr>
498 <?php
499 $inv_encounter = '';
500 $inv_date = '';
501 $inv_provider = 0;
502 $inv_payer = 0;
503 $total = 0.00;
504 while ($brow = sqlFetchArray($bres)) {
505 $thisdate = substr($brow['date'], 0, 10);
506 write_form_line($brow['code_type'], $brow['code'], $brow['id'],
507 $thisdate, $brow['code_text'], $brow['fee']);
508 $inv_encounter = $brow['encounter'];
509 $inv_provider = $brow['provider_id'];
510 $inv_payer = $brow['payer_id'];
511 if (!$inv_date || $inv_date < $thisdate) $inv_date = $thisdate;
512 $total += $brow['fee'];
514 while ($drow = sqlFetchArray($dres)) {
515 $thisdate = $drow['sale_date'];
516 write_form_line('MED', $drow['prescription_id'], $drow['sale_id'],
517 $thisdate, $drow['name'], $drow['fee']);
518 $inv_provider = $drow['provider_id'];
519 if (!$inv_date || $inv_date < $thisdate) $inv_date = $thisdate;
520 $total += $drow['fee'];
523 </table>
526 <table border='0' cellspacing='8'>
528 <tr>
529 <td>
530 <? xl('Payment Method','e'); ?>:
531 </td>
532 <td>
533 <select name='form_method'>
535 foreach ($payment_methods as $value) {
536 echo " <option value='$value'";
537 echo ">$value</option>\n";
540 </select>
541 </td>
542 </tr>
544 <tr>
545 <td>
546 <? xl('Check/Reference Number','e'); ?>:
547 </td>
548 <td>
549 <input type='text' name='form_source' size='10' value=''>
550 </td>
551 </tr>
553 <tr>
554 <td>
555 <? xl('Amount Paid','e'); ?>:
556 </td>
557 <td>
558 <input type='text' name='form_amount' size='10' value='<?php echo sprintf("%01.2f", $total) ?>'>
559 </td>
560 </tr>
562 <tr>
563 <td>
564 <?php xl('Posting Date','e'); ?>:
565 </td>
566 <td>
567 <input type='text' size='10' name='form_date' id='form_date'
568 value='<? echo $inv_date ?>'
569 title='yyyy-mm-dd date of service'
570 onkeyup='datekeyup(this,mypcc)' onblur='dateblur(this,mypcc)' />
571 <img src='../pic/show_calendar.gif' align='absbottom' width='24' height='22'
572 id='img_date' border='0' alt='[?]' style='cursor:pointer'
573 title='Click here to choose a date'>
574 </td>
575 </tr>
577 <tr>
578 <td colspan='2' align='center'>
579 &nbsp;<br>
580 <input type='submit' name='form_save' value='<?php xl('Save','e'); ?>' /> &nbsp;
581 <input type='button' value='Cancel' onclick='window.close()' />
582 <input type='hidden' name='form_provider' value='<?php echo $inv_provider ?>' />
583 <input type='hidden' name='form_payer' value='<?php echo $inv_payer ?>' />
584 <input type='hidden' name='form_encounter' value='<?php echo $inv_encounter ?>' />
585 </td>
586 </tr>
588 </table>
589 </center>
591 </form>
593 <script language='JavaScript'>
594 Calendar.setup({inputField:"form_date", ifFormat:"%Y-%m-%d", button:"img_date"});
595 </script>
597 </body>
598 </html>