You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

562 lines
20 KiB

<?php
/**
* checkout_receipt_array.inc.php
*
* This provides a function to build an array of data for printing on a checkout
* receipt. The idea is to support a receipt-printing script that is very easy
* to understand and customize and does not have to be concerned with where the
* data comes from.
*
* LICENSE: This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* http://www.gnu.org/licenses/licenses.html#GPL .
*
* @package OpenEMR
* @license http://www.gnu.org/licenses/licenses.html#GPL GNU GPL V3+
* @author Rod Roark <rod@sunsetsystems.com>
* @copyright Copyright (c) 2013-2021 Rod Roark <rod@sunsetsystems.com>
* @link http://www.open-emr.org
*/
// Get a list item's title, translated if appropriate.
function getAdjustTitle($option)
{
$row = sqlQuery(
"SELECT title, notes FROM list_options WHERE " .
"list_id = 'adjreason' AND option_id = ? AND activity = 1",
array($option)
);
if (empty($row['title'])) {
return $option;
}
return xl_list_label($row['title']);
}
// Store a receipt line item.
//
function receiptArrayDetailLine(
&$aReceipt,
$code_type,
$code,
$description,
$quantity,
$charge,
$billtime = '',
$tax = 0,
$chargecat = ''
) {
$adjust = 0;
$adjreason = '';
// This supports Suriname insurers.
// It is set to true when the Customer is set and its notes include "=Ins".
$insurer = false;
if ($chargecat) {
$tmprow = sqlQuery(
"SELECT notes FROM list_options WHERE list_id = 'chargecats' AND " .
"option_id = ? AND activity = 1 AND notes LIKE '%=Ins%' LIMIT 1",
array($chargecat)
);
if (!empty($tmprow['notes'])) {
$insurer = true;
}
}
// If an invoice level adjustment, get it into the right column.
if ($code_type === '') {
$adjust = 0 - $charge;
$charge = 0;
} else {
// Otherwise pull out any adjustments matching this line item.
if (!empty($GLOBALS['gbl_checkout_line_adjustments'])) {
// Total and clear matching adjustments in $aReceipt['_adjusts'].
for ($i = 0; $i < count($aReceipt['_adjusts']); ++$i) {
if (
$aReceipt['_adjusts'][$i]['code_type'] == $code_type &&
$aReceipt['_adjusts'][$i]['code'] == $code &&
$aReceipt['_adjusts'][$i]['post_time'] == $billtime
) {
$adjust += $aReceipt['_adjusts'][$i]['adj_amount'];
if ($aReceipt['_adjusts'][$i]['memo']) {
$adjreason = getAdjustTitle($aReceipt['_adjusts'][$i]['memo']);
}
$aReceipt['_adjusts'][$i]['adj_amount'] = 0;
}
}
}
}
$charge = sprintf('%01.2f', $charge);
$total = sprintf('%01.2f', $charge - $adjust);
if (empty($quantity)) {
$quantity = 1;
}
$price = sprintf('%01.4f', $charge / $quantity);
$tmp = sprintf('%01.2f', $price);
if ($price == $tmp) {
$price = $tmp; // converts xx.xx00 to xx.xx.
}
$aReceipt['items'][] = array(
'code_type' => $code_type,
'code' => $code,
'description' => $description,
'price' => $price,
'quantity' => $quantity,
'charge' => $charge,
'adjustment' => sprintf('%01.2f', $adjust),
'adjreason' => $adjreason,
'total' => $total,
'insurer' => $insurer,
'tax' => $tax,
);
$aReceipt['total_price'] = sprintf('%01.2f', $aReceipt['total_price' ] + $price);
$aReceipt['total_quantity'] = sprintf('%01.2f', $aReceipt['total_quantity' ] + $quantity);
$aReceipt['total_charges'] = sprintf('%01.2f', $aReceipt['total_charge' ] + $charge);
$aReceipt['total_adjustments'] = sprintf('%01.2f', $aReceipt['total_adjustments'] + $adjust);
$aReceipt['total_totals'] = sprintf('%01.2f', $aReceipt['total_totals' ] + $total);
}
// Store a receipt payment line.
//
function receiptArrayPaymentLine(&$aReceipt, $paydate, $amount, $description = '', $method = '')
{
$amount = sprintf('%01.2f', $amount);
$aReceipt['payments'][] = array(
'date' => $paydate,
'method' => $method,
'description' => $description,
'amount' => $amount,
);
$aReceipt['total_payments'] += $amount;
}
// Generate a receipt data array from the last-billed invoice for this patient,
// or from the specified encounter.
//
function generateReceiptArray($patient_id, $encounter = 0, $billtime = '')
{
// Get the most recent invoice data or that for the specified encounter.
$query = "SELECT " .
"fe.id, fe.date, fe.encounter, fe.facility_id, fe.invoice_refno, " .
"u.fname, u.mname, u.lname " .
"FROM form_encounter AS fe " .
"LEFT JOIN users AS u ON u.id = fe.provider_id " .
"WHERE fe.pid = ? ";
$binds = array($patient_id);
if ($encounter) {
$query .= "AND encounter = ?";
$binds[] = $encounter;
} else {
$query .= "ORDER BY id DESC LIMIT 1";
}
$ferow = sqlQuery($query, $binds);
if (empty($ferow)) {
die(xlt("This patient has no activity."));
}
$trans_id = $ferow['id'];
$encounter = $ferow['encounter'];
$svcdate = substr($ferow['date'], 0, 10);
$invoice_refno = $ferow['invoice_refno'];
$docname = '';
if (!empty($ferow['fname'])) {
$docname = trim($ferow['fname']);
}
if (!empty($ferow['mname'])) {
if ($docname) {
$docname .= ' ';
}
$docname .= trim($ferow['fname']);
}
if (!empty($ferow['lname'])) {
if ($docname) {
$docname .= ' ';
}
$docname .= trim($ferow['lname']);
}
// Get details for the visit's facility and organization facility.
$frow = getFacility($ferow['facility_id']);
$orgrow = getFacility(0);
$patdata = getPatientData($patient_id, 'fname,mname,lname,pubpid,street,city,state,postal_code');
// Get text for the logged-in user's name (first middle last).
$username = "UID: " . $_SESSION["authUserID"];
$userrow = sqlQuery(
"SELECT id, username, fname, mname, lname FROM users " .
"WHERE id = ?",
array($_SESSION["authUserID"])
);
if ($userrow['id']) {
if (!empty($userrow['fname'])) {
$username = $userrow['fname'];
}
if (!empty($userrow['mname'])) {
if (!empty($username)) {
$username .= ' ';
}
$username .= $userrow['mname'];
}
if (!empty($userrow['lname'])) {
if (!empty($username)) {
$username .= ' ';
}
$username .= $userrow['lname'];
}
}
// Compute numbers for summary on right side of page.
$head_begbal = get_patient_balance_excluding($patient_id, $encounter);
$query = "SELECT SUM(fee) AS amount FROM billing WHERE " .
"pid = ? AND encounter = ? AND activity = 1 AND " .
"code_type != 'COPAY'";
$binds = array($patient_id, $encounter);
if ($billtime) {
$query .= " AND billed = 1 AND bill_date <= ?";
$binds[] = $billtime;
}
$row = sqlQuery($query, $binds);
$head_charges = $row['amount'];
$query = "SELECT SUM(fee) AS amount FROM drug_sales WHERE " .
"pid = ? AND encounter = ?";
$binds = array($patient_id, $encounter);
if ($billtime) {
$query .= " AND billed = 1 AND bill_date <= ?";
$binds[] = $billtime;
}
$row = sqlQuery($query, $binds);
$head_charges += $row['amount'];
$query = "SELECT SUM(pay_amount) AS payments, " .
"SUM(adj_amount) AS adjustments FROM ar_activity WHERE " .
"deleted IS NULL AND pid = ? AND encounter = ?";
$binds = array($patient_id, $encounter);
if ($billtime) {
$query .= " AND post_time <= ?";
$binds[] = $billtime;
}
$row = sqlQuery($query, $binds);
$head_charges -= $row['adjustments'];
$head_payments = $row['payments'];
$query = "SELECT SUM(fee) AS amount FROM billing WHERE " .
"pid = ? AND encounter = ? AND activity = 1 AND " .
"code_type = 'COPAY'";
$binds = array($patient_id, $encounter);
if ($billtime) {
$query .= " AND billed = 1 AND bill_date <= ?";
$binds[] = $billtime;
}
$row = sqlQuery($query, $binds);
$head_payments -= $row['amount'];
// This is the amount due for the encounter as a whole up to this point.
$encounter_due = $head_charges - $head_payments;
$head_endbal = $head_begbal + $encounter_due;
$aReceipt = array(
'encounter_id' => $encounter,
'encounter_date' => $svcdate,
'invoice_refno' => $invoice_refno,
'patient_id' => $patient_id,
'patient_pubpid' => $patdata['pubpid'],
'patient_fname' => $patdata['fname'],
'patient_mname' => $patdata['mname'],
'patient_lname' => $patdata['lname'],
'patient_street' => $patdata['street'],
'patient_city' => $patdata['city'],
'patient_state' => $patdata['state'],
'patient_zip' => $patdata['postal_code'],
'facility_id' => $frow['id'],
'facility_name' => $frow['name'],
'facility_street' => $frow['street'],
'facility_city' => $frow['city'],
'facility_state' => $frow['state'],
'facility_zip' => $frow['postal_code'],
'facility_phone' => $frow['phone'],
'facility_fax' => $frow['fax'],
'facility_url' => empty($frow['website']) ? '' : $frow['website'],
'organization_name' => $orgrow['name'],
'docname' => $docname,
'username' => $username,
'userlogin' => $userrow['username'],
'starting_balance' => $head_begbal,
'ending_balance' => $head_endbal,
'items' => array(),
'payments' => array(),
'total_price' => 0,
'total_quantity' => 0,
'total_charges' => 0,
'total_adjustments' => 0,
'total_totals' => 0,
'total_payments' => 0,
'checkout_prvbal' => 0, // see calculation at end of this function
'encounter_due' => $encounter_due,
'cashier' => '',
);
// Get timestamp of the previous checkout, if any.
$prevtime = '1900-01-01 00:00:00';
if ($billtime) {
$tmp = sqlQuery(
"(SELECT bill_date FROM billing WHERE pid = ? AND encounter = ? AND activity = 1 AND billed = 1 AND bill_date < ?) " .
"UNION " .
"(SELECT bill_date FROM drug_sales WHERE pid = ? AND encounter = ? AND billed = 1 AND bill_date < ?) " .
"ORDER BY bill_date DESC LIMIT 1",
array($patient_id, $encounter, $billtime, $patient_id, $encounter, $billtime)
);
if (!empty($tmp['bill_date'])) {
$prevtime = $tmp['bill_date'];
}
}
// Create array aAdjusts from ar_activity rows for $inv_encounter.
$aReceipt['_adjusts'] = array();
$ares = sqlStatement(
"SELECT " .
"a.payer_type, a.adj_amount, a.memo, a.code_type, a.code, a.post_time, " .
"s.session_id, s.reference, s.check_date " .
"FROM ar_activity AS a " .
"LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
"a.deleted IS NULL AND a.pid = ? AND a.encounter = ? AND " .
"(a.adj_amount != 0 || a.pay_amount = 0)",
array($patient_id, $encounter)
);
while ($arow = sqlFetchArray($ares)) {
if ($billtime && $arow['post_time'] != $billtime) {
continue;
}
$aReceipt['_adjusts'][] = $arow;
}
// Product sales
$inres = sqlStatement(
"SELECT s.sale_id, s.sale_date, s.fee, s.billed, " .
"s.bill_date, s.quantity, s.drug_id, s.selector, s.user, s.chargecat, d.name " .
"FROM drug_sales AS s LEFT JOIN drugs AS d ON d.drug_id = s.drug_id " .
"WHERE s.pid = ? AND s.encounter = ? " .
"ORDER BY s.sale_id",
array($patient_id, $encounter)
);
while ($inrow = sqlFetchArray($inres)) {
if ($billtime && $inrow['bill_date'] != $billtime) {
continue;
}
$tmpname = $inrow['name'];
if ($tmpname !== $inrow['selector']) {
$tmpname .= ' / ' . $inrow['selector'];
}
$taxrow = sqlQuery(
"SELECT SUM(fee) AS fee FROM billing WHERE " .
"pid = ? AND encounter = ? AND code_type = 'TAX' AND activity = 1 AND ndc_info = ?",
array($patient_id, $encounter, 'P:' . $inrow['sale_id'])
);
$tax = $taxrow['fee'];
receiptArrayDetailLine(
$aReceipt,
'PROD',
$inrow['drug_id'],
$tmpname,
$inrow['quantity'],
$inrow['fee'],
$inrow['bill_date'],
$tax,
$inrow['chargecat']
);
if (!empty($inrow['user'])) {
$aReceipt['cashier'] = $inrow['user'];
}
}
// Service items. Use short code description when available.
$inres = sqlStatement(
"SELECT b.*, c.code_text_short, u.username FROM billing AS b " .
"LEFT JOIN code_types AS ct ON ct.ct_key = b.code_type " .
"LEFT JOIN codes AS c ON c.code_type = ct.ct_id AND c.code = b.code AND c.modifier = b.modifier " .
"LEFT JOIN users AS u on u.id = b.user " .
"WHERE " .
"b.pid = ? AND b.encounter = ? AND " .
"b.code_type != 'COPAY' AND b.code_type != 'TAX' AND b.activity = 1 AND " .
"(ct.ct_id IS NULL OR ct.ct_fee = 1 OR ct.ct_diag = 0) " .
"ORDER BY b.id",
array($patient_id, $encounter)
);
while ($inrow = sqlFetchArray($inres)) {
if ($billtime && $inrow['bill_date'] != $billtime) {
continue;
}
$code_text = empty($inrow['code_text_short']) ? $inrow['code_text'] : $inrow['code_text_short'];
$taxrow = sqlQuery(
"SELECT SUM(fee) AS fee FROM billing WHERE " .
"pid = ? AND encounter = ? AND code_type = 'TAX' AND activity = 1 AND ndc_info = ?",
array($patient_id, $encounter, 'S:' . $inrow['id'])
);
$tax = $taxrow['fee'];
receiptArrayDetailLine(
$aReceipt,
$inrow['code_type'],
$inrow['code'],
$code_text,
$inrow['units'],
$inrow['fee'],
$inrow['bill_date'],
$tax,
$inrow['chargecat']
);
if (!empty($inrow['username'])) {
$aReceipt['cashier'] = $inrow['username'];
}
}
// Write any adjustments left in the aAdjusts array.
foreach ($aReceipt['_adjusts'] as $arow) {
if ($arow['adj_amount'] == 0) {
continue;
}
if ($billtime && $arow['post_time'] != $billtime) {
continue;
}
// $payer = empty($arow['payer_type']) ? 'Pt' : ('Ins' . $arow['payer_type']);
receiptArrayDetailLine(
$aReceipt,
'',
xl('Adjustment'),
getAdjustTitle($arow['memo']),
1,
0 - $arow['adj_amount']
);
}
// Tax items.
$inres = sqlStatement(
"SELECT * FROM billing WHERE " .
"pid = ? AND encounter = ? AND code_type = 'TAX' AND activity = 1 " .
"ORDER BY id",
array($patient_id, $encounter)
);
while ($inrow = sqlFetchArray($inres)) {
if ($billtime && $inrow['bill_date'] != $billtime) {
continue;
}
receiptArrayDetailLine(
$aReceipt,
$inrow['code_type'],
$inrow['code'],
$inrow['code_text'],
1,
$inrow['fee']
);
}
$payments = 0;
// Get co-pays.
$inres = sqlStatement(
"SELECT fee, code_text, bill_date FROM billing WHERE " .
"pid = ? AND encounter = ? AND " .
"code_type = 'COPAY' AND activity = 1 AND fee != 0 " .
"ORDER BY id",
array($patient_id, $encounter)
);
while ($inrow = sqlFetchArray($inres)) {
if ($billtime && $inrow['bill_date'] != $billtime) {
continue;
}
$payments -= sprintf('%01.2f', $inrow['fee']);
receiptArrayPaymentLine(
$aReceipt,
$svcdate,
0 - $inrow['fee'],
$inrow['code_text'],
'COPAY'
);
}
// Get other payments.
$inres = sqlStatement(
"SELECT " .
"a.code, a.modifier, a.memo, a.payer_type, a.adj_amount, a.pay_amount, a.post_time, " .
"s.payer_id, s.reference, s.check_date, s.deposit_date " .
"FROM ar_activity AS a " .
"LEFT JOIN ar_session AS s ON s.session_id = a.session_id WHERE " .
"a.deleted IS NULL AND a.pid = ? AND a.encounter = ? AND " .
"a.pay_amount != 0 " .
"ORDER BY a.post_time, s.check_date, a.sequence_no",
array($patient_id, $encounter)
);
$payer = empty($inrow['payer_type']) ? 'Pt' : ('Ins' . $inrow['payer_type']);
while ($inrow = sqlFetchArray($inres)) {
$meth = $inrow['memo'];
if ($billtime && $inrow['post_time'] != $billtime) {
if ($inrow['post_time'] > $billtime || $inrow['post_time'] <= $prevtime) {
continue;
}
// This is the case of payments after the previous checkout and before this checkout.
$meth = xl('Previous');
}
$payments += sprintf('%01.2f', $inrow['pay_amount']);
receiptArrayPaymentLine(
$aReceipt,
$svcdate,
$inrow['pay_amount'],
$payer . ' ' . $inrow['reference'],
$meth
);
}
// Previous balance for this checkout is local to the encounter, and is the total balance
// for the encounter up to this time minus total balance for this checkout.
// If there is no $billtime then this should always be zero.
$aReceipt['checkout_prvbal'] = $aReceipt['encounter_due'] - $aReceipt['total_totals'] + $aReceipt['total_payments'];
// Get cashier as the person who did the checkout. If there was no checkout then it will
// remain as the person who entered the Fee Sheet.
$query = "SELECT u.username FROM ar_activity AS a" .
" JOIN users AS u ON u.id = a.post_user" .
" WHERE" .
" a.deleted IS NULL AND a.pid = ? AND a.encounter = ?";
$binds = array($patient_id, $encounter);
if ($billtime) {
$query .= " AND a.post_time = ?";
$binds[] = $billtime;
}
$query .= " ORDER BY a.sequence_no DESC LIMIT 1";
$inrow = sqlQuery($query, $binds);
if (!empty($inrow['username'])) {
$aReceipt['cashier'] = $inrow['username'];
}
return $aReceipt;
}
// Get the array of checkout timestamps for the specified visit.
//
function craGetTimestamps($patient_id, $encounter_id)
{
$ret = array();
$res = sqlStatement(
"(SELECT bill_date FROM billing WHERE pid = ? AND encounter = ? AND activity = 1 AND billed = 1) " .
"UNION " .
"(SELECT bill_date FROM drug_sales WHERE pid = ? AND encounter = ? AND billed = 1) " .
"ORDER BY bill_date",
array($patient_id, $encounter_id, $patient_id, $encounter_id)
);
while ($row = sqlFetchArray($res)) {
$ret[] = $row['bill_date'];
}
return $ret;
}