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.
 
 
 
 
 
 

506 lines
23 KiB

<?php
/**
* delete tool, for logging and removing patient data.
*
* Called from many different pages.
*
* @package OpenEMR
* @link http://www.open-emr.org
* @author Rod Roark <rod@sunsetsystems.com>
* @author Roberto Vasquez <robertogagliotta@gmail.com>
* @author Brady Miller <brady.g.miller@gmail.com>
* @copyright Copyright (c) 2005-2020 Rod Roark <rod@sunsetsystems.com>
* @copyright Copyright (c) 2015 Roberto Vasquez <robertogagliotta@gmail.com>
* @copyright Copyright (c) 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');
use OpenEMR\Billing\BillingUtilities;
use OpenEMR\Common\Acl\AclMain;
use OpenEMR\Common\Csrf\CsrfUtils;
use OpenEMR\Common\Logging\EventAuditLogger;
use OpenEMR\Core\Header;
if (!empty($_GET)) {
if (!CsrfUtils::verifyCsrfToken($_GET["csrf_token_form"])) {
CsrfUtils::csrfNotVerified();
}
}
$patient = $_REQUEST['patient'] ?? '';
$encounterid = $_REQUEST['encounterid'] ?? '';
$formid = $_REQUEST['formid'] ?? '';
$issue = $_REQUEST['issue'] ?? '';
$document = $_REQUEST['document'] ?? '';
$payment = $_REQUEST['payment'] ?? '';
$billing = $_REQUEST['billing'] ?? '';
$transaction = $_REQUEST['transaction'] ?? '';
$info_msg = "";
// Delete rows, with logging, for the specified table using the
// specified WHERE clause.
//
function row_delete($table, $where)
{
$tres = sqlStatement("SELECT * FROM " . escape_table_name($table) . " WHERE $where");
$count = 0;
while ($trow = sqlFetchArray($tres)) {
$logstring = "";
foreach ($trow as $key => $value) {
if (! $value || $value == '0000-00-00 00:00:00') {
continue;
}
if ($logstring) {
$logstring .= " ";
}
$logstring .= $key . "= '" . $value . "' ";
}
EventAuditLogger::instance()->newEvent("delete", $_SESSION['authUser'], $_SESSION['authProvider'], 1, "$table: $logstring");
++$count;
}
if ($count) {
$query = "DELETE FROM " . escape_table_name($table) . " WHERE $where";
if (!$GLOBALS['sql_string_no_show_screen']) {
echo text($query) . "<br />\n";
}
sqlStatement($query);
}
}
// Deactivate rows, with logging, for the specified table using the
// specified SET and WHERE clauses.
//
function row_modify($table, $set, $where)
{
if (sqlQuery("SELECT * FROM " . escape_table_name($table) . " WHERE $where")) {
EventAuditLogger::instance()->newEvent("deactivate", $_SESSION['authUser'], $_SESSION['authProvider'], 1, "$table: $where");
$query = "UPDATE " . escape_table_name($table) . " SET $set WHERE $where";
if (!$GLOBALS['sql_string_no_show_screen']) {
echo text($query) . "<br />\n";
}
sqlStatement($query);
}
}
// We use this to put dashes, colons, etc. back into a timestamp.
//
function decorateString($fmt, $str)
{
$res = '';
while ($fmt) {
$fc = substr($fmt, 0, 1);
$fmt = substr($fmt, 1);
if ($fc == '.') {
$res .= substr($str, 0, 1);
$str = substr($str, 1);
} else {
$res .= $fc;
}
}
return $res;
}
// Delete and undo product sales for a given patient or visit.
// This is special because it has to replace the inventory.
//
function delete_drug_sales($patient_id, $encounter_id = 0)
{
$where = $encounter_id ? "ds.encounter = '" . add_escape_custom($encounter_id) . "'" :
"ds.pid = '" . add_escape_custom($patient_id) . "' AND ds.encounter != 0";
sqlStatement("UPDATE drug_sales AS ds, drug_inventory AS di " .
"SET di.on_hand = di.on_hand + ds.quantity " .
"WHERE $where AND di.inventory_id = ds.inventory_id");
if ($encounter_id) {
row_delete("drug_sales", "encounter = '" . add_escape_custom($encounter_id) . "'");
} else {
row_delete("drug_sales", "pid = '" . add_escape_custom($patient_id) . "'");
}
}
// Delete a form's data that is specific to that form.
//
function form_delete($formdir, $formid, $patient_id, $encounter_id)
{
$formdir = ($formdir == 'newpatient') ? 'encounter' : $formdir;
$formdir = ($formdir == 'newGroupEncounter') ? 'groups_encounter' : $formdir;
if (substr($formdir, 0, 3) == 'LBF') {
row_delete("lbf_data", "form_id = '" . add_escape_custom($formid) . "'");
// Delete the visit's "source=visit" attributes that are not used by any other form.
$where = "pid = '" . add_escape_custom($patient_id) . "' AND encounter = '" .
add_escape_custom($encounter_id) . "' AND field_id NOT IN (" .
"SELECT lo.field_id FROM forms AS f, layout_options AS lo WHERE " .
"f.pid = '" . add_escape_custom($patient_id) . "' AND f.encounter = '" .
add_escape_custom($encounter_id) . "' AND f.formdir LIKE 'LBF%' AND " .
"f.deleted = 0 AND f.form_id != '" . add_escape_custom($formid) . "' AND " .
"lo.form_id = f.formdir AND lo.source = 'E' AND lo.uor > 0)";
// echo "<!-- $where -->\n"; // debugging
row_delete("shared_attributes", $where);
} elseif ($formdir == 'procedure_order') {
$tres = sqlStatement("SELECT procedure_report_id FROM procedure_report " .
"WHERE procedure_order_id = ?", array($formid));
while ($trow = sqlFetchArray($tres)) {
$reportid = (int)$trow['procedure_report_id'];
row_delete("procedure_result", "procedure_report_id = '" . add_escape_custom($reportid) . "'");
}
row_delete("procedure_report", "procedure_order_id = '" . add_escape_custom($formid) . "'");
row_delete("procedure_order_code", "procedure_order_id = '" . add_escape_custom($formid) . "'");
row_delete("procedure_order", "procedure_order_id = '" . add_escape_custom($formid) . "'");
} elseif ($formdir == 'physical_exam') {
row_delete("form_$formdir", "forms_id = '" . add_escape_custom($formid) . "'");
} elseif ($formdir == 'eye_mag') {
$tables = array('form_eye_base','form_eye_hpi','form_eye_ros','form_eye_vitals',
'form_eye_acuity','form_eye_refraction','form_eye_biometrics',
'form_eye_external', 'form_eye_antseg','form_eye_postseg',
'form_eye_neuro','form_eye_locking','form_eye_mag_orders');
foreach ($tables as $table_name) {
row_delete($table_name, "id = '" . add_escape_custom($formid) . "'");
}
row_delete("form_eye_mag_impplan", "form_id = '" . add_escape_custom($formid) . "'");
row_delete("form_eye_mag_wearing", "FORM_ID = '" . add_escape_custom($formid) . "'");
} else {
row_delete("form_$formdir", "id = '" . add_escape_custom($formid) . "'");
}
}
// Delete a specified document including its associated relations.
// Note the specific file is not deleted (instead flagged as deleted), since required to keep file for
// ONC 2015 certification purposes.
//
function delete_document($document)
{
sqlStatement("UPDATE `documents` SET `deleted` = 1 WHERE id = ?", [$document]);
row_delete("categories_to_documents", "document_id = '" . add_escape_custom($document) . "'");
row_delete("gprelations", "type1 = 1 AND id1 = '" . add_escape_custom($document) . "'");
}
?>
<html>
<head>
<?php Header::setupHeader('opener'); ?>
<title><?php echo xlt('Delete Patient, Encounter, Form, Issue, Document, Payment, Billing or Transaction'); ?></title>
<script>
function submit_form() {
top.restoreSession();
document.deletefrm.submit();
}
// Javascript function for closing the popup
function popup_close() {
dlgclose();
}
</script>
</head>
<body>
<div class="container mt-3">
<?php
// If the delete is confirmed...
//
if (!empty($_POST['form_submit'])) {
if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) {
CsrfUtils::csrfNotVerified();
}
if ($patient) {
if (!AclMain::aclCheckCore('admin', 'super') || !$GLOBALS['allow_pat_delete']) {
die(xlt("Not authorized!"));
}
row_modify("billing", "activity = 0", "pid = '" . add_escape_custom($patient) . "'");
row_modify("pnotes", "deleted = 1", "pid = '" . add_escape_custom($patient) . "'");
row_delete("prescriptions", "patient_id = '" . add_escape_custom($patient) . "'");
row_delete("claims", "patient_id = '" . add_escape_custom($patient) . "'");
delete_drug_sales($patient);
row_delete("payments", "pid = '" . add_escape_custom($patient) . "'");
row_modify("ar_activity", "deleted = NOW()", "pid = '" . add_escape_custom($patient) . "' AND deleted IS NULL");
row_delete("openemr_postcalendar_events", "pc_pid = '" . add_escape_custom($patient) . "'");
row_delete("immunizations", "patient_id = '" . add_escape_custom($patient) . "'");
row_delete("issue_encounter", "pid = '" . add_escape_custom($patient) . "'");
row_delete("lists", "pid = '" . add_escape_custom($patient) . "'");
row_delete("transactions", "pid = '" . add_escape_custom($patient) . "'");
row_delete("employer_data", "pid = '" . add_escape_custom($patient) . "'");
row_delete("history_data", "pid = '" . add_escape_custom($patient) . "'");
row_delete("insurance_data", "pid = '" . add_escape_custom($patient) . "'");
row_delete("patient_history", "pid = '" . add_escape_custom($patient) . "'");
$res = sqlStatement("SELECT * FROM forms WHERE pid = ?", array($patient));
while ($row = sqlFetchArray($res)) {
form_delete($row['formdir'], $row['form_id'], $row['pid'], $row['encounter']);
}
row_delete("forms", "pid = '" . add_escape_custom($patient) . "'");
// Delete all documents for the patient.
$res = sqlStatement("SELECT id FROM documents WHERE foreign_id = ? AND deleted = 0", array($patient));
while ($row = sqlFetchArray($res)) {
delete_document($row['id']);
}
row_delete("patient_data", "pid = '" . add_escape_custom($patient) . "'");
} elseif ($encounterid) {
if (!AclMain::aclCheckCore('admin', 'super')) {
die("Not authorized!");
}
row_modify("billing", "activity = 0", "encounter = '" . add_escape_custom($encounterid) . "'");
delete_drug_sales(0, $encounterid);
row_modify("ar_activity", "deleted = NOW()", "encounter = '" . add_escape_custom($encounterid) . "' AND deleted IS NULL");
row_delete("claims", "encounter_id = '" . add_escape_custom($encounterid) . "'");
row_delete("issue_encounter", "encounter = '" . add_escape_custom($encounterid) . "'");
$res = sqlStatement("SELECT * FROM forms WHERE encounter = ?", array($encounterid));
while ($row = sqlFetchArray($res)) {
form_delete($row['formdir'], $row['form_id'], $row['pid'], $row['encounter']);
}
row_delete("forms", "encounter = '" . add_escape_custom($encounterid) . "'");
} elseif ($formid) {
if (!AclMain::aclCheckCore('admin', 'super')) {
die("Not authorized!");
}
$row = sqlQuery("SELECT * FROM forms WHERE id = ?", array($formid));
$formdir = $row['formdir'];
if (! $formdir) {
die("There is no form with id '" . text($formid) . "'");
}
form_delete($formdir, $row['form_id'], $row['pid'], $row['encounter']);
row_delete("forms", "id = '" . add_escape_custom($formid) . "'");
} elseif ($issue) {
if (!AclMain::aclCheckCore('admin', 'super')) {
die("Not authorized!");
}
$ids = explode(",", $issue);
foreach ($ids as $id) {
row_delete("issue_encounter", "list_id = '" . add_escape_custom($id) . "'");
row_delete("lists_medication", "list_id = '" . add_escape_custom($id) . "'");
row_delete("lists", "id = '" . add_escape_custom($id) . "'");
}
} elseif ($document) {
if (!AclMain::aclCheckCore('patients', 'docs_rm')) {
die("Not authorized!");
}
delete_document($document);
} elseif ($payment) {
if (!AclMain::aclCheckCore('admin', 'super')) {
// allow biller to delete misapplied payments
if (!AclMain::aclCheckCore('acct', 'bill')) {
die("Not authorized!");
}
}
list($patient_id, $timestamp, $ref_id) = explode(".", $payment);
// if (empty($ref_id)) $ref_id = -1;
$timestamp = decorateString('....-..-.. ..:..:..', $timestamp);
$payres = sqlStatement("SELECT * FROM payments WHERE " .
"pid = ? AND dtime = ?", array($patient_id, $timestamp));
while ($payrow = sqlFetchArray($payres)) {
if ($payrow['encounter']) {
$ref_id = -1;
// The session ID passed in is useless. Look for the most recent
// patient payment session with pay total matching pay amount and with
// no adjustments. The resulting session ID may be 0 (no session) which
// is why we start with -1.
$tpmt = $payrow['amount1'] + $payrow['amount2'];
$seres = sqlStatement("SELECT " .
"SUM(pay_amount) AS pay_amount, session_id " .
"FROM ar_activity WHERE " .
"pid = ? AND " .
"encounter = ? AND " .
"deleted IS NULL AND " .
"payer_type = 0 AND " .
"adj_amount = 0.00 " .
"GROUP BY session_id ORDER BY session_id DESC", array($patient_id, $payrow['encounter']));
while ($serow = sqlFetchArray($seres)) {
if (sprintf("%01.2f", $serow['adj_amount']) != 0.00) {
continue;
}
if (sprintf("%01.2f", $serow['pay_amount'] - $tpmt) == 0.00) {
$ref_id = $serow['session_id'];
break;
}
}
if ($ref_id == -1) {
die(xlt('Unable to match this payment in ar_activity') . ": " . text($tpmt));
}
// Delete the payment.
row_modify(
"ar_activity",
"deleted = NOW()",
"pid = '" . add_escape_custom($patient_id) . "' AND " .
"encounter = '" . add_escape_custom($payrow['encounter']) . "' AND " .
"deleted IS NULL AND " .
"payer_type = 0 AND " .
"pay_amount != 0.00 AND " .
"adj_amount = 0.00 AND " .
"session_id = '" . add_escape_custom($ref_id) . "'"
);
if ($ref_id) {
row_delete(
"ar_session",
"patient_id = '" . add_escape_custom($patient_id) . "' AND " .
"session_id = '" . add_escape_custom($ref_id) . "'"
);
}
} else {
// Encounter is 0! Seems this happens for pre-payments.
$tpmt = sprintf("%01.2f", $payrow['amount1'] + $payrow['amount2']);
// Patched out 09/06/17- If this is prepayment can't see need for ar_activity when prepayments not stored there? In this case passed in session id is valid.
// Was causing delete of wrong prepayment session in the case of delete from checkout undo and/or front receipt delete if payment happens to be same
// amount of a previous prepayment. Much tested but look here if problems in postings.
//
/* row_delete("ar_session",
"patient_id = ' " . add_escape_custom($patient_id) . " ' AND " .
"payer_id = 0 AND " .
"reference = '" . add_escape_custom($payrow['source']) . "' AND " .
"pay_total = '" . add_escape_custom($tpmt) . "' AND " .
"(SELECT COUNT(*) FROM ar_activity where ar_activity.session_id = ar_session.session_id) = 0 " .
"ORDER BY session_id DESC LIMIT 1"); */
row_delete("ar_session", "session_id = '" . add_escape_custom($ref_id) . "'");
}
row_delete("payments", "id = '" . add_escape_custom($payrow['id']) . "'");
}
} elseif ($billing) {
if (!AclMain::aclCheckCore('acct', 'disc')) {
die("Not authorized!");
}
list($patient_id, $encounter_id) = explode(".", $billing);
row_modify(
"ar_activity",
"deleted = NOW()",
"pid = '" . add_escape_custom($patient_id) . "' AND encounter = '" .
add_escape_custom($encounter_id) . "' AND deleted IS NULL"
);
// Looks like this deletes all ar_session rows that have no matching ar_activity rows.
sqlStatement(
"DELETE ar_session FROM ar_session LEFT JOIN " .
"ar_activity ON ar_session.session_id = ar_activity.session_id AND ar_activity.deleted IS NULL " .
"WHERE ar_activity.session_id IS NULL"
);
row_modify(
"billing",
"activity = 0",
"pid = '" . add_escape_custom($patient_id) . "' AND " .
"encounter = '" . add_escape_custom($encounter_id) . "' AND " .
"code_type = 'COPAY' AND " .
"activity = 1"
);
sqlStatement("UPDATE form_encounter SET last_level_billed = 0, " .
"last_level_closed = 0, stmt_count = 0, last_stmt_date = NULL " .
"WHERE pid = ? AND encounter = ?", array($patient_id, $encounter_id));
sqlStatement("UPDATE drug_sales SET billed = 0 WHERE " .
"pid = ? AND encounter = ?", array($patient_id, $encounter_id));
BillingUtilities::updateClaim(true, $patient_id, $encounter_id, -1, -1, 1, 0, ''); // clears for rebilling
} elseif ($transaction) {
if (!AclMain::aclCheckCore('admin', 'super')) {
die("Not authorized!");
}
row_delete("transactions", "id = '" . add_escape_custom($transaction) . "'");
} else {
die("Nothing was recognized to delete!");
}
if (! $info_msg) {
$info_msg = xl('Delete successful.');
}
// Close this window and tell our opener that it's done.
// Not sure yet if the callback can be used universally.
echo "<script>\n";
if (!$encounterid) {
if ($info_msg) {
echo "let message = " . js_escape($info_msg) . ";
(async (message, time) => {
await asyncAlertMsg(message, time, 'success', 'lg');
})(message, 5000)
.then(res => {});";
echo " opener.dlgSetCallBack('imdeleted', false);\n";
} else {
echo " dlgclose('imdeleted', false);\n";
}
} else {
if ($GLOBALS['sql_string_no_show_screen']) {
echo " dlgclose('imdeleted', " . js_escape($encounterid) . ");\n";
} else { // this allows dialog to stay open then close with button or X.
echo " opener.dlgSetCallBack('imdeleted', " . js_escape($encounterid) . ");\n";
}
}
echo "</script></body></html>\n";
exit();
}
?>
<form method='post' name="deletefrm" action='deleter.php?patient=<?php echo attr_url($patient) ?>&encounterid=<?php echo attr_url($encounterid) ?>&formid=<?php echo attr_url($formid) ?>&issue=<?php echo attr_url($issue) ?>&document=<?php echo attr_url($document) ?>&payment=<?php echo attr_url($payment) ?>&billing=<?php echo attr_url($billing) ?>&transaction=<?php echo attr_url($transaction); ?>&csrf_token_form=<?php echo attr_url(CsrfUtils::collectCsrfToken()); ?>'>
<input type="hidden" name="csrf_token_form"
value="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" />
<p>
<?php
$type = '';
$id = '';
if ($patient) {
$id = $patient;
$type = 'patient';
} elseif ($encounterid) {
$id = $encounterid;
$type = 'encounter';
} elseif ($formid) {
$id = $formid;
$type = 'form';
} elseif ($issue) {
$id = $issue;
$type = ('issue');
} elseif ($document) {
$id = $document;
$type = 'document';
} elseif ($payment) {
$id = $payment;
$type = 'payment';
} elseif ($billing) {
$id = $billing;
$type = 'invoice';
} elseif ($transaction) {
$id = $transaction;
$type = 'transaction';
}
$ids = explode(",", $id);
if (count($ids) > 1) {
$type .= 's';
}
$msg = xl("You have selected to delete") . ' ' . count($ids) . ' ' . xl($type) . ". " . xl("Are you sure you want to continue?");
echo text($msg);
?>
</p>
<div class="btn-group">
<button onclick="submit_form()" class="btn btn-sm btn-primary mr-2"><?php echo xlt('Yes'); ?></button>
<button type="button" class="btn btn-sm btn-secondary" onclick="popup_close();"><?php echo xlt('No');?></button>
</div>
<input type='hidden' name='form_submit' value='delete'/>
</form>
</div>
</body>
</html>