• 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

42.55
/api/src/main/java/org/openmrs/ConceptAnswer.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;
11

12
import org.hibernate.annotations.BatchSize;
13
import org.hibernate.annotations.GenericGenerator;
14
import org.hibernate.annotations.Parameter;
15
import org.hibernate.envers.Audited;
16

17
import javax.persistence.Column;
18
import javax.persistence.Entity;
19
import javax.persistence.GeneratedValue;
20
import javax.persistence.GenerationType;
21
import javax.persistence.Id;
22
import javax.persistence.JoinColumn;
23
import javax.persistence.ManyToOne;
24
import javax.persistence.Table;
25
import java.util.Date;
26

27
/**
28
 * This class represents one option for an answer to a question type of {@link Concept}. The link to
29
 * the parent question Concept is stored in {@link #getConcept()} and the answer this object is
30
 * representing is stored in {@link #getAnswerConcept()}.
31
 *
32
 * @see Concept#getAnswers()
33
 */
34
@Entity
35
@Table(name = "concept_answer")
36
@BatchSize(size = 25)
37
@Audited
38
public class ConceptAnswer extends BaseOpenmrsObject implements Auditable, java.io.Serializable, Comparable<ConceptAnswer> {
39
        
40
        public static final long serialVersionUID = 3744L;
41
        
42
        // Fields
43
        @Id
44
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "concept_answer_id_seq")
45
        @GenericGenerator(
46
                        name = "concept_answer_id_seq",
47
                        strategy = "native",
48
                        parameters = @Parameter(name = "sequence", value = "concept_answer_concept_answer_id_seq")
49
        )
50
        @Column(name = "concept_answer_id")
51
        private Integer conceptAnswerId;
52
        
53
        /**
54
         * The question concept that this object is answering
55
         */
56
        @ManyToOne
57
        @JoinColumn(name = "concept_id", nullable = false)
58
        private Concept concept;
59
        
60
        /**
61
         * The answer to the question
62
         */
63
        @ManyToOne
64
        @JoinColumn(name = "answer_concept", nullable = false)
65
        private Concept answerConcept;
66
        
67
        /**
68
         * The {@link Drug} answer to the question. This can be null if this does not represent a drug
69
         * type of answer
70
         */
71
        @ManyToOne
72
        @JoinColumn(name = "answer_drug")
73
        private Drug answerDrug;
74
        
75
        @ManyToOne
76
        @JoinColumn(name = "creator", nullable = false)
77
        private User creator;
78
        
79
        @Column(name = "date_created", nullable = false)
80
        private Date dateCreated;
81
        
82
        @Column(name = "sort_weight")
83
        private Double sortWeight;
84
        
85
        // Constructors
86
        
87
        /** default constructor */
88
        public ConceptAnswer() {
1✔
89
        }
1✔
90
        
91
        /** constructor with id */
92
        public ConceptAnswer(Integer conceptAnswerId) {
1✔
93
                this.conceptAnswerId = conceptAnswerId;
1✔
94
        }
1✔
95
        
96
        public ConceptAnswer(Concept answerConcept) {
1✔
97
                this.answerConcept = answerConcept;
1✔
98
        }
1✔
99
        
100
        public ConceptAnswer(Concept answerConcept, Drug d) {
×
101
                this.answerConcept = answerConcept;
×
102
                this.answerDrug = d;
×
103
        }
×
104
        
105
        /**
106
         * @return Returns the answerConcept.
107
         */
108
        public Concept getAnswerConcept() {
109
                return answerConcept;
1✔
110
        }
111
        
112
        /**
113
         * @param answerConcept The answerConcept to set.
114
         */
115
        public void setAnswerConcept(Concept answerConcept) {
116
                this.answerConcept = answerConcept;
×
117
        }
×
118
        
119
        /**
120
         * @return Returns the answerDrug.
121
         */
122
        public Drug getAnswerDrug() {
123
                return answerDrug;
1✔
124
        }
125
        
126
        /**
127
         * @param answerDrug The answerDrug to set.
128
         */
129
        public void setAnswerDrug(Drug answerDrug) {
130
                this.answerDrug = answerDrug;
×
131
        }
×
132
        
133
        /**
134
         * @return Returns the concept.
135
         */
136
        public Concept getConcept() {
137
                return concept;
1✔
138
        }
