From 0e59665711ecdefce077b1dd5c8e627fa18cb8dd Mon Sep 17 00:00:00 2001 From: StevenXLi Date: Wed, 7 Dec 2022 15:55:53 -0500 Subject: [PATCH] 4312 add support for member identifier parameter return from $member match operation (#4314) * added failing test * implemented solution * added change log * removed special character in change log file name * added check for member identifier of type MB * changed msg code to latest * fixed test for updated msg code Co-authored-by: Steven Li --- .../java/ca/uhn/fhir/rest/api/Constants.java | 2 + ...er-return-from-member-match-operation.yaml | 5 ++ .../r4/MemberMatchR4ResourceProvider.java | 4 +- .../provider/r4/MemberMatcherR4Helper.java | 50 +++++++++++------ .../provider/MemberMatcherR4HelperTest.java | 54 ++++++++++++++++--- 5 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4312-add-support-for-member-identifier-parameter-return-from-member-match-operation.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java index c157847e894..7d1ecf04c9a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java @@ -211,6 +211,8 @@ public class Constants { * $member-match operation */ public static final String PARAM_MEMBER_PATIENT = "MemberPatient"; + public static final String PARAM_MEMBER_IDENTIFIER = "MemberIdentifier"; + public static final String PARAM_OLD_COVERAGE = "OldCoverage"; public static final String PARAM_NEW_COVERAGE = "NewCoverage"; public static final String PARAM_CONSENT = "Consent"; diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4312-add-support-for-member-identifier-parameter-return-from-member-match-operation.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4312-add-support-for-member-identifier-parameter-return-from-member-match-operation.yaml new file mode 100644 index 00000000000..8653e678659 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4312-add-support-for-member-identifier-parameter-return-from-member-match-operation.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 4312 +title: "Previously, when $member-match operation is executed, the parameters that were returned did not include the member identifier as a parameter. +This has now been added, and the consent resource is now updated with this identifier that's being returned." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/MemberMatchR4ResourceProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/MemberMatchR4ResourceProvider.java index 09cd4cc8782..21d286d4eb1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/MemberMatchR4ResourceProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/MemberMatchR4ResourceProvider.java @@ -29,10 +29,10 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.provider.ProviderConstants; +import org.hl7.fhir.r4.model.Consent; import org.hl7.fhir.r4.model.Coverage; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.r4.model.Consent; import javax.servlet.http.HttpServletRequest; import java.util.Optional; @@ -120,7 +120,7 @@ public class MemberMatchR4ResourceProvider { } myMemberMatcherR4Helper.addMemberIdentifierToMemberPatient(theMemberPatient, patient.getIdentifierFirstRep()); - myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient); + myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient, theMemberPatient); return myMemberMatcherR4Helper.buildSuccessReturnParameters(theMemberPatient, theCoverageToLink, theConsent); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/MemberMatcherR4Helper.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/MemberMatcherR4Helper.java index a42eb2ae2f1..8a8d7129f1c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/MemberMatcherR4Helper.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/MemberMatcherR4Helper.java @@ -1,14 +1,15 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.ParametersUtil; import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; @@ -26,16 +27,14 @@ import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.StringType; -import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Nullable; import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.UUID; import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT; +import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER; import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_PATIENT; import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE; @@ -102,13 +101,13 @@ public class MemberMatcherR4Helper { // search by received old coverage id List foundCoverages = findCoverageByCoverageId(theCoverageToMatch); if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) { - return Optional.of( (Coverage) foundCoverages.get(0) ); + return Optional.of((Coverage) foundCoverages.get(0)); } // search by received old coverage identifier foundCoverages = findCoverageByCoverageIdentifier(theCoverageToMatch); if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) { - return Optional.of( (Coverage) foundCoverages.get(0) ); + return Optional.of((Coverage) foundCoverages.get(0)); } return Optional.empty(); @@ -142,9 +141,9 @@ public class MemberMatcherR4Helper { return retVal.getAllResources(); } - public void updateConsentForMemberMatch(Consent theConsent, Patient thePatient) { + public void updateConsentForMemberMatch(Consent theConsent, Patient thePatient, Patient theMemberPatient) { addClientIdAsExtensionToConsentIfAvailable(theConsent); - addIdentifierToConsent(theConsent); + addIdentifierToConsent(theConsent, theMemberPatient); updateConsentPatientAndPerformer(theConsent, thePatient); // save the resource @@ -156,9 +155,29 @@ public class MemberMatcherR4Helper { ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_MEMBER_PATIENT, theMemberPatient); ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_NEW_COVERAGE, theCoverage); ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_CONSENT, theConsent); + ParametersUtil.addParameterToParameters(myFhirContext, parameters, PARAM_MEMBER_IDENTIFIER, getIdentifier(theMemberPatient)); return (Parameters) parameters; } + private Identifier getIdentifier(Patient theMemberPatient) { + List memberIdentifiers = theMemberPatient.getIdentifier(); + if (memberIdentifiers != null && memberIdentifiers.size() > 0) { + for (Identifier memberIdentifier : memberIdentifiers) { + List typeCodings = memberIdentifier.getType().getCoding(); + if (memberIdentifier.getType() != null && typeCodings != null && typeCodings.size() > 0) { + for (Coding typeCoding : typeCodings) { + if (typeCoding.getCode().equals("MB")) { + return memberIdentifier; + } + } + } + } + } + String i18nMessage = myFhirContext.getLocalizer().getMessage( + "operation.member.match.error.beneficiary.without.identifier"); + throw new UnprocessableEntityException(Msg.code(2219) + i18nMessage); + } + public void addMemberIdentifierToMemberPatient(Patient theMemberPatient, Identifier theNewIdentifier) { Coding coding = new Coding() .setSystem(OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM) @@ -170,7 +189,7 @@ public class MemberMatcherR4Helper { .setCoding(Lists.newArrayList(coding)) .setText(OUT_COVERAGE_IDENTIFIER_TEXT); - Identifier newIdentifier = new Identifier() + Identifier newIdentifier = new Identifier() .setUse(Identifier.IdentifierUse.USUAL) .setType(concept) .setSystem(theNewIdentifier.getSystem()) @@ -181,6 +200,7 @@ public class MemberMatcherR4Helper { /** * If there is a client id + * * @param theConsent - the consent to modify */ private void addClientIdAsExtensionToConsentIfAvailable(Consent theConsent) { @@ -208,7 +228,7 @@ public class MemberMatcherR4Helper { } if (theCoverage.getBeneficiaryTarget() != null - && ! theCoverage.getBeneficiaryTarget().getIdentifier().isEmpty()) { + && !theCoverage.getBeneficiaryTarget().getIdentifier().isEmpty()) { return Optional.of(theCoverage.getBeneficiaryTarget()); } @@ -230,7 +250,7 @@ public class MemberMatcherR4Helper { } /** - * Matching by member patient demographics - family name and birthdate only + * Matching by member patient demographics - family name and birthdate only */ public boolean validPatientMember(Patient thePatientFromContract, Patient thePatientToMatch) { if (thePatientFromContract == null || thePatientFromContract.getIdElement() == null) { @@ -254,10 +274,10 @@ public class MemberMatcherR4Helper { } public boolean validConsentDataAccess(Consent theConsent) { - if (theConsent.getPolicy().isEmpty()) { + if (theConsent.getPolicy().isEmpty()) { return false; } - for (Consent.ConsentPolicyComponent policyComponent: theConsent.getPolicy()) { + for (Consent.ConsentPolicyComponent policyComponent : theConsent.getPolicy()) { if (policyComponent.getUri() == null || !validConsentPolicy(policyComponent.getUri())) { return false; } @@ -280,8 +300,8 @@ public class MemberMatcherR4Helper { return false; } - private void addIdentifierToConsent(Consent theConsent) { - String consentId = UUID.randomUUID().toString(); + private void addIdentifierToConsent(Consent theConsent, Patient thePatient) { + String consentId = getIdentifier(thePatient).getValue(); Identifier consentIdentifier = new Identifier().setSystem(CONSENT_IDENTIFIER_CODE_SYSTEM).setValue(consentId); theConsent.addIdentifier(consentIdentifier); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/MemberMatcherR4HelperTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/MemberMatcherR4HelperTest.java index 7fc778c588c..d0730c71f88 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/MemberMatcherR4HelperTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/MemberMatcherR4HelperTest.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.provider.r4.IConsentExtensionProvider; import ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper; @@ -15,6 +16,8 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import com.google.common.collect.Lists; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Consent; import org.hl7.fhir.r4.model.Coverage; import org.hl7.fhir.r4.model.DateType; @@ -44,8 +47,11 @@ import java.util.Optional; import static ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper.CONSENT_IDENTIFIER_CODE_SYSTEM; import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT; +import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER; import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_PATIENT; import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -164,9 +170,14 @@ public class MemberMatcherR4HelperTest { @Test void buildSuccessReturnParameters() { + Identifier identifier = new Identifier(); + CodeableConcept identifierType = new CodeableConcept(); + identifierType.addCoding(new Coding("", "MB", "")); + identifier.setType(identifierType); Patient patient = new Patient(); Coverage coverage = new Coverage(); Consent consent = new Consent(); + patient.addIdentifier(identifier); Parameters result = myHelper.buildSuccessReturnParameters(patient, coverage, consent); @@ -178,8 +189,25 @@ public class MemberMatcherR4HelperTest { assertEquals(PARAM_CONSENT, result.getParameter().get(2).getName()); assertEquals(consent, result.getParameter().get(2).getResource()); + + assertEquals(PARAM_MEMBER_IDENTIFIER, result.getParameter().get(3).getName()); + assertEquals(identifier, result.getParameter().get(3).getValue()); } + @Test + void buildNotSuccessReturnParameters_IncorrectPatientIdentifier() { + Identifier identifier = new Identifier(); + Patient patient = new Patient(); + Coverage coverage = new Coverage(); + Consent consent = new Consent(); + patient.addIdentifier(identifier); + + try { + myHelper.buildSuccessReturnParameters(patient, coverage, consent); + } catch (Exception e) { + assertThat(e.getMessage(), startsWith(Msg.code(2219))); + } + } @Test void addMemberIdentifierToMemberPatient() { @@ -478,9 +506,17 @@ public class MemberMatcherR4HelperTest { return new Consent.ConsentPolicyComponent().setUri(uri + uriAccess); } - private Patient createPatientForMemberMatchUpdate() { + private Patient createPatientForMemberMatchUpdate(boolean addIdentifier) { Patient patient = new Patient(); patient.setId("Patient/RED"); + Identifier identifier = new Identifier(); + if (addIdentifier) { + CodeableConcept identifierType = new CodeableConcept(); + identifierType.addCoding(new Coding("", "MB", "")); + identifier.setType(identifierType); + } + identifier.setValue("RED-Patient"); + patient.addIdentifier(identifier); return patient; } @@ -488,12 +524,14 @@ public class MemberMatcherR4HelperTest { private void verifyConsentUpdatedAfterMemberMatch( Consent theConsent, Patient thePatient, + Patient theMemberPatient, List theSavedExtensions ) { // check consent identifier assertEquals(1, theConsent.getIdentifier().size()); assertEquals(CONSENT_IDENTIFIER_CODE_SYSTEM, theConsent.getIdentifier().get(0).getSystem()); assertNotNull(theConsent.getIdentifier().get(0).getValue()); + assertEquals(theConsent.getIdentifier().get(0).getValue(), theMemberPatient.getIdentifier().get(0).getValue()); // check consent patient info String patientRef = thePatient.getIdElement().toUnqualifiedVersionless().getValue(); @@ -528,12 +566,13 @@ public class MemberMatcherR4HelperTest { // setup Consent consent = getConsent(); consent.addPolicy(constructConsentPolicyComponent("#sensitive")); - Patient patient = createPatientForMemberMatchUpdate(); + Patient patient = createPatientForMemberMatchUpdate(false); + Patient memberPatient = createPatientForMemberMatchUpdate(true); ourLog.setLevel(Level.TRACE); // test - myHelper.updateConsentForMemberMatch(consent, patient); + myHelper.updateConsentForMemberMatch(consent, patient, memberPatient); // verify verify(myAppender, never()) @@ -542,7 +581,7 @@ public class MemberMatcherR4HelperTest { ArgumentCaptor consentCaptor = ArgumentCaptor.forClass(Consent.class); verify(myConsentDao).create(consentCaptor.capture()); Consent saved = consentCaptor.getValue(); - verifyConsentUpdatedAfterMemberMatch(saved, patient, Collections.emptyList()); + verifyConsentUpdatedAfterMemberMatch(saved, patient, memberPatient, Collections.emptyList()); } } @@ -571,7 +610,8 @@ public class MemberMatcherR4HelperTest { Extension ext = new Extension(); ext.setUrl("http://example.com"); ext.setValue(new StringType("value")); - Patient patient = createPatientForMemberMatchUpdate(); + Patient patient = createPatientForMemberMatchUpdate(false); + Patient memberPatient = createPatientForMemberMatchUpdate(true); ourLog.setLevel(Level.TRACE); @@ -580,13 +620,13 @@ public class MemberMatcherR4HelperTest { .thenReturn(Collections.singleton(ext)); // test - myHelper.updateConsentForMemberMatch(consent, patient); + myHelper.updateConsentForMemberMatch(consent, patient, memberPatient); // verify ArgumentCaptor consentCaptor = ArgumentCaptor.forClass(Consent.class); verify(myConsentDao).create(consentCaptor.capture()); Consent saved = consentCaptor.getValue(); - verifyConsentUpdatedAfterMemberMatch(saved, patient, Collections.emptyList()); + verifyConsentUpdatedAfterMemberMatch(saved, patient, memberPatient, Collections.emptyList()); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(ILoggingEvent.class); verify(myAppender).doAppend(eventCaptor.capture());