
001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2023 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.provider.r4; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.model.api.annotation.Description; 025import ca.uhn.fhir.rest.annotation.Operation; 026import ca.uhn.fhir.rest.annotation.OperationParam; 027import ca.uhn.fhir.rest.api.Constants; 028import ca.uhn.fhir.rest.api.server.RequestDetails; 029import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 030import ca.uhn.fhir.rest.server.provider.ProviderConstants; 031import org.hl7.fhir.r4.model.Consent; 032import org.hl7.fhir.r4.model.Coverage; 033import org.hl7.fhir.r4.model.Parameters; 034import org.hl7.fhir.r4.model.Patient; 035 036import java.util.Optional; 037import javax.annotation.Nullable; 038 039public class MemberMatchR4ResourceProvider { 040 041 private final MemberMatcherR4Helper myMemberMatcherR4Helper; 042 private final FhirContext myFhirContext; 043 044 public MemberMatchR4ResourceProvider(FhirContext theFhirContext, MemberMatcherR4Helper theMemberMatcherR4Helper) { 045 myFhirContext = theFhirContext; 046 myMemberMatcherR4Helper = theMemberMatcherR4Helper; 047 } 048 049 /** 050 * /Patient/$member-match operation 051 * Basic implementation matching by coverage id or by coverage identifier. Matching by 052 * Beneficiary (Patient) demographics on family name and birthdate in this version 053 */ 054 @Operation( 055 name = ProviderConstants.OPERATION_MEMBER_MATCH, 056 typeName = "Patient", 057 canonicalUrl = "http://hl7.org/fhir/us/davinci-hrex/OperationDefinition/member-match", 058 idempotent = false, 059 returnParameters = {@OperationParam(name = "MemberIdentifier", typeName = "string")}) 060 public Parameters patientMemberMatch( 061 javax.servlet.http.HttpServletRequest theServletRequest, 062 @Description( 063 shortDefinition = 064 "The target of the operation. Will be returned with Identifier for matched coverage added.") 065 @OperationParam(name = Constants.PARAM_MEMBER_PATIENT, min = 1, max = 1) 066 Patient theMemberPatient, 067 @Description(shortDefinition = "Old coverage information as extracted from beneficiary's card.") 068 @OperationParam(name = Constants.PARAM_OLD_COVERAGE, min = 1, max = 1) 069 Coverage oldCoverage, 070 @Description( 071 shortDefinition = 072 "New Coverage information. Provided as a reference. Optionally returned unmodified.") 073 @OperationParam(name = Constants.PARAM_NEW_COVERAGE, min = 1, max = 1) 074 Coverage newCoverage, 075 @Description( 076 shortDefinition = 077 "Consent information. Consent held by the system seeking the match that grants permission to access the patient information.") 078 @OperationParam(name = Constants.PARAM_CONSENT, min = 1, max = 1) 079 Consent theConsent, 080 RequestDetails theRequestDetails) { 081 return doMemberMatchOperation(theMemberPatient, oldCoverage, newCoverage, theConsent, theRequestDetails); 082 } 083 084 private Parameters doMemberMatchOperation( 085 Patient theMemberPatient, 086 Coverage theCoverageToMatch, 087 Coverage theCoverageToLink, 088 Consent theConsent, 089 RequestDetails theRequestDetails) { 090 091 validateParams(theMemberPatient, theCoverageToMatch, theCoverageToLink, theConsent); 092 093 Optional<Coverage> coverageOpt = 094 myMemberMatcherR4Helper.findMatchingCoverage(theCoverageToMatch, theRequestDetails); 095 if (coverageOpt.isEmpty()) { 096 String i18nMessage = 097 myFhirContext.getLocalizer().getMessage("operation.member.match.error.coverage.not.found"); 098 throw new UnprocessableEntityException(Msg.code(1155) + i18nMessage); 099 } 100 Coverage coverage = coverageOpt.get(); 101 102 Optional<Patient> patientOpt = myMemberMatcherR4Helper.getBeneficiaryPatient(coverage, theRequestDetails); 103 if (patientOpt.isEmpty()) { 104 String i18nMessage = 105 myFhirContext.getLocalizer().getMessage("operation.member.match.error.beneficiary.not.found"); 106 throw new UnprocessableEntityException(Msg.code(1156) + i18nMessage); 107 } 108 109 Patient patient = patientOpt.get(); 110 if (!myMemberMatcherR4Helper.validPatientMember(patient, theMemberPatient, theRequestDetails)) { 111 String i18nMessage = 112 myFhirContext.getLocalizer().getMessage("operation.member.match.error.patient.not.found"); 113 throw new UnprocessableEntityException(Msg.code(2146) + i18nMessage); 114 } 115 116 if (patient.getIdentifier().isEmpty()) { 117 String i18nMessage = myFhirContext 118 .getLocalizer() 119 .getMessage("operation.member.match.error.beneficiary.without.identifier"); 120 throw new UnprocessableEntityException(Msg.code(1157) + i18nMessage); 121 } 122 123 if (!myMemberMatcherR4Helper.validConsentDataAccess(theConsent)) { 124 String i18nMessage = myFhirContext 125 .getLocalizer() 126 .getMessage("operation.member.match.error.consent.release.data.mismatch"); 127 throw new UnprocessableEntityException(Msg.code(2147) + i18nMessage); 128 } 129 130 myMemberMatcherR4Helper.addMemberIdentifierToMemberPatient(theMemberPatient, patient.getIdentifierFirstRep()); 131 myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient, theMemberPatient, theRequestDetails); 132 return myMemberMatcherR4Helper.buildSuccessReturnParameters(theMemberPatient, theCoverageToLink, theConsent); 133 } 134 135 private void validateParams( 136 Patient theMemberPatient, Coverage theOldCoverage, Coverage theNewCoverage, Consent theConsent) { 137 validateParam(theMemberPatient, Constants.PARAM_MEMBER_PATIENT); 138 validateParam(theOldCoverage, Constants.PARAM_OLD_COVERAGE); 139 validateParam(theNewCoverage, Constants.PARAM_NEW_COVERAGE); 140 validateParam(theConsent, Constants.PARAM_CONSENT); 141 validateMemberPatientParam(theMemberPatient); 142 validateConsentParam(theConsent); 143 } 144 145 private void validateParam(@Nullable Object theParam, String theParamName) { 146 if (theParam == null) { 147 String i18nMessage = myFhirContext 148 .getLocalizer() 149 .getMessage("operation.member.match.error.missing.parameter", theParamName); 150 throw new UnprocessableEntityException(Msg.code(1158) + i18nMessage); 151 } 152 } 153 154 private void validateMemberPatientParam(Patient theMemberPatient) { 155 if (theMemberPatient.getName().isEmpty()) { 156 validateParam(null, Constants.PARAM_MEMBER_PATIENT_NAME); 157 } 158 159 validateParam(theMemberPatient.getName().get(0).getFamily(), Constants.PARAM_MEMBER_PATIENT_NAME); 160 validateParam(theMemberPatient.getBirthDate(), Constants.PARAM_MEMBER_PATIENT_BIRTHDATE); 161 } 162 163 private void validateConsentParam(Consent theConsent) { 164 if (theConsent.getPatient().isEmpty()) { 165 validateParam(null, Constants.PARAM_CONSENT_PATIENT_REFERENCE); 166 } 167 if (theConsent.getPerformer().isEmpty()) { 168 validateParam(null, Constants.PARAM_CONSENT_PERFORMER_REFERENCE); 169 } 170 } 171}