Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.

Commit bd453a8

Browse files
authored
Merge pull request #124 from schwzr/risk-principe
Modifications for Risk Principe
2 parents 2533ada + 4e5a6ff commit bd453a8

36 files changed

+2557
-69
lines changed

codex-process-data-transfer/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<parent>
99
<groupId>de.netzwerk-universitaetsmedizin.codex</groupId>
1010
<artifactId>codex-processes-ap1</artifactId>
11-
<version>1.0.0.0-SNAPSHOT</version>
11+
<version>1.1.0.0-SNAPSHOT</version>
1212
</parent>
1313

1414
<properties>

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/ConstantsDataTransfer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public interface ConstantsDataTransfer
9191
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_BAD_PATIENT_REFERENCE = "bad-patient-reference";
9292
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_FTTP_NOT_REACHABLE = "fttp-not-reachable";
9393
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_BLOOMFILTER = "no-dic-pseudonym-for-bloomfilter";
94+
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_LOCAL_PSEUDONYM = "no-dic-pseudonym-for-local-pseudonym";
9495
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_VALIDATION_FAILED = "validation-failed";
9596
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_ECRYPTION_OF_DATA_FOR_CRR_FAILED = "ecryption-of-data-for-crr-failed";
9697
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_UNABLE_TO_STORE_ECRYPTED_DATA = "unable-to-store-ecrypted-data";

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/DataTransferProcessPluginDefinition.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818
public class DataTransferProcessPluginDefinition implements ProcessPluginDefinition
1919
{
20-
public static final String VERSION = "1.0.0.0";
21-
public static final LocalDate DATE = LocalDate.of(2023, 9, 25);
20+
public static final String VERSION = "1.1.0.0";
21+
public static final LocalDate DATE = LocalDate.of(2024, 3, 18);
2222

2323
@Override
2424
public String getName()

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/FttpClient.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@ public interface FttpClient
1818
*/
1919
Optional<String> getDicPseudonym(String bloomFilter);
2020

21+
Optional<String> getDicPseudonymForLocalPseudonym(String localPseudonym);
22+
2123
void testConnection();
2224
}

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/FttpClientFactory.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ public Optional<String> getDicPseudonym(String bloomFilter)
6262
return pseudonym;
6363
}
6464

