Skip to content

Commit 37767da

Browse files
authored
FM2-664: Add support for the notes field on the Immunization resource (#570)
* FM2-664: Add support for the notes field on the Immunization resource * make sure that the note is not mapped when the concept is absent, but the operation succeeds * add constants
1 parent f7eeb9f commit 37767da

File tree

5 files changed

+184
-2
lines changed

5 files changed

+184
-2
lines changed

api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ImmunizationTranslatorImpl.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@
6161
public class ImmunizationTranslatorImpl implements ImmunizationTranslator {
6262

6363
public static final String IMMUNIZATION_GROUPING_CONCEPT = "CIEL:1421";
64+
65+
public static final String IMMUNIZATION_FREE_TEXT_COMMENT_CONCEPT = "CIEL:161011";
6466

6567
public static final Set<String> IMMUNIZATION_CONCEPTS = ImmutableSet.of("CIEL:984", "CIEL:1410", "CIEL:1418",
66-
"CIEL:1419", "CIEL:1420", "CIEL:165907");
68+
"CIEL:1419", "CIEL:1420", "CIEL:165907", "CIEL:161011");
6769

6870
public static final String CIEL_984;
6971

@@ -77,6 +79,8 @@ public class ImmunizationTranslatorImpl implements ImmunizationTranslator {
7779

7880
public static final String CIEL_165907;
7981

82+
public static final String CIEL_161011;
83+
8084
static {
8185
final Iterator<String> conceptIterator = IMMUNIZATION_CONCEPTS.iterator();
8286
CIEL_984 = conceptIterator.next();
@@ -85,6 +89,7 @@ public class ImmunizationTranslatorImpl implements ImmunizationTranslator {
8589
CIEL_1419 = conceptIterator.next();
8690
CIEL_1420 = conceptIterator.next();
8791
CIEL_165907 = conceptIterator.next();
92+
CIEL_161011 = conceptIterator.next();
8893
}
8994

9095
@Getter(PROTECTED)
@@ -380,6 +385,27 @@ public Obs toOpenmrsType(@Nonnull Obs openmrsImmunization, @Nonnull Immunization
380385
openmrsImmunization.removeGroupMember(members.get(CIEL_165907));
381386
}
382387

388+
Concept noteConcept = helper.conceptOrNull(CIEL_161011);
389+
if (noteConcept != null && fhirImmunization.hasNote() && fhirImmunization.getNoteFirstRep().hasText()) {
390+
Obs obs = members.get(CIEL_161011);
391+
if (obs == null) {
392+
obs = helper.addNewObs(openmrsImmunization, CIEL_161011);
393+
members.put(CIEL_161011, obs);
394+
obs.setValueText(fhirImmunization.getNoteFirstRep().getText());
395+
} else if (obs.getId() == null) {
396+
obs.setValueText(fhirImmunization.getNoteFirstRep().getText());
397+
} else {
398+
String newValue = fhirImmunization.getNoteFirstRep().getText();
399+
String prevValue = obs.getValueText();
400+
if (!newValue.equals(prevValue)) {
401+
obs = helper.replaceObs(openmrsImmunization, obs);
402+
obs.setValueText(newValue);
403+
}
404+
}
405+
} else {
406+
openmrsImmunization.removeGroupMember(members.get(CIEL_161011));
407+
}
408+
383409
return openmrsImmunization;
384410
}
385411

@@ -442,6 +468,13 @@ public Immunization toFhirResource(@Nonnull Obs openmrsImmunization) {
442468
}
443469
}
444470

471+
{
472+
Obs obs = members.get(CIEL_161011);
473+
if (obs != null) {
474+
immunization.addNote().setText(obs.getValueText());
475+
}
476+
}
477+
445478
immunization.getMeta().setLastUpdated(getLastUpdated(openmrsImmunization));
446479
immunization.getMeta().setVersionId(getVersionId(openmrsImmunization));
447480

api/src/main/java/org/openmrs/module/fhir2/api/util/ImmunizationObsGroupHelper.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import static org.openmrs.module.fhir2.FhirConstants.ADMINISTERING_ENCOUNTER_ROLE_PROPERTY;
1414
import static org.openmrs.module.fhir2.FhirConstants.IMMUNIZATIONS_ENCOUNTER_TYPE_PROPERTY;
1515
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.IMMUNIZATION_CONCEPTS;
16+
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.IMMUNIZATION_FREE_TEXT_COMMENT_CONCEPT;
1617
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.IMMUNIZATION_GROUPING_CONCEPT;
1718
import static org.openmrs.module.fhir2.api.util.FhirUtils.createExceptionErrorOperationOutcome;
1819

@@ -89,7 +90,14 @@ public EncounterRole getAdministeringEncounterRole() {
8990
+ "', but no administering encounter role is defined for this instance."));
9091
}
9192

