3 * This script merges two patient charts into a single patient chart.
4 * It is to correct the error of creating a duplicate patient.
7 * @link http://www.open-emr.org
8 * @author Rod Roark <rod@sunsetsystems.com>
9 * @author Brady Miller <brady.g.miller@gmail.com>
10 * @copyright Copyright (c) 2013 Rod Roark <rod@sunsetsystems.com>
11 * @copyright Copyright (c) 2018 Brady Miller <brady.g.miller@gmail.com>
12 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
18 require_once("../globals.php");
19 require_once("$srcdir/acl.inc");
21 use OpenEMR\Common\Csrf\CsrfUtils
;
22 use OpenEMR\Core\Header
;
24 // Set this to true for production use. If false you will get a "dry run" with no updates.
27 if (!acl_check('admin', 'super')) {
28 die(xlt('Not authorized'));
35 <title
><?php
echo xlt('Merge Patients'); ?
></title
>
36 <?php Header
::setupHeader(['jquery-ui']); ?
>
38 <script language
="JavaScript">
40 var mypcc
= <?php
echo js_escape($GLOBALS['phone_country_code']); ?
>;
45 // This is for callback by the find-patient popup.
46 function setpatient(pid
, lname
, fname
, dob
) {
47 el_pt_name
.value
= lname +
', ' + fname +
' (' + pid +
')';
51 // This invokes the find-patient popup.
52 function sel_patient(ename
, epid
) {
55 dlgopen('../main/calendar/find_patient_popup.php', '_blank', 600, 400);
62 <body
class="body_top">
63 <div
class="container">
64 <h2
><?php
echo xlt('Merge Patients') ?
></h2
>
68 function deleteRows($tblname, $colname, $source_pid)
71 $crow = sqlQuery("SELECT COUNT(*) AS count FROM " . escape_table_name($tblname) . " WHERE " . escape_sql_column_name($colname, array($tblname)) . " = ?", array($source_pid));
72 $count = $crow['count'];
74 $sql = "DELETE FROM " . escape_table_name($tblname) . " WHERE " . escape_sql_column_name($colname, array($tblname)) . " = ?";
75 echo "<br />$sql ($count)";
77 sqlStatement($sql, array($source_pid));
82 function updateRows($tblname, $colname, $source_pid, $target_pid)
85 $crow = sqlQuery("SELECT COUNT(*) AS count FROM " . escape_table_name($tblname) . " WHERE " . escape_sql_column_name($colname, array($tblname)) . " = ?", array($source_pid));
86 $count = $crow['count'];
88 $sql = "UPDATE " . escape_table_name($tblname) . " SET " . escape_sql_column_name($colname, array($tblname)) . " = ? WHERE " . escape_sql_column_name($colname, array($tblname)) . " = ?";
89 echo "<br />$sql ($count)";
91 sqlStatement($sql, array($target_pid, $source_pid));
96 if (!empty($_POST['form_submit'])) {
97 if (!CsrfUtils
::verifyCsrfToken($_POST["csrf_token_form"])) {
98 CsrfUtils
::csrfNotVerified();
101 $target_pid = intval($_POST['form_target_pid']);
102 $source_pid = intval($_POST['form_source_pid']);
103 echo "<div class='well'>";
104 if ($target_pid == $source_pid) {
105 die(xlt('Target and source pid may not be the same!'));
108 $tprow = sqlQuery("SELECT * FROM patient_data WHERE pid = ?", array($target_pid));
109 $sprow = sqlQuery("SELECT * FROM patient_data WHERE pid = ?", array($source_pid));
111 // Do some checking to make sure source and target exist and are the same person.
112 if (empty($tprow['pid'])) {
113 die(xlt('Target patient not found'));
116 if (empty($sprow['pid'])) {
117 die(xlt('Source patient not found'));
120 if ($tprow['ss'] != $sprow['ss']) {
121 die(xlt('Target and source SSN do not match'));
124 if (empty($tprow['DOB']) ||
$tprow['DOB'] == '0000-00-00') {
125 die(xlt('Target patient has no DOB'));
128 if (empty($sprow['DOB']) ||
$sprow['DOB'] == '0000-00-00') {
129 die(xlt('Source patient has no DOB'));
132 if ($tprow['DOB'] != $sprow['DOB']) {
133 die(xlt('Target and source DOB do not match'));
136 $tdocdir = "$OE_SITE_DIR/documents/" . check_file_dir_name($target_pid);
137 $sdocdir = "$OE_SITE_DIR/documents/" . check_file_dir_name($source_pid);
138 $sencdir = "$sdocdir/encounters";
139 $tencdir = "$tdocdir/encounters";
141 // Change normal documents first as that could fail if CouchDB connection fails.
142 $dres = sqlStatement("SELECT * FROM `documents` WHERE `foreign_id` = ?", array($source_pid));
143 while ($drow = sqlFetchArray($dres)) {
144 $d = new Document($drow['id']);
145 echo "<br />" . xlt('Changing patient ID for document') . ' ' . text($d->get_url_file());
147 if (!$d->change_patient($target_pid)) {
148 die("<br />" . xlt('Change failed! CouchDB connect error?'));
153 // Move scanned encounter documents and delete their container.
154 if (is_dir($sencdir)) {
155 if ($PRODUCTION && !file_exists($tdocdir)) {
159 if ($PRODUCTION && !file_exists($tencdir)) {
163 $dh = opendir($sencdir);
165 die(xlt('Cannot read directory') . " '" . text($sencdir) . "'");
168 while (false !== ($sfname = readdir($dh))) {
169 if ($sfname == '.' ||
$sfname == '..') {
173 if ($sfname == 'index.html') {
174 echo "<br />" . xlt('Deleting') . " " . text($sencdir) . "/" . text($sfname);
176 if (!unlink("$sencdir/$sfname")) {
177 die("<br />" . xlt('Delete failed!'));
184 echo "<br />" . xlt('Moving') . " " . text($sencdir) . "/" . text($sfname) . " " . xlt('to{{Destination}}') . " " . text($tencdir) . "/" . text($sfname);
186 if (!rename("$sencdir/$sfname", "$tencdir/$sfname")) {
187 die("<br />" . xlt('Move failed!'));
193 echo "<br />" . xlt('Deleting') . " $sencdir";
195 if (!rmdir($sencdir)) {
196 echo "<br />" . xlt('Directory delete failed; continuing.');
201 $tres = sqlStatement("SHOW TABLES");
202 while ($trow = sqlFetchArray($tres)) {
203 $tblname = array_shift($trow);
204 if ($tblname == 'patient_data' ||
$tblname == 'history_data' ||
$tblname == 'insurance_data') {
205 deleteRows($tblname, 'pid', $source_pid);
206 } else if ($tblname == 'chart_tracker') {
207 updateRows($tblname, 'ct_pid', $source_pid, $target_pid);
208 } else if ($tblname == 'documents') {
209 // Documents already handled.
210 } else if ($tblname == 'openemr_postcalendar_events') {
211 updateRows($tblname, 'pc_pid', $source_pid, $target_pid);
212 } else if ($tblname == 'log') {
213 // Don't mess with log data.
215 $crow = sqlQuery("SHOW COLUMNS FROM `" . escape_table_name($tblname) . "` WHERE " .
216 "`Field` LIKE 'pid' OR `Field` LIKE 'patient_id'");
217 if (!empty($crow['Field'])) {
218 $colname = $crow['Field'];
219 updateRows($tblname, $colname, $source_pid, $target_pid);
224 echo "<br />" . xlt('Merge complete.') . "</div>";
234 <form method
='post' action
='merge_patients.php'>
235 <input type
="hidden" name
="csrf_token_form" value
="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" />
236 <div
class="table-responsive">
237 <table style
='width:100%'>
240 <?php
echo xlt('Target Patient') ?
>
243 <input type
='text' size
='30' name
='form_target_patient'
244 value
=' (<?php echo xla('Click to select
'); ?>)'
245 onclick
='sel_patient(this, this.form.form_target_pid)'
246 title
='<?php echo xla('Click to select patient
'); ?>' readonly
/>
247 <input type
='hidden' name
='form_target_pid' value
='0' />
250 <?php
echo xlt('This is the main chart that is to receive the merged data.'); ?
>
255 <?php
echo xlt('Source Patient') ?
>
258 <input type
='text' size
='30' name
='form_source_patient'
259 value
=' (<?php echo xla('Click to select
'); ?>)'
260 onclick
='sel_patient(this, this.form.form_source_pid)'
261 title
='<?php echo xla('Click to select patient
'); ?>' readonly
/>
262 <input type
='hidden' name
='form_source_pid' value
='0' />
265 <?php
echo xlt('This is the chart that is to be merged into the main chart and then deleted.'); ?
>
269 <p
><input type
='submit' name
='form_submit' value
='<?php echo xla('Merge
'); ?>' /></p
>
272 <div
class="well well-lg">
273 <p
><strong
><?php
echo xlt('This utility is experimental. Back up your database and documents before using it!'); ?
></strong
></p
>
275 <?php
if (!$PRODUCTION) { ?
>
276 <p
><?php
echo xlt('This will be a "dry run" with no physical data updates.'); ?
></p
>
279 <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
>
281 <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
>
283 <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
>
285 <p
><?php
echo xlt('The merge will not run unless SSN and DOB for the two charts are identical. DOBs cannot be empty.'); ?
></p
>