139
        
140
        /**
141
         * @param concept The concept to set.
142
         */
143
        public void setConcept(Concept concept) {
144
                this.concept = concept;
1✔
145
        }
1✔
146
        
147
        /**
148
         * @return Returns the conceptAnswerId.
149
         */
150
        public Integer getConceptAnswerId() {
151
                return conceptAnswerId;
1✔
152
        }
153
        
154
        /**
155
         * @param conceptAnswerId The conceptAnswerId to set.
156
         */
157
        public void setConceptAnswerId(Integer conceptAnswerId) {
158
                this.conceptAnswerId = conceptAnswerId;
×
159
        }
×
160
        
161
        /**
162
         * @return Returns the creator.
163
         */
164
        @Override
165
        public User getCreator() {
166
                return creator;
×
167
        }
168
        
169
        /**
170
         * @param creator The creator to set.
171
         */
172
        @Override
173
        public void setCreator(User creator) {
174
                this.creator = creator;
×
175
        }
×
176
        
177
        /**
178
         * @return Returns the dateCreated.
179
         */
180
        @Override
181
        public Date getDateCreated() {
182
                return dateCreated;
×
183
        }
184
        
185
        /**
186
         * @param dateCreated The dateCreated to set.
187
         */
188
        @Override
189
        public void setDateCreated(Date dateCreated) {
190
                this.dateCreated = dateCreated;
×
191
        }
×
192
        
193
        /**
194
         * @since 1.5
195
         * @see org.openmrs.OpenmrsObject#getId()
196
         */
197
        @Override
198
        public Integer getId() {
199
                return getConceptAnswerId();
1✔
200
        }
201
        
202
        /**
203
         * @since 1.5
204
         * @see org.openmrs.OpenmrsObject#setId(java.lang.Integer)
205
         */
206
        @Override
207
        public void setId(Integer id) {
208
                setConceptAnswerId(id);
×
209
        }
×
210
        
211
        /**
212
         * Not currently used. Always returns null.
213
         *
214
         * @see org.openmrs.Auditable#getChangedBy()
215
         */
216
        @Override
217
        public User getChangedBy() {
218
                return null;
×
219
        }
220
        
221
        /**
222
         * Not currently used. Always returns null.
223
         *
224
         * @see org.openmrs.Auditable#getDateChanged()
225
         */
226
        @Override
227
        public Date getDateChanged() {
228
                return null;
×
229
        }
230
        
231
        /**
232
         * Not currently used.
233
         *
234
         * @see org.openmrs.Auditable#setChangedBy(org.openmrs.User)
235
         */
236
        @Override
237
        public void setChangedBy(User changedBy) {
238
        }
×
239
        
240
        /**
241
         * Not currently used.
242
         *
243
         * @see org.openmrs.Auditable#setDateChanged(java.util.Date)
244
         */
245
        @Override
246
        public void setDateChanged(Date dateChanged) {
247
        }
×
248
        
249
        /**
250
         * @return Returns the sortWeight.
251
         */
252
        public Double getSortWeight() {
253
                return sortWeight;
1✔
254
        }
255
        
256
        /**
257
         * @param sortWeight The sortWeight to set.
258
         */
259
        public void setSortWeight(Double sortWeight) {
260
                this.sortWeight = sortWeight;
1✔
261
        }
1✔
262
        
263
        /**
264
         * @see java.lang.Comparable#compareTo(java.lang.Object)
265
         * Note: this comparator imposes orderings that are inconsistent with equals.
266
         */
267
        @Override
268
        @SuppressWarnings("squid:S1210")
269
        public int compareTo(ConceptAnswer ca) {
270
                if ((getSortWeight() == null) && (ca.getSortWeight() != null)) {
1✔
271
                        return -1;
1✔
272
                }
UNCOV
273
                if ((getSortWeight() != null) && (ca.getSortWeight() == null)) {
×
UNCOV
274
                        return 1;
×
275
                }
276
                if ((getSortWeight() == null) && (ca.getSortWeight() == null)) {
×
277
                        return 0;
×
278
                }
279
                return (getSortWeight() < ca.getSortWeight()) ? -1 : (getSortWeight() > ca.getSortWeight()) ? 1 : 0;
×
280
        }
281
}
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