93+
public Concept conceptOrNull(String refTerm) {
94+
return getConceptFromMapping(refTerm).orElse(null);
95+
}
96+
9297
public Concept concept(String refTerm) {
98+
if (IMMUNIZATION_FREE_TEXT_COMMENT_CONCEPT.equals(refTerm)) {
99+
return conceptOrNull(refTerm);
100+
}
93101
return getConceptFromMapping(refTerm).orElseThrow(
94102
() -> createImmunizationRequestSetupError("The Immunization resource requires a concept mapped to '" + refTerm
95103
+ "', however either multiple concepts are mapped to that term or not concepts are mapped to that term."));

api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirImmunizationServiceImplTest.java

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,26 @@
1212
import static org.hamcrest.CoreMatchers.is;
1313
import static org.hamcrest.MatcherAssert.assertThat;
1414
import static org.hamcrest.Matchers.equalTo;
15+
import static org.hamcrest.Matchers.not;
1516
import static org.hl7.fhir.r4.model.Patient.SP_IDENTIFIER;
17+
import static org.junit.Assert.assertNull;
18+
import static org.junit.Assert.assertTrue;
19+
import static org.junit.jupiter.api.Assertions.assertNotNull;
1620
import static org.openmrs.module.fhir2.FhirConstants.ENCOUNTER;
1721
import static org.openmrs.module.fhir2.FhirConstants.PATIENT;
1822
import static org.openmrs.module.fhir2.FhirConstants.PRACTITIONER;
1923
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.CIEL_1410;
2024
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.CIEL_1418;
2125
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.CIEL_1419;
2226
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.CIEL_1420;
27+
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.CIEL_161011;
2328
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.CIEL_165907;
2429
import static org.openmrs.module.fhir2.api.translators.impl.ImmunizationTranslatorImpl.CIEL_984;
2530

31+
import java.nio.charset.StandardCharsets;
2632
import java.util.List;
2733
import java.util.Map;
34+
import java.util.Objects;
2835
import java.util.Set;
2936
import java.util.stream.Collectors;
3037

@@ -34,6 +41,7 @@
3441
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
3542
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
3643
import ca.uhn.fhir.rest.param.ReferenceParam;
44+
import org.apache.commons.io.IOUtils;
3745
import org.hl7.fhir.r4.model.Coding;
3846
import org.hl7.fhir.r4.model.DateTimeType;
3947
import org.hl7.fhir.r4.model.DateType;
@@ -43,6 +51,7 @@
4351
import org.openmrs.Obs;
4452
import org.openmrs.Provider;
4553
import org.openmrs.api.AdministrationService;
54+
import org.openmrs.api.ConceptService;
4655
import org.openmrs.api.ObsService;
4756
import org.openmrs.module.fhir2.BaseFhirContextSensitiveTest;
4857
import org.openmrs.module.fhir2.FhirConstants;
@@ -51,7 +60,11 @@
5160
import org.springframework.beans.factory.annotation.Qualifier;
5261

5362
public class FhirImmunizationServiceImplTest extends BaseFhirContextSensitiveTest {
54-
63+
64+
private static final String FREETEXT_COMMENT_CONCEPT_CODE = "161011";
65+
66+
private static final String FREETEXT_COMMENT_CONCEPT_SOURCE = "CIEL";
67+
5568
private static final String IMMUNIZATIONS_METADATA_XML = "org/openmrs/module/fhir2/Immunization_metadata.xml";
5669

5770
private static final String IMMUNIZATIONS_INITIAL_DATA_XML = "org/openmrs/module/fhir2/api/services/impl/FhirImmunizationService_initial_data.xml";
@@ -61,6 +74,9 @@ public class FhirImmunizationServiceImplTest extends BaseFhirContextSensitiveTes
6174
@Autowired
6275
private FhirImmunizationServiceImpl service;
6376

77+
@Autowired
78+
private ConceptService conceptService;
79+
6480
@Autowired
6581
private ObsService obsService;
6682

@@ -144,6 +160,43 @@ public void saveImmunization_shouldCreateEncounterAndObsGroupWhenNewImmunization
144160
equalTo(new DateTimeType("2022-07-31T18:30:00.000Z").getValue()));
145161
}
146162

