3 * This script merges two patient charts into a single patient chart.
4 * It is to correct the error of creating a duplicate patient.
6 * Copyright (C) 2013 Rod Roark <rod@sunsetsystems.com>
8 * LICENSE: This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>.
20 * @author Rod Roark <rod@sunsetsystems.com>
25 $sanitize_all_escapes = true;
26 $fake_register_globals = false;
28 require_once("../globals.php");
29 require_once("$srcdir/acl.inc");
30 require_once("$srcdir/log.inc");
32 // Set this to true for production use. If false you will get a "dry run" with no updates.
35 if (!acl_check('admin', 'super')) die(xlt('Not authorized'));
40 <link rel
="stylesheet" href
='<?php echo $css_header ?>' type
='text/css'>
41 <title
><?php
echo xlt('Merge Patients'); ?
></title
>
43 <script type
="text/javascript" src
="../../library/dialog.js"></script
>
45 <script language
="JavaScript">
47 var mypcc
= '<?php echo $GLOBALS['phone_country_code
']; ?>';
52 // This is for callback by the find-patient popup.
53 function setpatient(pid
, lname
, fname
, dob
) {
54 el_pt_name
.value
= lname +
', ' + fname +
' (' + pid +
')';
58 // This invokes the find-patient popup.
59 function sel_patient(ename
, epid
) {
62 dlgopen('../main/calendar/find_patient_popup.php', '_blank', 500, 400);
69 <body
class="body_top">
71 <center
><h2
><?php
echo xlt('Merge Patients') ?
></h2
></center
>
75 function deleteRows($tblname, $colname, $source_pid) {
77 $crow = sqlQuery("SELECT COUNT(*) AS count FROM `$tblname` WHERE `$colname` = $source_pid");
78 $count = $crow['count'];
80 $sql = "DELETE FROM `$tblname` WHERE `$colname` = $source_pid";
81 echo "<br />$sql ($count)";
82 if ($PRODUCTION) sqlStatement($sql);
86 function updateRows($tblname, $colname, $source_pid, $target_pid) {
88 $crow = sqlQuery("SELECT COUNT(*) AS count FROM `$tblname` WHERE `$colname` = $source_pid");
89 $count = $crow['count'];
91 $sql = "UPDATE `$tblname` SET `$colname` = '$target_pid' WHERE `$colname` = $source_pid";
92 echo "<br />$sql ($count)";
93 if ($PRODUCTION) sqlStatement($sql);
97 if (!empty($_POST['form_submit'])) {
98 $target_pid = intval($_POST['form_target_pid']);
99 $source_pid = intval($_POST['form_source_pid']);
103 if ($target_pid == $source_pid) die(xlt('Target and source pid may not be the same!'));
105 $tprow = sqlQuery("SELECT * FROM patient_data WHERE pid = ?", array($target_pid));
106 $sprow = sqlQuery("SELECT * FROM patient_data WHERE pid = ?", array($source_pid));
108 // Do some checking to make sure source and target are the same person.
109 if (empty($tprow['ss'])) die(xlt('Target patient not found or has no SSN'));
110 if (empty($sprow['ss'])) die(xlt('Source patient not found or has no SSN'));
111 if ($tprow['ss'] != $sprow['ss']) die(xlt('Target and source SSN do not match'));
112 if (empty($tprow['DOB']) ||
$tprow['DOB'] == '0000-00-00') die(xlt('Target patient has no DOB'));
113 if (empty($sprow['DOB']) ||
$sprow['DOB'] == '0000-00-00') die(xlt('Source patient has no DOB'));
114 if ($tprow['DOB'] != $sprow['DOB']) die(xlt('Target and source DOB do not match'));
116 $tdocdir = "$OE_SITE_DIR/documents/$target_pid";
117 $sdocdir = "$OE_SITE_DIR/documents/$source_pid";
118 $sencdir = "$sdocdir/encounters";
119 $tencdir = "$tdocdir/encounters";
121 // Check for any duplicate document names.
122 if (is_dir($sdocdir)) {
123 $dh = opendir($sdocdir);
124 if (!$dh) die(xlt('Cannot read directory') . " '$sdocdir'");
125 while (false !== ($sfname = readdir($dh))) {
126 if ($sfname == '.' ||
$sfname == '..') continue;
127 if ($sfname == 'index.html') continue;
128 if ($sfname == 'encounters') continue;
129 if (file_exists("$tdocdir/$sfname")) {
131 echo "<br />" . xlt('Duplicate document name') . " '$sfname' " . xlt('in source and target');
137 if ($fatal) die("<br />" . xlt('Aborted due to document duplication'));
139 // Move scanned encounter documents and delete their container.
140 if (is_dir($sencdir)) {
141 if ($PRODUCTION && !file_exists($tdocdir)) mkdir($tdocdir);
142 if ($PRODUCTION && !file_exists($tencdir)) mkdir($tencdir);
143 $dh = opendir($sencdir);
144 if (!$dh) die(xlt('Cannot read directory') . " '$sencdir'");
145 while (false !== ($sfname = readdir($dh))) {
146 if ($sfname == '.' ||
$sfname == '..') continue;
147 if ($sfname == 'index.html') {
148 echo "<br />" . xlt('Deleting') . " $sencdir/$sfname";
150 if (!unlink("$sencdir/$sfname"))
151 die("<br />" . xlt('Delete failed!'));
155 echo "<br />" . xlt('Moving') . " $sencdir/$sfname " . xlt('to') . " $tencdir/$sfname";
157 if (!rename("$sencdir/$sfname", "$tencdir/$sfname"))
158 die("<br />" . xlt('Move failed!'));
162 echo "<br />" . xlt('Deleting') . " $sencdir";
164 if (!rmdir($sencdir))
165 die("<br />" . xlt('Delete failed!'));
169 // Move normal documents and delete their container.
170 if (is_dir($sdocdir)) {
171 if ($PRODUCTION && !file_exists($tdocdir)) mkdir($tdocdir);
172 $dh = opendir($sdocdir);
173 if (!$dh) die(xlt('Cannot read directory') . " '$sdocdir'");
174 while (false !== ($sfname = readdir($dh))) {
175 if ($sfname == '.' ||
$sfname == '..') continue;
176 if ($sfname == 'encounters') continue;
177 if ($sfname == 'index.html') {
178 echo "<br />" . xlt('Deleting') . " $sdocdir/$sfname";
180 if (!unlink("$sdocdir/$sfname"))
181 die("<br />" . xlt('Delete failed!'));
185 echo "<br />" . xlt('Moving') . " $sdocdir/$sfname " . xlt('to') . " $tdocdir/$sfname";
187 if (!rename("$sdocdir/$sfname", "$tdocdir/$sfname"))
188 die("<br />" . xlt('Move failed!'));
192 echo "<br />" . xlt('Deleting') . " $sdocdir";
194 if (!rmdir($sdocdir))
195 die("<br />" . xlt('Delete failed!'));
199 $tres = sqlStatement("SHOW TABLES");
200 while ($trow = sqlFetchArray($tres)) {
201 $tblname = array_shift($trow);
202 if ($tblname == 'patient_data' ||
$tblname == 'history_data' ||
$tblname == 'insurance_data') {
203 deleteRows($tblname, 'pid', $source_pid);
205 else if ($tblname == 'chart_tracker') {
206 updateRows($tblname, 'ct_pid', $source_pid, $target_pid);
208 else if ($tblname == 'documents') {
209 $crow = sqlQuery("SELECT COUNT(*) AS count FROM `$tblname` WHERE `foreign_id` = '$source_pid'");
210 $count = $crow['count'];
212 $sql = "UPDATE `$tblname` SET " .
213 "`url` = replace(`url`, '/documents/$source_pid/', '/documents/$target_pid/') " .
214 "WHERE `foreign_id` = '$source_pid'";
215 echo "<br />$sql ($count)";
216 if ($PRODUCTION) sqlStatement($sql);
218 updateRows($tblname, 'foreign_id', $source_pid, $target_pid);
220 else if ($tblname == 'openemr_postcalendar_events') {
221 updateRows($tblname, 'pc_pid', $source_pid, $target_pid);
223 else if ($tblname == 'log') {
224 // Don't mess with log data.
227 $crow = sqlQuery("SHOW COLUMNS FROM `$tblname` WHERE " .
228 "`Field` LIKE 'pid' OR `Field` LIKE 'patient_id'");
229 if (!empty($crow['Field'])) {
230 $colname = $crow['Field'];
231 updateRows($tblname, $colname, $source_pid, $target_pid);
236 echo "<br />" . xlt('Merge complete.');
246 <form method
='post' action
='merge_patients.php'>
248 <table style
='width:90%'>
251 <?php
echo xlt('Target Patient') ?
>
254 <input type
='text' size
='30' name
='form_target_patient'
255 value
=' (<?php echo xla('Click to select
'); ?>)'
256 onclick
='sel_patient(this, this.form.form_target_pid)'
257 title
='Click to select patient' readonly
/>
258 <input type
='hidden' name
='form_target_pid' value
='0' />
261 <?php
echo xlt('This is the main chart that is to receive the merged data.'); ?
>
266 <?php
echo xlt('Source Patient') ?
>
269 <input type
='text' size
='30' name
='form_source_patient'
270 value
=' (<?php echo xla('Click to select
'); ?>)'
271 onclick
='sel_patient(this, this.form.form_source_pid)'
272 title
='Click to select patient' readonly
/>
273 <input type
='hidden' name
='form_source_pid' value
='0' />
276 <?php
echo xlt('This is the chart that is to be merged into the main chart and then deleted.'); ?
>
280 <p
><input type
='submit' name
='form_submit' value
='<?php echo xla('Merge
'); ?>' /></p
>
284 <!-- I don
't think it's good to run big globs of text through the current translation
285 system
. Let
's find another way. -->
287 <p>This utility is experimental. Back up your database and documents before using it!</p>
289 <?php if (!$PRODUCTION) { ?>
290 <p>This will be a "dry run" with no physical data updates.</p>
293 <p>This will merge two patient charts into one. It is useful when a patient has been
294 duplicated by mistake. If that happens often, fix your office procedures - do not run this
297 <p>The first ("target") chart is the one that is considered the most complete and accurate.
298 Demographics, history and insurance sections for this one will be retained.</p>
300 <p>The second ("source") chart will have its demographics, history and insurance sections
301 discarded. Its other data will be merged into the target chart.</p>
303 <p>The merge will not run unless SSN and DOB for the two charts are present and identical.
304 Also there must not be any documents with identical names. If any of these problems are
305 found then you should fix them and retry the merge.</p>