65+
@Override
66+
public Optional<String> getDicPseudonymForLocalPseudonym(String localPseudonym)
67+
{
68+
Optional<String> pseudonym = sha256(localPseudonym).map(p -> "dic_test/" + p);
69+
70+
logger.warn(
71+
"Returning simulated DIC pseudonym '{}' for local pseudonym '{}', fTTP connection not configured.",
72+
pseudonym.orElseThrow(), localPseudonym);
73+
74+
return pseudonym;
75+
}
76+
6577
private Optional<String> sha256(String original)
6678
{
6779
try
@@ -142,7 +154,7 @@ public void testConnection()
142154
{
143155
logger.info(
144156
"Testing connection to fTTP with {trustStorePath: {}, certificatePath: {}, privateKeyPath: {}, privateKeyPassword: {},"
145-
+ " basicAuthUsername {}, basicAuthPassword {}, serverBase: {}, apiKey: {}, study: {}, target: {}, proxyUrl {}, proxyUsername, proxyPassword {}}",
157+
+ " basicAuthUsername: {}, basicAuthPassword: {}, serverBase: {}, apiKey: {}, study: {}, target: {}, proxyUrl: {}, proxyUsername: {}, proxyPassword: {}}",
146158
trustStorePath, certificatePath, privateKeyPath, privateKeyPassword != null ? "***" : "null",
147159
fttpBasicAuthUsername, fttpBasicAuthPassword != null ? "***" : "null", fttpServerBase,
148160
fttpApiKey != null ? "***" : "null", fttpStudy, fttpTarget, proxyUrl, proxyUsername,

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/FttpClientImpl.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,18 @@ protected Parameters createParametersForPsnWorkflow(String dicSourceAndPseudonym
154154
return p;
155155
}
156156

157+
protected Parameters createParametersForPsnWorkflowLocalPseudonym(String localPseudonym)
158+
{
159+
Parameters p = new Parameters();
160+
p.addParameter("study", fttpStudy);
161+
p.addParameter("original", localPseudonym);
162+
p.addParameter("source", "local");
163+
p.addParameter("target", fttpTarget);
164+
p.addParameter("apikey", fttpApiKey);
165+
166+
return p;
167+
}
168+
157169
@Override
158170
public Optional<String> getDicPseudonym(String bloomFilter)
159171
{
@@ -178,6 +190,30 @@ public Optional<String> getDicPseudonym(String bloomFilter)
178190
}
179191
}
180192

193+
@Override
194+
public Optional<String> getDicPseudonymForLocalPseudonym(String localPseudonym)
195+
{
196+
Objects.requireNonNull(localPseudonym, "localPseudonym");
197+
198+
logger.info("Requesting DIC Pseudonym for local Pseudonym {} ...", localPseudonym);
199+
200+
try
201+
{
202+
IGenericClient client = createGenericClient();
203+
204+
Parameters parameters = client.operation().onServer().named("requestPsnWorkflow")
205+
.withParameters(createParametersForPsnWorkflowLocalPseudonym(localPseudonym))
206+
.accept(Constants.CT_FHIR_XML_NEW).encoded(EncodingEnum.XML).execute();
207+
208+
return getPseudonym(parameters).map(p -> fttpTarget + "/" + p);
209+
}
210+
catch (Exception e)
211+
{
212+
logger.error("Error while retrieving DIC pseudonym: {} - {}", e.getClass().getName(), e.getMessage());
213+
throw new BpmnError(CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_FTTP_NOT_REACHABLE, e.getMessage());
214+
}
215+
}
216+
181217
protected Parameters createParametersForBfWorkflow(String bloomFilter)
182218
{
183219
Parameters p = new Parameters();

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractFhirClient.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,8 @@ public void updatePatient(Patient patient)
653653
Objects.requireNonNull(patient, "patient");
654654

655655
String id = patient.getIdElement().toVersionless().getValue();
656+
// set the patient id to versionless id to workaround a `If-Match`-header bug in hapi fhir client
657+
patient.setId(id);
656658
logger.info("Updating patient {}", id);
657659

658660
try

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/receive/StoreValidationErrorForDts.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import org.slf4j.Logger;
1616
import org.slf4j.LoggerFactory;
1717

18-
import ca.uhn.fhir.context.FhirContext;
18+
import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.logging.DataLogger;
1919
import dev.dsf.bpe.v1.ProcessPluginApi;
2020
import dev.dsf.bpe.v1.activity.AbstractServiceDelegate;
2121
import dev.dsf.bpe.v1.constants.NamingSystems;
@@ -26,12 +26,14 @@ public class StoreValidationErrorForDts extends AbstractServiceDelegate
2626
private static final Logger logger = LoggerFactory.getLogger(StoreValidationErrorForDts.class);
2727

2828
private final String dtsIdentifierValue;
29+
private final DataLogger dataLogger;
2930

30-
public StoreValidationErrorForDts(ProcessPluginApi api, String dtsIdentifierValue)
31+
public StoreValidationErrorForDts(ProcessPluginApi api, String dtsIdentifierValue, DataLogger dataLogger)
3132
{
3233
super(api);
3334

3435
this.dtsIdentifierValue = dtsIdentifierValue;
36+
this.dataLogger = dataLogger;
3537
}
3638

3739
@Override
@@ -40,6 +42,7 @@ public void afterPropertiesSet() throws Exception
4042
super.afterPropertiesSet();
4143

4244
Objects.requireNonNull(dtsIdentifierValue, "dtsIdentifierValue");
45+
Objects.requireNonNull(dataLogger, "dataLogger");
4346
}
4447

4548
@Override
@@ -72,7 +75,7 @@ private IdType createBinaryResource(Binary binary)
7275
}
7376
catch (Exception e)
7477
{
75-
logger.debug("Binary to create {}", FhirContext.forR4().newJsonParser().encodeResourceToString(binary));
78+
dataLogger.logData("Binary to create", binary);
7679
logger.warn("Error while creating Binary resource: " + e.getMessage(), e);
7780

7881
throw new BpmnError(

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/ReadData.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.camunda.bpm.engine.delegate.BpmnError;
2727
import org.camunda.bpm.engine.delegate.DelegateExecution;
28+
import org.hl7.fhir.instance.model.api.IBaseResource;
2829
import org.hl7.fhir.r4.model.Bundle;
2930
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
3031
import org.hl7.fhir.r4.model.Bundle.BundleType;
@@ -69,6 +70,7 @@
6970
import org.hl7.fhir.r4.model.Procedure.ProcedurePerformerComponent;
7071
import org.hl7.fhir.r4.model.Reference;
7172
import org.hl7.fhir.r4.model.Resource;
73+
import org.hl7.fhir.r4.model.Specimen;
7274
import org.hl7.fhir.r4.model.Task;
7375
import org.slf4j.Logger;
7476
import org.slf4j.LoggerFactory;
@@ -176,6 +178,7 @@ protected Bundle toBundle(String pseudonym, Stream<DomainResource> resourcesStre
176178
.collect(Collectors.toMap(r -> r.getIdElement().toUnqualifiedVersionless().getValue(),
177179
r -> "urn:uuid:" + UUID.randomUUID().toString()));
178180

181+
179182
List<DomainResource> resourcesWithTemporaryReferences = fixReferences(resources, resourcesById, uuidsById);
180183
List<BundleEntryComponent> entries = resourcesWithTemporaryReferences.stream().map(r ->
181184
{
@@ -240,12 +243,20 @@ else if (resource instanceof Observation o)
240243
Observation::setEncounter);
241244
fixReferences(o, uuidsById, "Observation.hasMember", Observation::hasHasMember, Observation::getHasMember,
242245
Observation::setHasMember);
246+
fixReference(o, uuidsById, "Observation.specimen", Observation::hasSpecimen, Observation::getSpecimen,
247+
Observation::setSpecimen);
243248
}
244249
else if (resource instanceof Procedure p)
245250
{
246251
fixReference(p, uuidsById, "Procedure.encounter", Procedure::hasEncounter, Procedure::getEncounter,
247252
Procedure::setEncounter);
248253
}
254+
else if (resource instanceof Specimen s)
255+
{
256+
fixReferences(s, uuidsById, "Specimen.parent", Specimen::hasParent, Specimen::getParent,
257+
Specimen::setParent);
258+
}
259+
249260

250261
return resource;
251262
}
@@ -269,6 +280,18 @@ else if (oldReference.hasReference() && oldReference.getReference() == null && o
269280
{
270281
logger.debug("Not removing empty reference at {} with data-absent-reason extension", path);
271282
}
283+
else if (!oldReference.getResource().isEmpty())
284+
{
285+
String internalId = "#" + UUID.randomUUID();
286+
Reference fixedReference = new Reference(internalId);
287+
IBaseResource oldContainedResource = clean((DomainResource) oldReference.getResource());
288+
oldContainedResource.setId(internalId);
289+
fixedReference.setResource(oldContainedResource);
290+
setReference.apply(resource, fixedReference);
291+
logger.debug(
292+
"Replacing reference to contained resource at {} from resource {} with bundle temporary id in transport bundle",
293+
path, getAbsoluteId(resource).getValue());
294+
}
272295
else
273296
{
274297
logger.warn("Removing reference at {} from resource {} in transport bundle", path,
@@ -307,6 +330,18 @@ else if (oldReference.hasReference() && oldReference.getReference() == null
307330
logger.debug("Not removing empty reference at {}[{}] with data-absent-reason extension", path, i);
308331
fixedReferences.add(oldReference);
309332
}
333+
else if (!oldReference.getResource().isEmpty())
334+
{
335+
String internalId = "#" + UUID.randomUUID();
336+
Reference fixedReference = new Reference(internalId);
337+
IBaseResource oldContainedResource = clean((DomainResource) oldReference.getResource());
338+
oldContainedResource.setId(internalId);
339+
fixedReference.setResource(oldContainedResource);
340+
fixedReferences.add(fixedReference);
341+
logger.debug(
342+
"Replacing reference to contained resource at {}[{}] from resource {} with bundle temporary id in transport bundle",
343+
path, i, getAbsoluteId(resource).getValue());
344+
}
310345
else
311346
{
312347
logger.warn("Removing reference at {}[{}] from resource {} in transport bundle", path, i,
@@ -342,9 +377,10 @@ private <R extends DomainResource, C> R fixReferenceFromComponents(R resource, M
342377
i, getAbsoluteId(resource).getValue());
343378
setReference.apply(component, new Reference(uuidsById.get(oldReference.getReference())));
344379
}
345-
else if (oldReference.hasReference() && oldReference.getReference() == null
380+
else if ((oldReference.hasReference() && oldReference.getReference() == null
346381
&& oldReference.getReferenceElement_()
347382
.hasExtension("http://hl7.org/fhir/StructureDefinition/data-absent-reason"))
383+
|| oldReference.hasExtension("http://hl7.org/fhir/StructureDefinition/data-absent-reason"))
348384
{
349385
logger.debug("Not removing empty reference at " + path + " with data-absent-reason extension", i);
350386
}
@@ -645,7 +681,6 @@ else if (resource instanceof Observation o)
645681
cleanUnsupportedReferences(o, "Observation.focus", Observation::hasFocus, Observation::setFocus);
646682
cleanUnsupportedReferences(o, "Observation.performer", Observation::hasPerformer,
647683
Observation::setPerformer);
648-
cleanUnsupportedReference(o, "Observation.specimen", Observation::hasSpecimen, Observation::setSpecimen);
649684
cleanUnsupportedReference(o, "Observation.device", Observation::hasDevice, Observation::setDevice);
650685
cleanUnsupportedReferences(o, "Observation.derivedFrom", Observation::hasDerivedFrom,
651686
Observation::setDerivedFrom);
@@ -674,6 +709,20 @@ else if (resource instanceof Procedure p)
674709
cleanUnsupportedReferences(p, "Procedure.usedReference", Procedure::hasUsedReference,
675710
Procedure::setUsedReference);
676711
}
712+
else if (resource instanceof Specimen s)
713+
{
714+
cleanUnsupportedReferences(s, "Specimen.request", Specimen::hasRequest, Specimen::setRequest);
715+
cleanUnsupportedReferenceFromComponent(s, "Specimen.collection.collector", Specimen::hasCollection,
716+
Specimen::getCollection, Specimen.SpecimenCollectionComponent::hasCollector,
717+
Specimen.SpecimenCollectionComponent::setCollector);
718+
cleanUnsupportedReferencesFromComponents(s, "Specimen.processing[{}].additive", Specimen::hasProcessing,
719+
Specimen::getProcessing, Specimen.SpecimenProcessingComponent::hasAdditive,
720+
Specimen.SpecimenProcessingComponent::setAdditive);
721+
cleanUnsupportedReferenceFromComponents(s, "Specimen.container[{}].additiveReference",
722+
Specimen::hasContainer, Specimen::getContainer,
723+
Specimen.SpecimenContainerComponent::hasAdditiveReference,
724+
Specimen.SpecimenContainerComponent::setAdditive);
725+
}
677726
else
678727
throw new RuntimeException("Resource of type " + resource.getResourceType().name() + " not supported");
679728
}
@@ -741,10 +790,21 @@ else if (resource instanceof Procedure p)
741790
p.setIdentifier(Collections.emptyList());
742791
p.setSubject(patientRef);
743792
}
793+
else if (resource instanceof Specimen s)
794+
{
795+
s.setIdentifier(Collections.emptyList());
796+
s.setAccessionIdentifier(null);
797+
s.setSubject(patientRef);
798+
}
744799
else
745800
throw new RuntimeException("Resource of type " + resource.getResourceType().name() + " not supported");
746801
}
747802

803+
if (resource instanceof DomainResource d)
804+
d.getContained().forEach(r -> setSubjectOrIdentifier(r, pseudonym));
805+
else
806+
throw new RuntimeException("Resource of type " + resource.getResourceType().name() + " not supported");
807+
748808
return resource;
749809
}
750810

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/ResolvePsn.java

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_PATIENT_REFERENCE;
44
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_BLOOMFILTER;
5+
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_LOCAL_PSEUDONYM;
56
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_PATIENT_NOT_FOUND;
67
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.IDENTIFIER_NUM_CODEX_DIC_PSEUDONYM_TYPE_CODE;
78
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.IDENTIFIER_NUM_CODEX_DIC_PSEUDONYM_TYPE_SYSTEM;
89
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_BLOOM_FILTER;
910
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM;
11+
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.RFC_4122_SYSTEM;
1012

1113
import java.util.Objects;
1214
import java.util.Optional;
@@ -94,8 +96,33 @@ private Optional<String> getPseudonym(Patient patient)
9496

9597
private String resolvePseudonymAndUpdatePatient(Patient patient)
9698
{
97-
String bloomFilter = getBloomFilter(patient);
98-
String pseudonym = resolveBloomFilter(bloomFilter);
99+
String pseudonym;
100+
// first try to find a bloom filter
101+
Optional<String> bloomFilter = getBloomFilter(patient);
102+
if (bloomFilter.isPresent())
103+
{
104+
pseudonym = resolveBloomFilter(bloomFilter.get());
105+
}
106+
else
107+
{
108+
// otherwise try to find a local pseudonym
109+
// --> no record linkage
110+
logger.info(
111+
"No bloom filter present for patient {}. Try to use the local pseudonym for data transfer without record linkage",
112+
patient.getIdElement().getValue());
113+
Optional<String> localPseudonym = getLocalPseudonym(patient);
114+
if (localPseudonym.isPresent())
115+
{
116+
pseudonym = resolveLocalPseudonym(localPseudonym.get());
117+
}
118+
else
119+
{
120+
logger.info("No local pseudonym present for patient {}. Aborted", patient.getIdElement().getValue());
121+
throw new RuntimeException("Could not find pseudonym");
122+
}
123+
124+
125+
}
99126

100127
patient.getIdentifier().removeIf(i -> NAMING_SYSTEM_NUM_CODEX_BLOOM_FILTER.equals(i.getSystem()));
101128
patient.addIdentifier().setSystem(NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM).setValue(pseudonym).getType()
@@ -107,12 +134,18 @@ private String resolvePseudonymAndUpdatePatient(Patient patient)
107134
return pseudonym;
108135
}
109136

110-
private String getBloomFilter(Patient patient)
137+
private Optional<String> getBloomFilter(Patient patient)
111138
{
112139
return patient.getIdentifier().stream().filter(Identifier::hasSystem)
113140
.filter(i -> NAMING_SYSTEM_NUM_CODEX_BLOOM_FILTER.equals(i.getSystem())).filter(Identifier::hasValue)
114-
.findFirst().map(Identifier::getValue).orElseThrow(() -> new RuntimeException(
115-
"No bloom filter present in patient " + patient.getIdElement().getValue()));
141+
.findFirst().map(Identifier::getValue);
142+
}
143+
144+
private Optional<String> getLocalPseudonym(Patient patient)
145+
{
146+
return patient.getIdentifier().stream().filter(Identifier::hasSystem)
147+
.filter(i -> RFC_4122_SYSTEM.equals(i.getSystem())).filter(Identifier::hasValue).findFirst()
148+
.map(Identifier::getValue);
116149
}
117150

118151
private String resolveBloomFilter(String bloomFilter)
@@ -123,6 +156,15 @@ private String resolveBloomFilter(String bloomFilter)
123156
"Unable to get DIC pseudonym for given BloomFilter"));
124157
}
125158

159+
private String resolveLocalPseudonym(String localPseudonym)
160+
{
161+
return fttpClientFactory.getFttpClient().getDicPseudonymForLocalPseudonym(localPseudonym)
162+
.orElseThrow(() -> new BpmnError(
163+
CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_LOCAL_PSEUDONYM,
164+
"Unable to get DIC pseudonym for given localPseudonym"));
165+
}
166+
167+
126168
private void updatePatient(Patient patient)
127169
{
128170
dataStoreClientFactory.getDataStoreClient().getFhirClient().updatePatient(patient);

0 commit comments

Comments
 (0)