163+
@Test
164+
public void saveImmunization_shouldSaveImmunizationWithNoteField() throws Exception {
165+
FhirContext ctx = FhirContext.forR4();
166+
IParser parser = ctx.newJsonParser();
167+
String json = IOUtils.toString(
168+
Objects.requireNonNull(
169+
getClass().getResourceAsStream("/org/openmrs/module/fhir2/providers/immunization-note.json")),
170+
StandardCharsets.UTF_8);
171+
Immunization newImmunization = parser.parseResource(Immunization.class, json);
172+
Immunization savedImmunization = service.create(newImmunization);
173+
Obs obs = obsService.getObsByUuid(savedImmunization.getId());
174+
Map<String, Obs> members = helper.getObsMembersMap(obs);
175+
assertThat(members.get(CIEL_161011).getValueText(), is("This is a test immunization note."));
176+
assertThat(savedImmunization.getNoteFirstRep().getText(), is("This is a test immunization note."));
177+
}
178+
179+
@Test
180+
public void saveImmunization_shouldNotFailIfNoteConceptIsMissingAndNoteProvided() throws Exception {
181+
// Remove the note concept since @Before loads it
182+
conceptService.purgeConcept(conceptService.getConceptByMapping(FREETEXT_COMMENT_CONCEPT_CODE, FREETEXT_COMMENT_CONCEPT_SOURCE));
183+
assertNull(conceptService.getConceptByMapping(FREETEXT_COMMENT_CONCEPT_CODE, FREETEXT_COMMENT_CONCEPT_SOURCE));
184+
185+
FhirContext ctx = FhirContext.forR4();
186+
IParser parser = ctx.newJsonParser();
187+
String json = IOUtils.toString(
188+
Objects.requireNonNull(
189+
getClass().getResourceAsStream("/org/openmrs/module/fhir2/providers/immunization-note.json")),
190+
StandardCharsets.UTF_8);
191+
Immunization newImmunization = parser.parseResource(Immunization.class, json);
192+
Immunization savedImmunization = service.create(newImmunization);
193+
Obs obs = obsService.getObsByUuid(savedImmunization.getId());
194+
Map<String, Obs> members = helper.getObsMembersMap(obs);
195+
assertNull(members.get(CIEL_161011));
196+
assertTrue(savedImmunization.getNote().isEmpty() || savedImmunization.getNoteFirstRep().getText() == null);
197+
assertThat(savedImmunization.getNoteFirstRep().getText(), is(not("This is a test immunization note.")));
198+
}
199+
147200
@Test
148201
public void updateImmunization_shouldUpdateImmunizationAccordingly() {
149202
// setup
@@ -188,6 +241,43 @@ public void updateImmunization_shouldUpdateImmunizationAccordingly() {
188241
assertThat(members.get(CIEL_165907).getValueDate(), equalTo(new DateType("2020-10-08").getValue()));
189242
}
190243

244+
@Test
245+
public void updateImmunization_shouldUpdateNoteFieldWhenNoteConceptIsAvailable() throws Exception {
246+
FhirContext ctx = FhirContext.forR4();
247+
IParser parser = ctx.newJsonParser();
248+
String createJson = IOUtils.toString(
249+
Objects.requireNonNull(
250+
getClass().getResourceAsStream("/org/openmrs/module/fhir2/providers/immunization-note.json")),
251+
StandardCharsets.UTF_8);
252+
Immunization created = service.create(parser.parseResource(Immunization.class, createJson));
253+
assertThat(created.getNoteFirstRep().getText(), is("This is a test immunization note."));
254+
255+
Immunization immunizationToBeUpdated = service.get(created.getId());
256+
immunizationToBeUpdated.getNote().clear();
257+
immunizationToBeUpdated.addNote().setText("This is an UPDATED immunization note.");
258+
Immunization updated = service.update(created.getId(), immunizationToBeUpdated);
259+
assertThat(updated.getNoteFirstRep().getText(), is("This is an UPDATED immunization note."));
260+
}
261+
262+
@Test
263+
public void updateImmunization_shouldNotFailIfNoteConceptIsMissingButNoteIsProvided() throws Exception {
264+
// Remove the note concept since @Before loads it
265+
conceptService.purgeConcept(conceptService.getConceptByMapping(FREETEXT_COMMENT_CONCEPT_CODE, FREETEXT_COMMENT_CONCEPT_SOURCE));
266+
assertNull(conceptService.getConceptByMapping(FREETEXT_COMMENT_CONCEPT_CODE, FREETEXT_COMMENT_CONCEPT_SOURCE));
267+
268+
FhirContext ctx = FhirContext.forR4();
269+
IParser parser = ctx.newJsonParser();
270+
String createJson = IOUtils.toString(
271+
Objects.requireNonNull(
272+
getClass().getResourceAsStream("/org/openmrs/module/fhir2/providers/immunization-note.json")),
273+
StandardCharsets.UTF_8);
274+
Immunization created = service.create(parser.parseResource(Immunization.class, createJson));
275+
created.getNote().clear();
276+
created.addNote().setText("This is an UPDATED immunization note.");
277+
Immunization updated = service.update(created.getId(), created);
278+
assertTrue(updated.getNote().isEmpty() || updated.getNoteFirstRep().getText() == null);
279+
}
280+
191281
@Test
192282
public void searchImmunizations_shouldFetchImmunizationsByPatientIdentifier() {
193283
// setup

test-data/src/main/resources/org/openmrs/module/fhir2/Immunization_metadata.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,10 @@
6767
<concept_reference_term code="165907" concept_reference_term_id="12014" concept_source_id="60000" creator="1" date_created="2020-07-07 19:08:19.0" name="" retired="false" uuid="0c6863f7-d439-41ab-b32e-84edafaddeb6"/>
6868
<concept_reference_map concept_id="70014" concept_map_id="11014" concept_map_type_id="2" concept_reference_term_id="12014" creator="1" date_created="2020-07-07 19:08:19.0" uuid="ed4768c8-b917-465d-ac69-589cd75cff07"/>
6969
<concept_name concept_id="70014" concept_name_id="70014" concept_name_type="FULLY_SPECIFIED" creator="1" date_created="2020-07-07 19:30:26.0" locale="en" locale_preferred="true" name=" Vaccine lot expiration date" uuid="a349f90e-f317-4817-ae53-e9430e857f8d" voided="false"/>
70+
71+
<!-- CIEL:161011 Free text comment -->
72+
<concept changed_by="1" class_id="5" concept_id="70015" creator="1" datatype_id="3" date_changed="2024-01-01 00:00:00.0" date_created="2024-01-01 00:00:00.0" is_set="false" retired="false" uuid="161011AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"/>
73+
<concept_reference_term code="161011" concept_reference_term_id="12015" concept_source_id="60000" creator="1" date_created="2024-01-01 00:00:00.0" name="" retired="false" uuid="ciel-161011-term-uuid"/>
74+
<concept_reference_map concept_id="70015" concept_map_id="11015" concept_map_type_id="2" concept_reference_term_id="12015" creator="1" date_created="2024-01-01 00:00:00.0" uuid="ciel-161011-map-uuid"/>
75+
<concept_name concept_id="70015" concept_name_id="70015" concept_name_type="FULLY_SPECIFIED" creator="1" date_created="2024-01-01 00:00:00.0" locale="en" locale_preferred="true" name="Free text comment" uuid="ciel-161011-name-uuid" voided="false"/>
7076
</dataset>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"resourceType": "Immunization",
3+
"status": "completed",
4+
"vaccineCode": {
5+
"coding": [
6+
{
7+
"code": "15f83cd6-64e9-4e06-a5f9-364d3b14a43d",
8+
"display": "Aspirin as a vaccine"
9+
}
10+
]
11+
},
12+
"patient": {
13+
"reference": "Patient/a7e04421-525f-442f-8138-05b619d16def",
14+
"type": "Patient"
15+
},
16+
"encounter": {
17+
"reference": "Encounter/7d8c1980-6b78-11e0-93c3-18a905e044dc",
18+
"type": "Encounter"
19+
},
20+
"occurrenceDateTime": "2020-07-08T18:30:00.000Z",
21+
"manufacturer": {
22+
"display": "Acme"
23+
},
24+
"lotNumber": "FOO1234",
25+
"expirationDate": "2022-07-31T18:30:00.000Z",
26+
"performer": [
27+
{
28+
"actor": {
29+
"reference": "Practitioner/f9badd80-ab76-11e2-9e96-0800200c9a66",
30+
"type": "Practitioner"
31+
}
32+
}
33+
],
34+
"protocolApplied": [
35+
{
36+
"doseNumberPositiveInt": 2,
37+
"series": "Dose 2"
38+
}
39+
],
40+
"note": [
41+
{
42+
"text": "This is a test immunization note."
43+
}
44+
]
45+
}

0 commit comments

Comments
 (0)