From b9804f77a2e683bcc3565fbfe3673248681a72f2 Mon Sep 17 00:00:00 2001 From: Rod Roark Date: Thu, 28 Mar 2013 23:46:57 -0700 Subject: [PATCH] Added administrative utility to merge two patient charts. --- interface/main/left_nav.php | 1 + interface/patient_file/merge_patients.php | 308 ++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 interface/patient_file/merge_patients.php diff --git a/interface/main/left_nav.php b/interface/main/left_nav.php index 99b2a6022..93c258173 100644 --- a/interface/main/left_nav.php +++ b/interface/main/left_nav.php @@ -1278,6 +1278,7 @@ if (!empty($reg)) { + diff --git a/interface/patient_file/merge_patients.php b/interface/patient_file/merge_patients.php new file mode 100644 index 000000000..24ae06355 --- /dev/null +++ b/interface/patient_file/merge_patients.php @@ -0,0 +1,308 @@ + +* +* 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 2 +* 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 . +* +* @package OpenEMR +* @author Rod Roark +*/ + +set_time_limit(0); + +$sanitize_all_escapes = true; +$fake_register_globals = false; + +require_once("../globals.php"); +require_once("$srcdir/acl.inc"); +require_once("$srcdir/log.inc"); + +// Set this to true for production use. If false you will get a "dry run" with no updates. +$PRODUCTION = true; + +if (!acl_check('admin', 'super')) die(xlt('Not authorized')); +?> + + + + +<?php echo xlt('Merge Patients'); ?> + + + + + + + + + +

+ +$sql ($count)"; + if ($PRODUCTION) sqlStatement($sql); + } +} + +function updateRows($tblname, $colname, $source_pid, $target_pid) { + global $PRODUCTION; + $crow = sqlQuery("SELECT COUNT(*) AS count FROM `$tblname` WHERE `$colname` = $source_pid"); + $count = $crow['count']; + if ($count) { + $sql = "UPDATE `$tblname` SET `$colname` = '$target_pid' WHERE `$colname` = $source_pid"; + echo "
$sql ($count)"; + if ($PRODUCTION) sqlStatement($sql); + } +} + +if (!empty($_POST['form_submit'])) { + $target_pid = intval($_POST['form_target_pid']); + $source_pid = intval($_POST['form_source_pid']); + + $fatal = 0; + + if ($target_pid == $source_pid) die(xlt('Target and source pid may not be the same!')); + + $tprow = sqlQuery("SELECT * FROM patient_data WHERE pid = ?", array($target_pid)); + $sprow = sqlQuery("SELECT * FROM patient_data WHERE pid = ?", array($source_pid)); + + // Do some checking to make sure source and target are the same person. + if (empty($tprow['ss'])) die(xlt('Target patient not found or has no SSN')); + if (empty($sprow['ss'])) die(xlt('Source patient not found or has no SSN')); + if ($tprow['ss'] != $sprow['ss']) die(xlt('Target and source SSN do not match')); + if (empty($tprow['DOB']) || $tprow['DOB'] == '0000-00-00') die(xlt('Target patient has no DOB')); + if (empty($sprow['DOB']) || $sprow['DOB'] == '0000-00-00') die(xlt('Source patient has no DOB')); + if ($tprow['DOB'] != $sprow['DOB']) die(xlt('Target and source DOB do not match')); + + $tdocdir = "$OE_SITE_DIR/documents/$target_pid"; + $sdocdir = "$OE_SITE_DIR/documents/$source_pid"; + $sencdir = "$sdocdir/encounters"; + $tencdir = "$tdocdir/encounters"; + + // Check for any duplicate document names. + if (is_dir($sdocdir)) { + $dh = opendir($sdocdir); + if (!$dh) die(xlt('Cannot read directory') . " '$sdocdir'"); + while (false !== ($sfname = readdir($dh))) { + if ($sfname == '.' || $sfname == '..') continue; + if ($sfname == 'index.html') continue; + if ($sfname == 'encounters') continue; + if (file_exists("$tdocdir/$sfname")) { + ++$fatal; + echo "
" . xlt('Duplicate document name') . " '$sfname' " . xlt('in source and target'); + } + } + closedir($dh); + } + + if ($fatal) die("
" . xlt('Aborted due to document duplication')); + + // Move scanned encounter documents and delete their container. + if (is_dir($sencdir)) { + if ($PRODUCTION && !file_exists($tdocdir)) mkdir($tdocdir); + if ($PRODUCTION && !file_exists($tencdir)) mkdir($tencdir); + $dh = opendir($sencdir); + if (!$dh) die(xlt('Cannot read directory') . " '$sencdir'"); + while (false !== ($sfname = readdir($dh))) { + if ($sfname == '.' || $sfname == '..') continue; + if ($sfname == 'index.html') { + echo "
" . xlt('Deleting') . " $sencdir/$sfname"; + if ($PRODUCTION) { + if (!unlink("$sencdir/$sfname")) + die("
" . xlt('Delete failed!')); + } + continue; + } + echo "
" . xlt('Moving') . " $sencdir/$sfname " . xlt('to') . " $tencdir/$sfname"; + if ($PRODUCTION) { + if (!rename("$sencdir/$sfname", "$tencdir/$sfname")) + die("
" . xlt('Move failed!')); + } + } + closedir($dh); + echo "
" . xlt('Deleting') . " $sencdir"; + if ($PRODUCTION) { + if (!rmdir($sencdir)) + die("
" . xlt('Delete failed!')); + } + } + + // Move normal documents and delete their container. + if (is_dir($sdocdir)) { + if ($PRODUCTION && !file_exists($tdocdir)) mkdir($tdocdir); + $dh = opendir($sdocdir); + if (!$dh) die(xlt('Cannot read directory') . " '$sdocdir'"); + while (false !== ($sfname = readdir($dh))) { + if ($sfname == '.' || $sfname == '..') continue; + if ($sfname == 'encounters') continue; + if ($sfname == 'index.html') { + echo "
" . xlt('Deleting') . " $sdocdir/$sfname"; + if ($PRODUCTION) { + if (!unlink("$sdocdir/$sfname")) + die("
" . xlt('Delete failed!')); + } + continue; + } + echo "
" . xlt('Moving') . " $sdocdir/$sfname " . xlt('to') . " $tdocdir/$sfname"; + if ($PRODUCTION) { + if (!rename("$sdocdir/$sfname", "$tdocdir/$sfname")) + die("
" . xlt('Move failed!')); + } + } + closedir($dh); + echo "
" . xlt('Deleting') . " $sdocdir"; + if ($PRODUCTION) { + if (!rmdir($sdocdir)) + die("
" . xlt('Delete failed!')); + } + } + + $tres = sqlStatement("SHOW TABLES"); + while ($trow = sqlFetchArray($tres)) { + $tblname = array_shift($trow); + if ($tblname == 'patient_data' || $tblname == 'history_data' || $tblname == 'insurance_data') { + deleteRows($tblname, 'pid', $source_pid); + } + else if ($tblname == 'chart_tracker') { + updateRows($tblname, 'ct_pid', $source_pid, $target_pid); + } + else if ($tblname == 'documents') { + $crow = sqlQuery("SELECT COUNT(*) AS count FROM `$tblname` WHERE `foreign_id` = '$source_pid'"); + $count = $crow['count']; + if ($count) { + $sql = "UPDATE `$tblname` SET " . + "`url` = replace(`url`, '/documents/$source_pid/', '/documents/$target_pid/') " . + "WHERE `foreign_id` = '$source_pid'"; + echo "
$sql ($count)"; + if ($PRODUCTION) sqlStatement($sql); + } + updateRows($tblname, 'foreign_id', $source_pid, $target_pid); + } + else if ($tblname == 'openemr_postcalendar_events') { + updateRows($tblname, 'pc_pid', $source_pid, $target_pid); + } + else if ($tblname == 'log') { + // Don't mess with log data. + } + else { + $crow = sqlQuery("SHOW COLUMNS FROM `$tblname` WHERE " . + "`Field` LIKE 'pid' OR `Field` LIKE 'patient_id'"); + if (!empty($crow['Field'])) { + $colname = $crow['Field']; + updateRows($tblname, $colname, $source_pid, $target_pid); + } + } + } + + echo "
" . xlt('Merge complete.'); + + exit(0); +} +?> + +

+ +

+ +
+
+ + + + + + + + + + + +
+ + + )' + onclick='sel_patient(this, this.form.form_target_pid)' + title='Click to select patient' readonly /> + + + +
+ + + )' + onclick='sel_patient(this, this.form.form_source_pid)' + title='Click to select patient' readonly /> + + + +
+

' />

+
+
+ + + +

This utility is experimental. Back up your database and documents before using it!

+ + +

This will be a "dry run" with no physical data updates.

+ + +

This will merge two patient charts into one. It is useful when a patient has been +duplicated by mistake. If that happens often, fix your office procedures - do not run this +routinely!

+ +

The first ("target") chart is the one that is considered the most complete and accurate. +Demographics, history and insurance sections for this one will be retained.

+ +

The second ("source") chart will have its demographics, history and insurance sections +discarded. Its other data will be merged into the target chart.

+ +

The merge will not run unless SSN and DOB for the two charts are present and identical. +Also there must not be any documents with identical names. If any of these problems are +found then you should fix them and retry the merge.

+ + + -- 2.11.4.GIT