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");
31 require_once("$srcdir/classes/Document.class.php");
33 // Set this to true for production use. If false you will get a "dry run" with no updates.
36 if (!acl_check('admin', 'super')) die(xlt('Not authorized'));
41 <link rel
="stylesheet" href
='<?php echo $css_header ?>' type
='text/css'>
42 <title
><?php
echo xlt('Merge Patients'); ?
></title
>
44 <script type
="text/javascript" src
="../../library/dialog.js"></script
>
46 <script language
="JavaScript">
48 var mypcc
= '<?php echo $GLOBALS['phone_country_code
']; ?>';
53 // This is for callback by the find-patient popup.
54 function setpatient(pid
, lname
, fname
, dob
) {
55 el_pt_name
.value
= lname +
', ' + fname +
' (' + pid +
')';
59 // This invokes the find-patient popup.
60 function sel_patient(ename
, epid
) {
63 dlgopen('../main/calendar/find_patient_popup.php', '_blank', 500, 400);
70 <body
class="body_top">
72 <center
><h2
><?php
echo xlt('Merge Patients') ?
></h2
></center
>
76 function deleteRows($tblname, $colname, $source_pid) {
78 $crow = sqlQuery("SELECT COUNT(*) AS count FROM `$tblname` WHERE `$colname` = $source_pid");
79 $count = $crow['count'];
81 $sql = "DELETE FROM `$tblname` WHERE `$colname` = $source_pid";
82 echo "<br />$sql ($count)";
83 if ($PRODUCTION) sqlStatement($sql);
87 function updateRows($tblname, $colname, $source_pid, $target_pid) {
89 $crow = sqlQuery("SELECT COUNT(*) AS count FROM `$tblname` WHERE `$colname` = $source_pid");
90 $count = $crow['count'];
92 $sql = "UPDATE `$tblname` SET `$colname` = '$target_pid' WHERE `$colname` = $source_pid";
93 echo "<br />$sql ($count)";
94 if ($PRODUCTION) sqlStatement($sql);
98 if (!empty($_POST['form_submit'])) {
99 $target_pid = intval($_POST['form_target_pid']);
100 $source_pid = intval($_POST['form_source_pid']);
102 if ($target_pid == $source_pid) die(xlt('Target and source pid may not be the same!'));
104 $tprow = sqlQuery("SELECT * FROM patient_data WHERE pid = ?", array($target_pid));
105 $sprow = sqlQuery("SELECT * FROM patient_data WHERE pid = ?", array($source_pid));
107 // Do some checking to make sure source and target exist and are the same person.
108 if (empty($tprow['pid'])) die(xlt('Target patient not found'));
109 if (empty($sprow['pid'])) die(xlt('Source patient not found'));
110 if ($tprow['ss'] != $sprow['ss']) die(xlt('Target and source SSN do not match'));
111 if (empty($tprow['DOB']) ||
$tprow['DOB'] == '0000-00-00') die(xlt('Target patient has no DOB'));
112 if (empty($sprow['DOB']) ||
$sprow['DOB'] == '0000-00-00') die(xlt('Source patient has no DOB'));
113 if ($tprow['DOB'] != $sprow['DOB']) die(xlt('Target and source DOB do not match'));
115 $tdocdir = "$OE_SITE_DIR/documents/$target_pid";
116 $sdocdir = "$OE_SITE_DIR/documents/$source_pid";
117 $sencdir = "$sdocdir/encounters";
118 $tencdir = "$tdocdir/encounters";
120 // Change normal documents first as that could fail if CouchDB connection fails.
121 $dres = sqlStatement("SELECT * FROM `documents` WHERE `foreign_id` = '$source_pid'");
122 while ($drow = sqlFetchArray($dres)) {
123 $d = new Document($drow['id']);
124 echo "<br />" . xlt('Changing patient ID for document') . ' ' . text($d->get_url_file());
126 if (!$d->change_patient($target_pid)) {
127 die("<br />" . xlt('Change failed! CouchDB connect error?'));
132 // Move scanned encounter documents and delete their container.
133 if (is_dir($sencdir)) {
134 if ($PRODUCTION && !file_exists($tdocdir)) mkdir($tdocdir);
135 if ($PRODUCTION && !file_exists($tencdir)) mkdir($tencdir);
136 $dh = opendir($sencdir);
137 if (!$dh) die(xlt('Cannot read directory') . " '$sencdir'");
138 while (false !== ($sfname = readdir($dh))) {
139 if ($sfname == '.' ||
$sfname == '..') continue;
140 if ($sfname == 'index.html') {
141 echo "<br />" . xlt('Deleting') . " $sencdir/$sfname";
143 if (!unlink("$sencdir/$sfname"))
144 die("<br />" . xlt('Delete failed!'));
148 echo "<br />" . xlt('Moving') . " $sencdir/$sfname " . xlt('to') . " $tencdir/$sfname";
150 if (!rename("$sencdir/$sfname", "$tencdir/$sfname"))
151 die("<br />" . xlt('Move failed!'));
155 echo "<br />" . xlt('Deleting') . " $sencdir";
157 if (!rmdir($sencdir))
158 echo "<br />" . xlt('Directory delete failed; continuing.');
162 $tres = sqlStatement("SHOW TABLES");
163 while ($trow = sqlFetchArray($tres)) {
164 $tblname = array_shift($trow);
165 if ($tblname == 'patient_data' ||
$tblname == 'history_data' ||
$tblname == 'insurance_data') {
166 deleteRows($tblname, 'pid', $source_pid);
168 else if ($tblname == 'chart_tracker') {
169 updateRows($tblname, 'ct_pid', $source_pid, $target_pid);
171 else if ($tblname == 'documents') {
172 // Documents already handled.
174 else if ($tblname == 'openemr_postcalendar_events') {
175 updateRows($tblname, 'pc_pid', $source_pid, $target_pid);
177 else if ($tblname == 'log') {
178 // Don't mess with log data.
181 $crow = sqlQuery("SHOW COLUMNS FROM `$tblname` WHERE " .
182 "`Field` LIKE 'pid' OR `Field` LIKE 'patient_id'");
183 if (!empty($crow['Field'])) {
184 $colname = $crow['Field'];
185 updateRows($tblname, $colname, $source_pid, $target_pid);
190 echo "<br />" . xlt('Merge complete.');
200 <form method
='post' action
='merge_patients.php'>
202 <table style
='width:90%'>
205 <?php
echo xlt('Target Patient') ?
>
208 <input type
='text' size
='30' name
='form_target_patient'
209 value
=' (<?php echo xla('Click to select
'); ?>)'
210 onclick
='sel_patient(this, this.form.form_target_pid)'
211 title
='<?php echo xla('Click to select patient
'); ?>' readonly
/>
212 <input type
='hidden' name
='form_target_pid' value
='0' />
215 <?php
echo xlt('This is the main chart that is to receive the merged data.'); ?
>
220 <?php
echo xlt('Source Patient') ?
>
223 <input type
='text' size
='30' name
='form_source_patient'
224 value
=' (<?php echo xla('Click to select
'); ?>)'
225 onclick
='sel_patient(this, this.form.form_source_pid)'
226 title
='<?php echo xla('Click to select patient
'); ?>' readonly
/>
227 <input type
='hidden' name
='form_source_pid' value
='0' />
230 <?php
echo xlt('This is the chart that is to be merged into the main chart and then deleted.'); ?
>
234 <p
><input type
='submit' name
='form_submit' value
='<?php echo xla('Merge
'); ?>' /></p
>
238 <p
><?php
echo xlt('This utility is experimental. Back up your database and documents before using it!'); ?
></p
>
240 <?php
if (!$PRODUCTION) { ?
>
241 <p
><?php
echo xlt('This will be a "dry run" with no physical data updates.'); ?
></p
>
244 <p
><?php
echo xlt('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!'); ?
></p
>
246 <p
><?php
echo xlt('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.'); ?
></p
>
248 <p
><?php
echo xlt('The second ("source") chart will have its demographics, history and insurance sections discarded. Its other data will be merged into the target chart.'); ?
></p
>
250 <p
><?php
echo xlt('The merge will not run unless SSN and DOB for the two charts are identical. DOBs cannot be empty.'); ?
></p
>