• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 27660627112

17 Jun 2026 01:57AM UTC coverage: 63.664% (+0.2%) from 63.512%
27660627112

push

github

web-flow
mergePatients does not reassign Conditions, Allergies, or MedicationDispenses to the surviving patient (#6198)

* mergePatients now reassigns Conditions, Allergies and MedicationDispenses to the surviving patient

PatientServiceImpl.mergePatients had no reassignment helpers for the
patient-scoped Conditions, Allergies and MedicationDispenses tables, which
were added to core after the merge logic was written. After a merge these
clinical records stayed attached to the now-voided non-preferred patient and
silently disappeared from the surviving patient's record - a clinical-data-loss
and patient-safety concern (e.g. a hidden allergy).

Add mergeConditions, mergeAllergies and mergeMedicationDispenses helpers that
move each non-voided record from the non-preferred to the preferred patient via
the corresponding services, mirroring the existing
mergeObservationsNotContainedInEncounters pattern. Saving through the services
keeps the changes audited and emits the normal save events. The moves are
recorded in new PersonMergeLogData collections so they appear in the
PersonMergeLog.

Allergies are deduplicated by allergen: an allergy whose allergen the preferred
patient already records is not moved, because a patient cannot hold two
allergies for the same allergen and a duplicate would make the survivor's
allergy list fail to load afterwards.

Fixes #6194

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Keep @since on the new PersonMergeLogData accessors only, not the private fields

The moved* fields are private (not API and not rendered by Javadoc), so the
field-level @since 2.9.0 tags were redundant. The public getters/adders keep
@since 2.9.0, matching the codebase convention (Patient, Condition, Allergy
attach @since to accessors, not backing fields).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

52 of 55 new or added lines in 2 files covered. (94.55%)

3 existing lines in 2 files now uncovered.

23909 of 37555 relevant lines covered (63.66%)

0.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.48
/api/src/main/java/org/openmrs/person/PersonMergeLogData.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.person;
11

12
import java.util.ArrayList;
13
import java.util.Date;
14
import java.util.List;
15

16
import org.openmrs.api.PatientService;
17

18
/**
19
 * This class is used for communicating to the <code>PatientService</code> the data that
20
 * needs to be serialized. This data represents the details of a merge. It is also used for
21
 * abstracting the serialization outside of the PatientService and to allow storing the
22
 * deserialized form of the merged data
23
 *
24
 * @see PersonMergeLog
25
 * @see PatientService#mergePatients(org.openmrs.Patient, org.openmrs.Patient)
26
 * @since 1.9
27
 */
28
public class PersonMergeLogData {
1✔
29
        
30
        /**
31
         * List of UUIDs of visits moved from non-preferred to preferred
32
         */
33
        private List<String> movedVisits;
34
        
35
        /**
36
         * List of UUIDs of encounters moved from non-preferred to preferred
37
         */
38
        private List<String> movedEncounters;
39
        
40
        /**
41
         * List of UUIDs of patient programs copied from non-preferred to preferred
42
         * (Deprecated in 2.6.8 and 2.7.0+, as we now move programs)
43
         */
44
        @Deprecated
45
        private List<String> createdPrograms;
46

47
        /**
48
         * List of UUIDs of patient programs moved from non-preferred to preferred
49
         */
50
        private List<String> movedPrograms;
51
        
52
        /**
53
         * List of UUIDs of voided relationships
54
         */
55
        private List<String> voidedRelationships;
56
        
57
        /**
58
         * List of UUIDs of created relationships
59
         */
60
        private List<String> createdRelationships;
61
        
62
        /**
63
         * List of UUIDs of observations not contained within any encounter moved from non-preferred to
64
         * preferred
65
         */
66
        private List<String> movedIndependentObservations;
67

68
        /**
69
         * List of UUIDs of conditions moved from non-preferred to preferred
70
         */
71
        private List<String> movedConditions;
72

73
        /**
74
         * List of UUIDs of allergies moved from non-preferred to preferred
75
         */
76
        private List<String> movedAllergies;
77

78
        /**
79
         * List of UUIDs of medication dispenses moved from non-preferred to preferred
80
         */
81
        private List<String> movedMedicationDispenses;
82

83
        /**
84
         * List of UUIDs of orders copied from non-preferred to preferred
85
         */
86
        private List<String> createdOrders;
87
        
88
        /**
89
         * List of UUIDs of identifiers copied from non-preferred to preferred
90
         */
91
        private List<String> createdIdentifiers;
92
        
93
        /**
94
         * List of UUIDs of addresses copied from non-preferred to preferred
95
         */
96
        private List<String> createdAddresses;
97
        
98
        /**
99
         * List of UUIDs of names copied from non-preferred to preferred
100
         */
101
        private List<String> createdNames;
102
        
103
        /**
104
         * List of UUIDs of attributes copied from non-preferred to preferred
105
         */
106
        private List<String> createdAttributes;
107
        
108
        /**
109
         * List of UUIDs of users moved to be associated from non-preferred to be associated to
110
         * preferred
111
         */
112
        private List<String> movedUsers;
113
        
114
        /**
115
         * Value of gender of preferred person as it was before the merge occurred
116
         */
117
        private String priorGender;
118
        
119
        /**
120
         * Value of Date of Birth of preferred person as it was before the merge occurred
121
         */
122
        private Date priorDateOfBirth;
123
        
124
        /**
125
         * Whether the date of birth of preferred person was an estimated value before the merge
126
         * occurred
127
         */
128
        private boolean priorDateOfBirthEstimated;
129
        
130
        /**
131
         * Value of Date of Death of preferred person as it was before the merge occurred
132
         */
133
        private Date priorDateOfDeath;
134
        
135
        /**
136
         * Whether the date of death of preferred person was an estimated value before the merge
137
         * occurred
138
         */
139
        private Boolean priorDateOfDeathEstimated;
140
        
141
        /**
142
         * Value of cause of death of preferred person as it was before the merge occurred
143
         */
144
        private String priorCauseOfDeath;
145
        
146
        public List<String> getMovedVisits() {
147
                return movedVisits;
1✔
148
        }
149
        
150
        public List<String> getMovedEncounters() {
151
                return movedEncounters;
1✔
152
        }
153
        
154
        public void addMovedVisit(String uuid) {
155
                if (movedVisits == null) {
1✔
156
                        movedVisits = new ArrayList<>();
1✔
157
                }
158
                movedVisits.add(uuid);
1✔
159
        }
1✔
160
        
161
        public void addMovedEncounter(String uuid) {
162
                if (movedEncounters == null) {
1✔
163
                        movedEncounters = new ArrayList<>();
1✔
164
                }
165
                movedEncounters.add(uuid);
1✔
166
        }
1✔
167
        
168
        @Deprecated
169
        public List<String> getCreatedPrograms() {
170
                return createdPrograms;
1✔
171
        }
172
        
173
        @Deprecated
174
        public void addCreatedProgram(String uuid) {
175
                if (createdPrograms == null) {
×
176
                        createdPrograms = new ArrayList<>();
×
177
                }
178
                createdPrograms.add(uuid);
×
179
        }
×
180

181
        public List<String> getMovedPrograms() {
182
                return movedPrograms;
1✔
183
        }
184

185
        public void addMovedProgram(String uuid) {
186
                if (movedPrograms == null) {
1✔
187
                        movedPrograms = new ArrayList<>();
1✔
188
                }
189
                movedPrograms.add(uuid);
1✔
190
        }
1✔
191
        
192
        public List<String> getVoidedRelationships() {
193
                return voidedRelationships;
1✔
194
        }
195
        
196
        public void addVoidedRelationship(String uuid) {
197
                if (voidedRelationships == null) {
1✔
198
                        voidedRelationships = new ArrayList<>();
1✔
199
                }
200
                voidedRelationships.add(uuid);
1✔
201
        }
1✔
202
        
203
        public List<String> getCreatedRelationships() {
204
                return createdRelationships;
1✔
205
        }
206
        
207
        public void addCreatedRelationship(String uuid) {
208
                if (createdRelationships == null) {
1✔
209
                        createdRelationships = new ArrayList<>();
1✔
210
                }
211
                createdRelationships.add(uuid);
1✔
212
        }
1✔
213
        
214
        public List<String> getMovedIndependentObservations() {
215
                return movedIndependentObservations;
1✔
216
        }
217
        
218
        public void addMovedIndependentObservation(String uuid) {
219
                if (movedIndependentObservations == null) {
1✔
220
                        movedIndependentObservations = new ArrayList<>();
1✔
221
                }
222
                movedIndependentObservations.add(uuid);
1✔
223
        }
1✔
224

225
        /**
226
         * @since 2.9.0
227
         */
228
        public List<String> getMovedConditions() {
229
                return movedConditions;
1✔
230
        }
231

232
        /**
233
         * @since 2.9.0
234
         */
235
        public void addMovedCondition(String uuid) {
236
                if (movedConditions == null) {
1✔
237
                        movedConditions = new ArrayList<>();
1✔
238
                }
239
                movedConditions.add(uuid);
1✔
240
        }
1✔
241

242
        /**
243
         * @since 2.9.0
244
         */
245
        public List<String> getMovedAllergies() {
246
                return movedAllergies;
1✔
247
        }
248

249
        /**
250
         * @since 2.9.0
251
         */
252
        public void addMovedAllergy(String uuid) {
253
                if (movedAllergies == null) {
1✔
254
                        movedAllergies = new ArrayList<>();
1✔
255
                }
256
                movedAllergies.add(uuid);
1✔
257
        }
1✔
258

259
        /**
260
         * @since 2.9.0
261
         */
262
        public List<String> getMovedMedicationDispenses() {
263
                return movedMedicationDispenses;
1✔
264
        }
265

266
        /**
267
         * @since 2.9.0
268
         */
269
        public void addMovedMedicationDispense(String uuid) {
270
                if (movedMedicationDispenses == null) {
1✔
271
                        movedMedicationDispenses = new ArrayList<>();
1✔
272
                }
273
                movedMedicationDispenses.add(uuid);
1✔
274
        }
1✔
275

276
        public List<String> getCreatedOrders() {
277
                return createdOrders;
1✔
278
        }
279
        
280
        public void addCreatedOrder(String uuid) {
281
                if (createdOrders == null) {
1✔
282
                        createdOrders = new ArrayList<>();
1✔
283
                }
284
                createdOrders.add(uuid);
1✔
285
        }
1✔
286
        
287
        public List<String> getCreatedIdentifiers() {
288
                return createdIdentifiers;
1✔
289
        }
290
        
291
        public void addCreatedIdentifier(String uuid) {
292
                if (createdIdentifiers == null) {
1✔
293
                        createdIdentifiers = new ArrayList<>();
1✔
294
                }
295
                createdIdentifiers.add(uuid);
1✔
296
        }
1✔
297
        
298
        public List<String> getCreatedAddresses() {
299
                return createdAddresses;
1✔
300
        }
301
        
302
        public void addCreatedAddress(String uuid) {
303
                if (createdAddresses == null) {
1✔
304
                        createdAddresses = new ArrayList<>();
1✔
305
                }
306
                createdAddresses.add(uuid);
1✔
307
        }
1✔
308
        
309
        public List<String> getCreatedNames() {
310
                return createdNames;
1✔
311
        }
312
        
313
        public void addCreatedName(String uuid) {
314
                if (createdNames == null) {
1✔
315
                        createdNames = new ArrayList<>();
1✔
316
                }
317
                createdNames.add(uuid);
1✔
318
        }
1✔
319
        
320
        public List<String> getCreatedAttributes() {
321
                return createdAttributes;
1✔
322
        }
323
        
324
        public void addCreatedAttribute(String uuid) {
325
                if (createdAttributes == null) {
1✔
326
                        createdAttributes = new ArrayList<>();
1✔
327
                }
328
                createdAttributes.add(uuid);
1✔
329
        }
1✔
330
        
331
        public List<String> getMovedUsers() {
332
                return movedUsers;
1✔
333
        }
334
        
335
        public void addMovedUser(String uuid) {
336
                if (movedUsers == null) {
1✔
337
                        movedUsers = new ArrayList<>();
1✔
338
                }
339
                movedUsers.add(uuid);
1✔
340
        }
1✔
341
        
342
        public String getPriorGender() {
343
                return priorGender;
1✔
344
        }
345
        
346
        public void setPriorGender(String priorGender) {
347
                this.priorGender = priorGender;
1✔
348
        }
1✔
349
        
350
        public Date getPriorDateOfBirth() {
351
                return priorDateOfBirth;
1✔
352
        }
353
        
354
        public void setPriorDateOfBirth(Date priorDateOfBirth) {
355
                this.priorDateOfBirth = priorDateOfBirth;
1✔
356
        }
1✔
357
        
358
        public boolean isPriorDateOfBirthEstimated() {
359
                return priorDateOfBirthEstimated;
1✔
360
        }
361
        
362
        public void setPriorDateOfBirthEstimated(boolean priorDateOfBirthEstimated) {
363
                this.priorDateOfBirthEstimated = priorDateOfBirthEstimated;
1✔
364
        }
1✔
365
        
366
        public Date getPriorDateOfDeath() {
367
                return priorDateOfDeath;
1✔
368
        }
369
        
370
        public void setPriorDateOfDeath(Date priorDateOfDeath) {
371
                this.priorDateOfDeath = priorDateOfDeath;
1✔
372
        }
1✔
373
        
374
        public Boolean getPriorDateOfDeathEstimated() {
375
                return priorDateOfDeathEstimated;
1✔
376
        }
377
        
378
        public void setPriorDateOfDeathEstimated(Boolean priorDateOfDeathEstimated) {
379
                this.priorDateOfDeathEstimated = priorDateOfDeathEstimated;
1✔
380
        }
1✔
381
        
382
        public String getPriorCauseOfDeath() {
383
                return priorCauseOfDeath;
1✔
384
        }
385
        
386
        public void setPriorCauseOfDeath(String uuid) {
387
                this.priorCauseOfDeath = uuid;
1✔
388
        }
1✔
389
        
390
        /**
391
         * Computes a unique hash value representing the object
392
         *
393
         * @return hash value
394
         */
395
        public int computeHashValue() {
396
                String str = "";
1✔
397
                if (getCreatedAddresses() != null) {
1✔
398
                        str += getCreatedAddresses().toString();
1✔
399
                }
400
                if (getCreatedAttributes() != null) {
1✔
401
                        str += getCreatedAttributes().toString();
1✔
402
                }
403
                if (getCreatedIdentifiers() != null) {
1✔
404
                        str += getCreatedIdentifiers().toString();
1✔
405
                }
406
                if (getCreatedNames() != null) {
1✔
407
                        str += getCreatedNames().toString();
1✔
408
                }
409
                if (getCreatedOrders() != null) {
1✔
410
                        str += getCreatedOrders().toString();
1✔
411
                }
412
                if (getCreatedPrograms() != null) {
1✔
413
                        str += getCreatedPrograms().toString();
×
414
                }
415
                if (getMovedPrograms() != null) {
1✔
416
                        str += getMovedPrograms().toString();
1✔
417
                }
418
                if (getCreatedRelationships() != null) {
1✔
419
                        str += getCreatedRelationships().toString();
1✔
420
                }
421
                if (getVoidedRelationships() != null) {
1✔
422
                        str += getVoidedRelationships().toString();
1✔
423
                }
424
                if (getMovedVisits() != null) {
1✔
425
                        str += getMovedVisits().toString();
×
426
                }
427
                if (getMovedEncounters() != null) {
1✔
428
                        str += getMovedEncounters().toString();
1✔
429
                }
430
                if (getMovedIndependentObservations() != null) {
1✔
431
                        str += getMovedIndependentObservations().toString();
1✔
432
                }
433
                if (getMovedConditions() != null) {
1✔
NEW
434
                        str += getMovedConditions().toString();
×
435
                }
436
                if (getMovedAllergies() != null) {
1✔
NEW
437
                        str += getMovedAllergies().toString();
×
438
                }
439
                if (getMovedMedicationDispenses() != null) {
1✔
NEW
440
                        str += getMovedMedicationDispenses().toString();
×
441
                }
442
                if (getMovedUsers() != null) {
1✔
443
                        str += getMovedUsers().toString();
1✔
444
                }
445
                str += getPriorCauseOfDeath();
1✔
446
                str += getPriorGender();
1✔
447
                str += (getPriorDateOfBirth() != null) ? getPriorDateOfBirth().toString() : getPriorDateOfBirth();
1✔
448
                str += (getPriorDateOfBirth() != null) ? getPriorDateOfDeath().toString() : getPriorDateOfDeath();
1✔
449
                str += isPriorDateOfBirthEstimated();
1✔
450
                return str.hashCode();
1✔
451
        }
452
        
453
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc