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.
595 lines
21 KiB
595 lines
21 KiB
<?php |
|
|
|
/** |
|
* This report cross-references appointments with encounters. |
|
* For a given date, show a line for each appointment with the |
|
* matching encounter, and also for each encounter that has no |
|
* matching appointment. This helps to catch these errors: |
|
* |
|
* * Appointments with no encounter |
|
* * Encounters with no appointment |
|
* * Codes not justified |
|
* * Codes not authorized |
|
* * Procedure codes without a fee |
|
* * Fees assigned to diagnoses (instead of procedures) |
|
* * Encounters not billed |
|
* |
|
* For decent performance the following indexes are highly recommended: |
|
* openemr_postcalendar_events.pc_eventDate |
|
* forms.encounter |
|
* billing.pid_encounter |
|
* |
|
* @package OpenEMR |
|
* @link http://www.open-emr.org |
|
* @author Rod Roark <rod@sunsetsystems.com> |
|
* @author Brady Miller <brady.g.miller@gmail.com> |
|
* @copyright Copyright (c) 2005-2016 Rod Roark <rod@sunsetsystems.com> |
|
* @copyright Copyright (c) 2017-2018 Brady Miller <brady.g.miller@gmail.com> |
|
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 |
|
*/ |
|
|
|
require_once("../globals.php"); |
|
require_once("$srcdir/patient.inc"); |
|
require_once("../../custom/code_types.inc.php"); |
|
|
|
use OpenEMR\Billing\BillingUtilities; |
|
use OpenEMR\Common\Acl\AclMain; |
|
use OpenEMR\Common\Csrf\CsrfUtils; |
|
use OpenEMR\Common\Twig\TwigContainer; |
|
use OpenEMR\Core\Header; |
|
use OpenEMR\Services\FacilityService; |
|
|
|
if (!AclMain::aclCheckCore('acct', 'rep_a')) { |
|
echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Appointments and Encounters")]); |
|
exit; |
|
} |
|
|
|
if (!empty($_POST)) { |
|
if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) { |
|
CsrfUtils::csrfNotVerified(); |
|
} |
|
} |
|
|
|
$facilityService = new FacilityService(); |
|
|
|
$errmsg = ""; |
|
$alertmsg = ''; // not used yet but maybe later |
|
$grand_total_charges = 0; |
|
$grand_total_copays = 0; |
|
$grand_total_encounters = 0; |
|
|
|
function postError($msg) |
|
{ |
|
global $errmsg; |
|
if ($errmsg) { |
|
$errmsg .= '<br />'; |
|
} |
|
|
|
$errmsg .= text($msg); |
|
} |
|
|
|
function bucks($amount) |
|
{ |
|
if ($amount) { |
|
return oeFormatMoney($amount); |
|
} |
|
} |
|
|
|
function endDoctor(&$docrow) |
|
{ |
|
global $grand_total_charges, $grand_total_copays, $grand_total_encounters; |
|
if (!$docrow['docname']) { |
|
return; |
|
} |
|
|
|
echo " <tr class='report_totals'>\n"; |
|
echo " <td colspan='5'>\n"; |
|
echo " " . xlt('Totals for') . ' ' . text($docrow['docname']) . "\n"; |
|
echo " </td>\n"; |
|
echo " <td align='right'>\n"; |
|
echo " " . text($docrow['encounters']) . " \n"; |
|
echo " </td>\n"; |
|
echo " <td align='right'>\n"; |
|
echo " "; |
|
echo text(bucks($docrow['charges'])); |
|
echo " \n"; |
|
echo " </td>\n"; |
|
echo " <td align='right'>\n"; |
|
echo " "; |
|
echo text(bucks($docrow['copays'])); |
|
echo " \n"; |
|
echo " </td>\n"; |
|
echo " <td colspan='2'>\n"; |
|
echo " \n"; |
|
echo " </td>\n"; |
|
echo " </tr>\n"; |
|
|
|
$grand_total_charges += $docrow['charges']; |
|
$grand_total_copays += $docrow['copays']; |
|
$grand_total_encounters += $docrow['encounters']; |
|
|
|
$docrow['charges'] = 0; |
|
$docrow['copays'] = 0; |
|
$docrow['encounters'] = 0; |
|
} |
|
|
|
$form_facility = isset($_POST['form_facility']) ? $_POST['form_facility'] : ''; |
|
$form_from_date = (isset($_POST['form_from_date'])) ? DateToYYYYMMDD($_POST['form_from_date']) : date('Y-m-d'); |
|
$form_to_date = (isset($_POST['form_to_date'])) ? DateToYYYYMMDD($_POST['form_to_date']) : date('Y-m-d'); |
|
if (!empty($_POST['form_refresh'])) { |
|
// MySQL doesn't grok full outer joins so we do it the hard way. |
|
// |
|
$sqlBindArray = array(); |
|
$query = "( " . |
|
"SELECT " . |
|
"e.pc_eventDate, e.pc_startTime, " . |
|
"fe.encounter, fe.date AS encdate, " . |
|
"f.authorized, " . |
|
"p.fname, p.lname, p.pid, p.pubpid, " . |
|
"CONCAT( u.lname, ', ', u.fname ) AS docname " . |
|
"FROM openemr_postcalendar_events AS e " . |
|
"LEFT OUTER JOIN form_encounter AS fe " . |
|
"ON fe.date = e.pc_eventDate AND fe.pid = e.pc_pid " . |
|
"LEFT OUTER JOIN forms AS f ON f.pid = fe.pid AND f.encounter = fe.encounter AND f.formdir = 'newpatient' " . |
|
"LEFT OUTER JOIN patient_data AS p ON p.pid = e.pc_pid " . |
|
// "LEFT OUTER JOIN users AS u ON BINARY u.username = BINARY f.user WHERE "; |
|
"LEFT OUTER JOIN users AS u ON u.id = fe.provider_id WHERE "; |
|
if ($form_to_date) { |
|
$query .= "e.pc_eventDate >= ? AND e.pc_eventDate <= ? "; |
|
array_push($sqlBindArray, $form_from_date, $form_to_date); |
|
} else { |
|
$query .= "e.pc_eventDate = ? "; |
|
array_push($sqlBindArray, $form_from_date); |
|
} |
|
|
|
if ($form_facility !== '') { |
|
$query .= "AND e.pc_facility = ? "; |
|
array_push($sqlBindArray, $form_facility); |
|
} |
|
|
|
// $query .= "AND ( e.pc_catid = 5 OR e.pc_catid = 9 OR e.pc_catid = 10 ) " . |
|
$query .= "AND e.pc_pid != '' AND e.pc_apptstatus != ? " . |
|
") UNION ( " . |
|
"SELECT " . |
|
"e.pc_eventDate, e.pc_startTime, " . |
|
"fe.encounter, fe.date AS encdate, " . |
|
"f.authorized, " . |
|
"p.fname, p.lname, p.pid, p.pubpid, " . |
|
"CONCAT( u.lname, ', ', u.fname ) AS docname " . |
|
"FROM form_encounter AS fe " . |
|
"LEFT OUTER JOIN openemr_postcalendar_events AS e " . |
|
"ON fe.date = e.pc_eventDate AND fe.pid = e.pc_pid AND " . |
|
// "( e.pc_catid = 5 OR e.pc_catid = 9 OR e.pc_catid = 10 ) " . |
|
"e.pc_pid != '' AND e.pc_apptstatus != ? " . |
|
"LEFT OUTER JOIN forms AS f ON f.pid = fe.pid AND f.encounter = fe.encounter AND f.formdir = 'newpatient' " . |
|
"LEFT OUTER JOIN patient_data AS p ON p.pid = fe.pid " . |
|
// "LEFT OUTER JOIN users AS u ON BINARY u.username = BINARY f.user WHERE "; |
|
"LEFT OUTER JOIN users AS u ON u.id = fe.provider_id WHERE "; |
|
array_push($sqlBindArray, '?', '?'); |
|
if ($form_to_date) { |
|
// $query .= "LEFT(fe.date, 10) >= '$form_from_date' AND LEFT(fe.date, 10) <= '$form_to_date' "; |
|
$query .= "fe.date >= ? AND fe.date <= ? "; |
|
array_push($sqlBindArray, $form_from_date . ' 00:00:00', $form_to_date . ' 23:59:59'); |
|
} else { |
|
// $query .= "LEFT(fe.date, 10) = '$form_from_date' "; |
|
$query .= "fe.date >= ? AND fe.date <= ? "; |
|
array_push($sqlBindArray, $form_from_date . ' 00:00:00', $form_from_date . ' 23:59:59'); |
|
} |
|
|
|
if ($form_facility !== '') { |
|
$query .= "AND fe.facility_id = ? "; |
|
array_push($sqlBindArray, $form_facility); |
|
} |
|
|
|
$query .= ") ORDER BY docname, IFNULL(pc_eventDate, encdate), pc_startTime"; |
|
|
|
$res = sqlStatement($query, $sqlBindArray); |
|
} |
|
?> |
|
<html> |
|
<head> |
|
<title><?php echo xlt('Appointments and Encounters'); ?></title> |
|
|
|
<?php Header::setupHeader(['datetime-picker', 'report-helper']); ?> |
|
|
|
<style> |
|
/* specifically include & exclude from printing */ |
|
@media print { |
|
#report_parameters { |
|
visibility: hidden; |
|
display: none; |
|
} |
|
#report_parameters_daterange { |
|
visibility: visible; |
|
display: inline; |
|
} |
|
#report_results table { |
|
margin-top: 0px; |
|
} |
|
} |
|
|
|
/* specifically exclude some from the screen */ |
|
@media screen { |
|
#report_parameters_daterange { |
|
visibility: hidden; |
|
display: none; |
|
} |
|
} |
|
</style> |
|
|
|
<script> |
|
$(function () { |
|
oeFixedHeaderSetup(document.getElementById('mymaintable')); |
|
var win = top.printLogSetup ? top : opener.top; |
|
win.printLogSetup(document.getElementById('printbutton')); |
|
|
|
$('.datepicker').datetimepicker({ |
|
<?php $datetimepicker_timepicker = false; ?> |
|
<?php $datetimepicker_showseconds = false; ?> |
|
<?php $datetimepicker_formatInput = true; ?> |
|
<?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?> |
|
<?php // can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?> |
|
}); |
|
}); |
|
</script> |
|
</head> |
|
|
|
<body class="body_top"> |
|
|
|
<span class='title'><?php echo xlt('Report'); ?> - <?php echo xlt('Appointments and Encounters'); ?></span> |
|
|
|
<div id="report_parameters_daterange"> |
|
<?php echo text(oeFormatShortDate($form_from_date)) . " " . xlt('to{{Range}}') . " " . text(oeFormatShortDate($form_to_date)); ?> |
|
</div> |
|
|
|
<form method='post' id='theform' action='appt_encounter_report.php' onsubmit='return top.restoreSession()'> |
|
<input type="hidden" name="csrf_token_form" value="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" /> |
|
|
|
<div id="report_parameters"> |
|
|
|
<table> |
|
<tr> |
|
<td width='630px'> |
|
<div style='float:left'> |
|
|
|
<table class='text'> |
|
<tr> |
|
<td class='col-form-label'> |
|
<?php echo xlt('Facility'); ?>: |
|
</td> |
|
<td> |
|
<?php |
|
// Build a drop-down list of facilities. |
|
// |
|
$fres = $facilityService->getAllFacility(); |
|
echo " <select name='form_facility' class='form-control'>\n"; |
|
echo " <option value=''>-- " . xlt('All Facilities') . " --\n"; |
|
foreach ($fres as $frow) { |
|
$facid = $frow['id']; |
|
echo " <option value='" . attr($facid) . "'"; |
|
if ($facid == $form_facility) { |
|
echo " selected"; |
|
} |
|
echo ">" . text($frow['name']) . "\n"; |
|
} |
|
|
|
echo " <option value='0'"; |
|
if ($form_facility === '0') { |
|
echo " selected"; |
|
} |
|
|
|
echo ">-- " . xlt('Unspecified') . " --\n"; |
|
echo " </select>\n"; |
|
?> |
|
</td> |
|
<td class='col-form-label'> |
|
<?php echo xlt('DOS'); ?>: |
|
</td> |
|
<td> |
|
<input type='text' class='datepicker form-control' name='form_from_date' id="form_from_date" size='10' value='<?php echo attr(oeFormatShortDate($form_from_date)); ?>' > |
|
</td> |
|
<td class='col-form-label'> |
|
<?php echo xlt('To{{Range}}'); ?>: |
|
</td> |
|
<td> |
|
<input type='text' class='datepicker form-control' name='form_to_date' id="form_to_date" size='10' value='<?php echo attr(oeFormatShortDate($form_to_date)); ?>' > |
|
</td> |
|
</tr> |
|
<tr> |
|
<td> </td> |
|
<td> |
|
<div class="checkbox"> |
|
<label><input type='checkbox' name='form_details' |
|
value='1'<?php echo (!empty($_POST['form_details'])) ? " checked" : ""; ?>><?php echo xlt('Details') ?></label> |
|
</div> |
|
</td> |
|
</tr> |
|
</table> |
|
|
|
</div> |
|
|
|
</td> |
|
<td class='h-100' align='left' valign='middle'> |
|
<table class='w-100 h-100' style='border-left:1px solid;'> |
|
<tr> |
|
<td> |
|
<div class="text-center"> |
|
<div class="btn-group" role="group"> |
|
<a href='#' class='btn btn-secondary btn-save' onclick='$("#form_refresh").attr("value","true"); $("#theform").submit();'> |
|
<?php echo xlt('Submit'); ?> |
|
</a> |
|
<?php if (!empty($_POST['form_refresh'])) { ?> |
|
<a href='#' class='btn btn-secondary btn-print' id='printbutton'> |
|
<?php echo xlt('Print'); ?> |
|
</a> |
|
<?php } ?> |
|
</div> |
|
</div> |
|
</td> |
|
</tr> |
|
</table> |
|
</td> |
|
</tr> |
|
</table> |
|
|
|
</div> <!-- end apptenc_report_parameters --> |
|
|
|
<?php |
|
if (!empty($_POST['form_refresh'])) { |
|
?> |
|
<div id="report_results"> |
|
<table class='table' id='mymaintable'> |
|
|
|
<thead class='thead-light'> |
|
<th> <?php echo xlt('Practitioner'); ?> </th> |
|
<th> <?php echo xlt('Date/Appt'); ?> </th> |
|
<th> <?php echo xlt('Patient'); ?> </th> |
|
<th> <?php echo xlt('ID'); ?> </th> |
|
<th align='right'> <?php echo xlt('Chart'); ?> </th> |
|
<th align='right'> <?php echo xlt('Encounter'); ?> </th> |
|
<th align='right'> <?php echo xlt('Charges'); ?> </th> |
|
<th align='right'> <?php echo xlt('Copays'); ?> </th> |
|
<th> <?php echo xlt('Billed'); ?> </th> |
|
<th> <?php echo xlt('Error'); ?> </th> |
|
</thead> |
|
<tbody> |
|
<?php |
|
if ($res) { |
|
$docrow = array('docname' => '', 'charges' => 0, 'copays' => 0, 'encounters' => 0); |
|
|
|
while ($row = sqlFetchArray($res)) { |
|
$patient_id = $row['pid']; |
|
$encounter = $row['encounter']; |
|
$docname = $row['docname'] ? $row['docname'] : xl('Unknown'); |
|
|
|
if ($docname != $docrow['docname']) { |
|
endDoctor($docrow); |
|
} |
|
|
|
$errmsg = ""; |
|
$billed = "Y"; |
|
$charges = 0; |
|
$copays = 0; |
|
$gcac_related_visit = false; |
|
|
|
// Scan the billing items for status and fee total. |
|
// |
|
$query = "SELECT code_type, code, modifier, authorized, billed, fee, justify " . |
|
"FROM billing WHERE " . |
|
"pid = ? AND encounter = ? AND activity = 1"; |
|
$bres = sqlStatement($query, array($patient_id, $encounter)); |
|
// |
|
while ($brow = sqlFetchArray($bres)) { |
|
$code_type = $brow['code_type']; |
|
if ($code_types[$code_type]['fee'] && !$brow['billed']) { |
|
$billed = ""; |
|
} |
|
|
|
if (!$GLOBALS['simplified_demographics'] && !$brow['authorized']) { |
|
postError(xl('Needs Auth')); |
|
} |
|
|
|
if ($code_types[$code_type]['just']) { |
|
if (! $brow['justify']) { |
|
postError(xl('Needs Justify')); |
|
} |
|
} |
|
|
|
if ($code_types[$code_type]['fee']) { |
|
$charges += $brow['fee']; |
|
if ($brow['fee'] == 0 && !$GLOBALS['ippf_specific']) { |
|
postError(xl('Missing Fee')); |
|
} |
|
} else { |
|
if ($brow['fee'] != 0) { |
|
postError(xl('Fee is not allowed')); |
|
} |
|
} |
|
|
|
// Custom logic for IPPF to determine if a GCAC issue applies. |
|
if ($GLOBALS['ippf_specific']) { |
|
if (!empty($code_types[$code_type]['fee'])) { |
|
$sqlBindArray = array(); |
|
$query = "SELECT related_code FROM codes WHERE code_type = ? AND code = ? AND "; |
|
array_push($sqlBindArray, $code_types[$code_type]['id'], $brow['code']); |
|
if ($brow['modifier']) { |
|
$query .= "modifier = ?"; |
|
array_push($sqlBindArray, $brow['modifier']); |
|
} else { |
|
$query .= "(modifier IS NULL OR modifier = '')"; |
|
} |
|
|
|
$query .= " LIMIT 1"; |
|
$tmp = sqlQuery($query, $sqlBindArray); |
|
$relcodes = explode(';', $tmp['related_code']); |
|
foreach ($relcodes as $codestring) { |
|
if ($codestring === '') { |
|
continue; |
|
} |
|
|
|
list($codetype, $code) = explode(':', $codestring); |
|
if ($codetype !== 'IPPF') { |
|
continue; |
|
} |
|
|
|
if (preg_match('/^25222/', $code)) { |
|
$gcac_related_visit = true; |
|
} |
|
} |
|
} |
|
} // End IPPF stuff |
|
} // end while |
|
|
|
$copays -= BillingUtilities::getPatientCopay($patient_id, $encounter); |
|
|
|
// The following is removed, perhaps temporarily, because gcac reporting |
|
// no longer depends on gcac issues. -- Rod 2009-08-11 |
|
/****************************************************************** |
|
// More custom code for IPPF. Generates an error message if a |
|
// GCAC issue is required but is not linked to this visit. |
|
if (!$errmsg && $gcac_related_visit) { |
|
$grow = sqlQuery("SELECT l.id, l.title, l.begdate, ie.pid " . |
|
"FROM lists AS l " . |
|
"LEFT JOIN issue_encounter AS ie ON ie.pid = l.pid AND " . |
|
"ie.encounter = '$encounter' AND ie.list_id = l.id " . |
|
"WHERE l.pid = '$patient_id' AND " . |
|
"l.activity = 1 AND l.type = 'ippf_gcac' " . |
|
"ORDER BY ie.pid DESC, l.begdate DESC LIMIT 1"); |
|
// Note that reverse-ordering by ie.pid is a trick for sorting |
|
// issues linked to the encounter (non-null values) first. |
|
if (empty($grow['pid'])) { // if there is no linked GCAC issue |
|
if (empty($grow)) { // no GCAC issue exists |
|
$errmsg = "GCAC issue does not exist"; |
|
} |
|
else { // there is one but none is linked |
|
$errmsg = "GCAC issue is not linked"; |
|
} |
|
} |
|
} |
|
******************************************************************/ |
|
if ($gcac_related_visit) { |
|
$grow = sqlQuery("SELECT COUNT(*) AS count FROM forms " . |
|
"WHERE pid = ? AND encounter = ? AND " . |
|
"deleted = 0 AND formdir = 'LBFgcac'", array($patient_id, $encounter)); |
|
if (empty($grow['count'])) { // if there is no gcac form |
|
postError(xl('GCAC visit form is missing')); |
|
} |
|
} // end if |
|
/*****************************************************************/ |
|
|
|
if (!$billed) { |
|
postError($GLOBALS['simplified_demographics'] ? |
|
xl('Not checked out') : xl('Not billed')); |
|
} |
|
|
|
if (!$encounter) { |
|
postError(xl('No visit')); |
|
} |
|
|
|
if (! $charges) { |
|
$billed = ""; |
|
} |
|
|
|
$docrow['charges'] += $charges; |
|
$docrow['copays'] += $copays; |
|
if ($encounter) { |
|
++$docrow['encounters']; |
|
} |
|
|
|
if (!empty($_POST['form_details'])) { |
|
?> |
|
<tr> |
|
<td> |
|
<?php echo ($docname == $docrow['docname']) ? "" : text($docname); ?> |
|
</td> |
|
<td> |
|
<?php |
|
/***************************************************************** |
|
if ($form_to_date) { |
|
echo $row['pc_eventDate'] . '<br />'; |
|
echo substr($row['pc_startTime'], 0, 5); |
|
} |
|
*****************************************************************/ |
|
if (empty($row['pc_eventDate'])) { |
|
echo text(oeFormatShortDate(substr($row['encdate'], 0, 10))); |
|
} else { |
|
echo text(oeFormatShortDate($row['pc_eventDate'])) . ' ' . text(substr($row['pc_startTime'], 0, 5)); |
|
} |
|
?> |
|
</td> |
|
<td> |
|
<?php echo text($row['fname']) . " " . text($row['lname']); ?> |
|
</td> |
|
<td> |
|
<?php echo text($row['pubpid']); ?> |
|
</td> |
|
<td align='right'> |
|
<?php echo text($row['pid']); ?> |
|
</td> |
|
<td align='right'> |
|
<?php echo text($encounter); ?> |
|
</td> |
|
<td align='right'> |
|
<?php echo text(bucks($charges)); ?> |
|
</td> |
|
<td align='right'> |
|
<?php echo text(bucks($copays)); ?> |
|
</td> |
|
<td> |
|
<?php echo text($billed); ?> |
|
</td> |
|
<td style='color:#cc0000'> |
|
<?php echo $errmsg; ?> |
|
</td> |
|
</tr> |
|
<?php |
|
} // end of details line |
|
|
|
$docrow['docname'] = $docname; |
|
} // end of row |
|
|
|
endDoctor($docrow); |
|
|
|
echo " <tr class='report_totals'>\n"; |
|
echo " <td colspan='5'>\n"; |
|
echo " " . xlt('Grand Totals') . "\n"; |
|
echo " </td>\n"; |
|
echo " <td align='right'>\n"; |
|
echo " " . text($grand_total_encounters) . " \n"; |
|
echo " </td>\n"; |
|
echo " <td align='right'>\n"; |
|
echo " "; |
|
echo text(bucks($grand_total_charges)); |
|
echo " \n"; |
|
echo " </td>\n"; |
|
echo " <td align='right'>\n"; |
|
echo " "; |
|
echo text(bucks($grand_total_copays)); |
|
echo " \n"; |
|
echo " </td>\n"; |
|
echo " <td colspan='2'>\n"; |
|
echo " \n"; |
|
echo " </td>\n"; |
|
echo " </tr>\n"; |
|
} |
|
?> |
|
</tbody> |
|
</table> |
|
</div> <!-- end the apptenc_report_results --> |
|
<?php } else { ?> |
|
<div class='text'> |
|
<?php echo xlt('Please input search criteria above, and click Submit to view results.'); ?> |
|
</div> |
|
<?php } ?> |
|
|
|
<input type='hidden' name='form_refresh' id='form_refresh' value=''/> |
|
|
|
</form> |
|
<script> |
|
<?php if ($alertmsg) { |
|
echo " alert(" . js_escape($alertmsg) . ");\n"; |
|
} ?> |
|
</script> |
|
</body> |
|
|
|
</html>
|
|
|