2 // Copyright (C) 2006 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.
17 // (2) Drug sales without an encounter will have 20YYMMDD, possibly
18 // with a suffix, as the encounter-number portion of their invoice
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
48 function invoice_initialize(& $invoice_info, $patient_id, $provider_id,
49 $payer_id = 0, $encounter = 0)
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 " .
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'];
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'];
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'];
87 return "Payer '" . $payer_id . "' has not yet been posted to the accounting system.";
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.
99 foreach (array("primary", "secondary", "tertiary") as $instype) {
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 " .
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 $invoice_info['salesman'] = $foreign_provider_id;
116 $invoice_info['customerid'] = $foreign_patient_id;
117 $invoice_info['payer_id'] = $foreign_payer_id;
118 $invoice_info['invoicenumber'] = $patient_id . "." . $encounter;
119 $invoice_info['dosdate'] = date("m-d-Y");
120 $invoice_info['items'] = array();
121 $invoice_info['total'] = '0.00';
126 function invoice_add_line_item(& $invoice_info, $code_type, $code,
130 $tii['maincode'] = $code;
131 $tii['itemtext'] = "$code_type:$code";
132 if ($code_text) $tii['itemtext'] .= " $code_text";
134 $tii['price'] = sprintf("%01.2f", $amount);
135 $tii['glaccountid'] = $GLOBALS['oer_config']['ws_accounting']['income_acct'];
136 $invoice_info['total'] = sprintf("%01.2f", $invoice_info['total'] +
$tii['price']);
137 $invoice_info['items'][] = $tii;
141 function invoice_post(& $invoice_info)
143 $function['ezybiz.add_invoice'] = array(new xmlrpcval($invoice_info, "struct"));
145 list($name, $var) = each($function);
146 $f = new xmlrpcmsg($name, $var);
148 $c = new xmlrpc_client($GLOBALS['oer_config']['ws_accounting']['url'],
149 $GLOBALS['oer_config']['ws_accounting']['server'],
150 $GLOBALS['oer_config']['ws_accounting']['port']);
152 $c->setCredentials($GLOBALS['oer_config']['ws_accounting']['username'],
153 $GLOBALS['oer_config']['ws_accounting']['password']);
156 if (!$r) return "XMLRPC send failed";
158 // We are not doing anything with the return value yet... should we?
160 if (is_object($tv)) {
161 $value = $tv->getval();
167 if ($r->faultCode()) {
168 return "Fault: Code: " . $r->faultCode() . " Reason '" . $r->faultString() . "'";
174 /////////////////// End of invoice posting functions /////////////////
176 // Generate a receipt from the last-billed invoice for this patient.
178 function generate_receipt($patient_id) {
179 global $sl_err, $sl_cash_acc, $css_header;
181 // Get details for what we guess is the primary facility.
182 $frow = sqlQuery("SELECT * FROM facility " .
183 "ORDER BY billing_location DESC, accepts_assignment DESC, id LIMIT 1");
185 $patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
189 // Get the most recent invoice data into $arrow.
191 $arres = SLQuery("SELECT * FROM ar WHERE " .
192 "invnumber LIKE '$patient_id.%' " .
193 "ORDER BY id DESC LIMIT 1");
194 if ($sl_err) die($sl_err);
195 $arrow = SLGetRow($arres, 0);
196 if (! $arrow) die(xl("This patient has no activity."));
198 $trans_id = $arrow['id'];
200 // Determine the date of service. An 8-digit encounter number is
201 // presumed to be a date of service imported during conversion or
202 // associated with prescriptions only. Otherwise look it up in the
203 // form_encounter table.
206 list($trash, $encounter) = explode(".", $arrow['invnumber']);
207 if (strlen($encounter) >= 8) {
208 $svcdate = substr($encounter, 0, 4) . "-" . substr($encounter, 4, 2) .
209 "-" . substr($encounter, 6, 2);
211 else if ($encounter) {
212 $tmp = sqlQuery("SELECT date FROM form_encounter WHERE " .
213 "encounter = $encounter");
214 $svcdate = substr($tmp['date'], 0, 10);
219 <link rel
='stylesheet' href
='<?php echo $css_header ?>' type
='text/css'>
220 <title
><?
xl('Receipt for Payment','e'); ?
></title
>
222 <body
<?
echo $top_bg_line;?
> leftmargin
='0' topmargin
='0' marginwidth
='0' marginheight
='0'>
224 <p
><b
><?php
echo $frow['name'] ?
>
225 <br
><?php
echo $frow['street'] ?
>
226 <br
><?php
echo $frow['city'] . ', ' . $frow['state'] . ' ' . $frow['postal_code'] ?
>
227 <br
><?php
echo $frow['phone'] ?
>
229 <br
><?php
echo date('F j, Y') ?
>
234 <?php
echo $patdata['fname'] . ' ' . $patdata['mname'] . ' ' . $patdata['lname'] ?
>
235 <br
><?php
echo $patdata['street'] ?
>
236 <br
><?php
echo $patdata['city'] . ', ' . $patdata['state'] . ' ' . $patdata['postal_code'] ?
>
243 <td
><b
>Description
</b
></td
>
244 <td align
='right'><b
>Amount
</b
></td
>
249 // Request all line items with money belonging to the invoice.
250 $inres = SLQuery("select * from invoice where trans_id = $trans_id and sellprice != 0");
251 if ($sl_err) die($sl_err);
253 for ($irow = 0; $irow < SLRowCount($inres); ++
$irow) {
254 $row = SLGetRow($inres, $irow);
255 $amount = sprintf('%01.2f', $row['sellprice']);
258 echo " <td>$svcdate</td>\n";
259 echo " <td>" . $row['description'] . "</td>\n";
260 echo " <td align='right'>$amount</td>\n";
264 $chart_id_cash = SLQueryValue("select id from chart where accno = '$sl_cash_acc'");
265 if ($sl_err) die($sl_err);
266 if (! $chart_id_cash) die("There is no COA entry for cash account '$sl_cash_acc'");
268 // Request all cash entries belonging to the invoice.
269 $atres = SLQuery("select * from acc_trans where trans_id = $trans_id and chart_id = $chart_id_cash");
270 if ($sl_err) die($sl_err);
272 for ($irow = 0; $irow < SLRowCount($atres); ++
$irow) {
273 $row = SLGetRow($atres, $irow);
274 $amount = sprintf('%01.2f', $row['amount']); // negative
276 $rowsource = $row['source'];
277 if (strtolower($rowsource) == 'co-pay') $rowsource = '';
279 echo " <td>" . $row['transdate'] . "</td>\n";
280 echo " <td>Payment $rowsource</td>\n";
281 echo " <td align='right'>$amount</td>\n";
286 <td colspan
='3'> 
;</td
>
290 <td
><b
>Balance Due
</b
></td
>
291 <td align
='right'><?php
echo sprintf('%01.2f', $charges) ?
></td
>
295 <p
> 
;<a href
='' onclick
='window.print(); return false;'>Print</a
></p
>
302 // Function to output a line item for the input form.
305 function write_form_line($code_type, $code, $id, $date, $description, $amount) {
307 $amount = sprintf("%01.2f", $amount);
310 echo "<input type='hidden' name='line[$lino][code_type]' value='$code_type'>";
311 echo "<input type='hidden' name='line[$lino][code]' value='$code'>";
312 echo "<input type='hidden' name='line[$lino][id]' value='$id'>";
313 echo "<input type='hidden' name='line[$lino][description]' value='$description'>";
315 echo " <td>$description</td>";
316 echo " <td align='right'><input type='text' name='line[$lino][amount]' " .
317 "value='$amount' size='6' maxlength='8' style='text-align:right'></td>\n";
322 $payment_methods = array(
331 $alertmsg = ''; // anything here pops up in an alert box
333 // If the Save button was clicked...
335 if ($_POST['form_save']) {
337 // On a save, do the following:
338 // Flag drug_sales and billing items as billed.
339 // Post the corresponding invoice with its payment(s) to sql-ledger
340 // and be careful to use a unique invoice number.
341 // Call the generate-receipt function.
344 $form_pid = $_POST['form_pid'];
345 $form_encounter = $_POST['form_encounter'];
347 // If there is no associated encounter (i.e. this invoice has only
348 // prescriptions) then assign an encounter number of the current
349 // date, with an optional suffix to ensure that it's unique.
351 if (! $form_encounter) {
353 $form_encounter = date('Ymd');
355 while (SLQueryValue("select id from ar where " .
356 "invnumber = '$form_pid.$form_encounter$tmp'")) {
357 $tmp = $tmp ?
$tmp +
1 : 1;
359 $form_encounter .= $tmp;
363 // Initialize an array of invoice information for posting.
365 $invoice_info = array();
366 $msg = invoice_initialize($invoice_info, $_POST['form_pid'],
367 $_POST['form_provider'], $_POST['form_payer'], $form_encounter);
370 $form_amount = $_POST['form_amount'];
371 $lines = $_POST['line'];
373 for ($lino = 0; $lines[$lino]['code_type']; ++
$lino) {
374 $line = $lines[$lino];
376 $code_type = $line['code_type'];
378 $amount = sprintf('%01.2f', trim($line['amount']));
380 $msg = invoice_add_line_item($invoice_info, $code_type,
381 $line['code'], $line['description'], $amount);
384 if ($code_type == 'MED') {
385 $query = "update drug_sales SET fee = '$amount', " .
386 "encounter = '$form_encounter' WHERE " .
390 $query = "UPDATE billing SET billed = 1, bill_date = NOW() WHERE " .
394 // echo $query . "<br>\n"; // debugging
397 if ($_POST['form_amount']) {
398 $paydesc = $_POST['form_source'] ?
$_POST['form_source'] : '';
399 $msg = invoice_add_line_item($invoice_info, 'COPAY',
400 $_POST['form_method'],
402 $_POST['form_amount']);
406 $msg = invoice_post($invoice_info);
409 generate_receipt($_POST['form_pid']);
413 // Get the unbilled billing table items and prescription sales for
416 $query = "SELECT id, date, code_type, code, code_text, " .
417 "provider_id, payer_id, fee, encounter " .
419 "WHERE pid = '$pid' AND activity = 1 AND billed = 0 " .
420 "ORDER BY encounter";
421 $bres = sqlStatement($query);
423 $query = "SELECT s.sale_id, s.sale_date, s.prescription_id, s.fee, " .
424 "d.name, r.provider_id " .
425 "FROM drug_sales AS s " .
426 "LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
427 "LEFT JOIN prescriptions AS r ON r.id = s.prescription_id " .
428 "WHERE s.pid = '$pid' AND s.encounter = 0 " .
429 "ORDER BY s.sale_id";
430 $dres = sqlStatement($query);
432 // If there are none, just redisplay the last receipt and exit.
434 if (mysql_num_rows($bres) == 0 && mysql_num_rows($dres) == 0) {
435 generate_receipt($pid);
439 // Now write a data entry form:
440 // List unbilled billing items (cpt, hcpcs, copays) for the patient.
441 // List unbilled prescription sales for the patient.
442 // Present an editable dollar amount for each line item, a total
443 // which is also the default value of the input payment amount,
444 // and OK and Cancel buttons.
448 <link rel
='stylesheet' href
='<?php echo $css_header ?>' type
='text/css'>
449 <title
><?
xl('Patient Checkout','e'); ?
></title
>
452 <script language
="JavaScript">
456 <body
<?
echo $top_bg_line;?
> leftmargin
='0' topmargin
='0' marginwidth
='0'
459 <form method
='post' action
='pos_checkout.php'>
460 <input type
='hidden' name
='form_pid' value
='<?php echo $pid ?>' />
465 <table cellspacing
='5'>
467 <td colspan
='3' align
='center'>
468 <b
><?
xl('Patient Checkout for ','e'); ?
><?php
echo $patdata['fname'] . " " .
469 $patdata['lname'] . " (" . $patdata['pubpid'] . ")" ?
></b
>
474 <td
><b
>Description
</b
></td
>
475 <td align
='right'><b
>Amount
</b
> 
;</td
>
482 while ($brow = sqlFetchArray($bres)) {
483 write_form_line($brow['code_type'], $brow['code'], $brow['id'],
484 substr($brow['date'], 0, 10), $brow['code_text'], $brow['fee']);
485 $inv_encounter = $brow['encounter'];
486 $inv_provider = $brow['provider_id'];
487 $inv_payer = $brow['payer_id'];
488 $total +
= $brow['fee'];
490 while ($drow = sqlFetchArray($dres)) {
491 write_form_line('MED', $drow['prescription_id'], $drow['sale_id'],
492 $drow['sale_date'], $drow['name'], $drow['fee']);
493 $inv_provider = $drow['provider_id'];
494 $total +
= $drow['fee'];
500 <table border
='0' cellspacing
='8'>
504 <?
xl('Payment Method','e'); ?
>:
507 <select name
='form_method'>
509 foreach ($payment_methods as $value) {
510 echo " <option value='$value'";
511 echo ">$value</option>\n";
520 <?
xl('Check/Reference Number','e'); ?
>:
523 <input type
='text' name
='form_source' size
='10' value
=''>
529 <?
xl('Amount Paid','e'); ?
>:
532 <input type
='text' name
='form_amount' size
='10' value
='<?php echo sprintf("%01.2f", $total) ?>'>
537 <td colspan
='2' align
='center'>
539 <input type
='submit' name
='form_save' value
='Save' />  
;
540 <input type
='button' value
='Cancel' onclick
='window.close()' />
541 <input type
='hidden' name
='form_provider' value
='<?php echo $inv_provider ?>' />
542 <input type
='hidden' name
='form_payer' value
='<?php echo $inv_payer ?>' />
543 <input type
='hidden' name
='form_encounter' value
='<?php echo $inv_encounter ?>' />