input) {
+ var procedureId = AnalyzerUtils.getRequiredId(input, "id");
+
+ if (procedureId.isEmpty()) {
+ return "";
+ }
+
+ if (
+ permissionEvaluator.hasPermission(
+ SecurityContextHolder.getContext().getAuthentication(),
+ procedureId.get(),
+ Procedure.class.getSimpleName(),
+ PermissionType.READ
+ )
+ ) {
+ return mtbService.getProtocol(
+ therapieplanServiceFactory
+ .currentUsableInstance()
+ .findReferencedMtbs(procedureId.get())
+ );
+ }
+
+ return "";
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/config/PluginConfiguration.java b/src/main/java/dev/dnpm/config/PluginConfiguration.java
new file mode 100644
index 0000000..6b20009
--- /dev/null
+++ b/src/main/java/dev/dnpm/config/PluginConfiguration.java
@@ -0,0 +1,88 @@
+package dev.dnpm.config;
+
+import dev.dnpm.database.SettingsRepository;
+import dev.dnpm.services.*;
+import dev.dnpm.services.consent.ConsentManagerServiceFactory;
+import dev.dnpm.services.molekulargenetik.MolekulargenetikFormService;
+import dev.dnpm.services.molekulargenetik.OsMolekulargenetikFormService;
+import dev.dnpm.services.mtb.DefaultMtbService;
+import dev.dnpm.services.mtb.MtbService;
+import dev.dnpm.services.strahlentherapie.DefaultStrahlentherapieService;
+import dev.dnpm.services.strahlentherapie.StrahlentherapieService;
+import dev.dnpm.services.systemtherapie.DefaultSystemtherapieService;
+import dev.dnpm.services.systemtherapie.SystemtherapieService;
+import dev.dnpm.services.therapieplan.TherapieplanServiceFactory;
+import de.itc.onkostar.api.IOnkostarApi;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+import javax.sql.DataSource;
+
+/**
+ * Dynamische Konfiguration des Plugins basierend auf Onkostar-Einstellungen
+ *
+ * @since 0.0.2
+ */
+@Configuration
+@ComponentScan(basePackages = { "dev.dnpm.analyzer", "dev.dnpm.security" })
+@EnableJpaRepositories(basePackages = "dev.dnpm.database")
+public class PluginConfiguration {
+
+ @Bean
+ public FormService formService(final DataSource dataSource) {
+ return new DefaultFormService(dataSource);
+ }
+
+ @Bean
+ public StudienService studienService(final DataSource dataSource) {
+ return new DefaultStudienService(dataSource);
+ }
+
+ @Bean
+ public SettingsService settingsService(final SettingsRepository settingsRepository) {
+ return new SettingsService(settingsRepository);
+ }
+
+ @Bean
+ public MtbService mtbService(final IOnkostarApi onkostarApi) {
+ return new DefaultMtbService(onkostarApi);
+ }
+
+ @Bean
+ public SystemtherapieService systemtherapieService(
+ final IOnkostarApi onkostarApi,
+ final SettingsService settingsService
+ ) {
+ return new DefaultSystemtherapieService(onkostarApi, settingsService);
+ }
+
+ @Bean
+ public StrahlentherapieService strahlentherapieService(
+ final IOnkostarApi onkostarApi,
+ final SettingsService settingsService
+ ) {
+ return new DefaultStrahlentherapieService(onkostarApi, settingsService);
+ }
+
+ @Bean
+ public ConsentManagerServiceFactory consentManagerServiceFactory(final IOnkostarApi onkostarApi) {
+ return new ConsentManagerServiceFactory(onkostarApi);
+ }
+
+ @Bean
+ public TherapieplanServiceFactory therapieplanServiceFactory(
+ final IOnkostarApi onkostarApi,
+ final SettingsService settingsService,
+ final FormService formService
+ ) {
+ return new TherapieplanServiceFactory(onkostarApi, settingsService, formService);
+ }
+
+ @Bean
+ public MolekulargenetikFormService molekulargenetikFormService() {
+ return new OsMolekulargenetikFormService();
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/database/ReadOnlyRepository.java b/src/main/java/dev/dnpm/database/ReadOnlyRepository.java
new file mode 100644
index 0000000..8f20f6b
--- /dev/null
+++ b/src/main/java/dev/dnpm/database/ReadOnlyRepository.java
@@ -0,0 +1,22 @@
+package dev.dnpm.database;
+
+import org.springframework.data.repository.NoRepositoryBean;
+import org.springframework.data.repository.Repository;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Basis-Repository for ReadOnly Spring-Data-JPA Repositories
+ * Entity-Klassen müssen in Package de.itc.db.dnpm liegen
+ * @param Typ des Entities
+ * @param Typ der ID
+ */
+@NoRepositoryBean
+public interface ReadOnlyRepository extends Repository {
+
+ T findById(ID id);
+
+ List findAll();
+
+}
diff --git a/src/main/java/dev/dnpm/database/SettingsRepository.java b/src/main/java/dev/dnpm/database/SettingsRepository.java
new file mode 100644
index 0000000..e802b1a
--- /dev/null
+++ b/src/main/java/dev/dnpm/database/SettingsRepository.java
@@ -0,0 +1,14 @@
+package dev.dnpm.database;
+
+import de.itc.db.dnpm.Setting;
+import org.springframework.stereotype.Repository;
+
+/**
+ * Spring Data JPA Repository zum Lesen von Einstellungen
+ */
+@Repository("dnpmSettingRepository")
+public interface SettingsRepository extends ReadOnlyRepository {
+
+ Setting findByName(String name);
+
+}
diff --git a/src/main/java/dev/dnpm/dto/EcogStatusWithDate.java b/src/main/java/dev/dnpm/dto/EcogStatusWithDate.java
new file mode 100644
index 0000000..8b3140d
--- /dev/null
+++ b/src/main/java/dev/dnpm/dto/EcogStatusWithDate.java
@@ -0,0 +1,44 @@
+package dev.dnpm.dto;
+
+import org.springframework.util.Assert;
+
+import java.util.Date;
+
+/**
+ * Datenklasse zum Abbilden des ECOG-Status und Datum
+ */
+public class EcogStatusWithDate {
+ private final Date date;
+ private final String status;
+
+ public EcogStatusWithDate(Date date, String status) {
+ Assert.notNull(date, "Date cannot be null");
+ Assert.hasText(status, "Status cannot be empty String");
+ Assert.isTrue(isValidEcogCode(status), "Not a valid ADT.LeistungszustandECOG code");
+ this.date = date;
+ this.status = status;
+ }
+
+ private boolean isValidEcogCode(String status) {
+ switch (status) {
+ case "0":
+ case "1":
+ case "2":
+ case "3":
+ case "4":
+ case "5":
+ case "U":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+}
diff --git a/src/main/java/dev/dnpm/dto/Studie.java b/src/main/java/dev/dnpm/dto/Studie.java
new file mode 100644
index 0000000..2461788
--- /dev/null
+++ b/src/main/java/dev/dnpm/dto/Studie.java
@@ -0,0 +1,74 @@
+package dev.dnpm.dto;
+
+public class Studie {
+ private final String kategorieName;
+ private final String code;
+ private final String studiennummer;
+ private final String shortDesc;
+ private final String description;
+ private final int version;
+
+ private final boolean active;
+
+ public Studie(final String kategorieName, final int version, final String code, final String studiennummer, final String shortDesc, final String description, final boolean active) {
+ this.kategorieName = kategorieName;
+ this.version = version;
+ this.code = code;
+ this.studiennummer = studiennummer;
+ this.shortDesc = shortDesc;
+ this.description = description;
+ this.active = active;
+ }
+
+ public String getKategorieName() {
+ return kategorieName;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getStudiennummer() {
+ return studiennummer;
+ }
+
+ public String getShortDesc() {
+ return shortDesc;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ public Type getType() {
+ if (this.hasNctNumber()) {
+ return Type.NCT;
+ } else if (this.hasEudraCtNumber()) {
+ return Type.EUDRA_CT;
+ } else {
+ return Type.UNKNOWN;
+ }
+ }
+
+ private boolean hasNctNumber() {
+ return null != studiennummer && studiennummer.toLowerCase().startsWith("nct");
+ }
+
+ private boolean hasEudraCtNumber() {
+ return null != studiennummer && studiennummer.matches("\\d{4}-\\d{6}-\\d{2}");
+ }
+
+ public enum Type {
+ NCT,
+ EUDRA_CT,
+ UNKNOWN
+ }
+}
diff --git a/src/main/java/dev/dnpm/dto/Variant.java b/src/main/java/dev/dnpm/dto/Variant.java
new file mode 100644
index 0000000..a7f75cd
--- /dev/null
+++ b/src/main/java/dev/dnpm/dto/Variant.java
@@ -0,0 +1,110 @@
+package dev.dnpm.dto;
+
+import de.itc.onkostar.api.Procedure;
+
+import java.util.Optional;
+
+/**
+ * Ein Auszug der Variante aus dem NGS-Bericht zur Übertragung an das Frontend zur Auswahl der stützenden molekularen Alteration
+ *
+ * @since 0.2.0
+ */
+public class Variant {
+ private final Integer id;
+
+ private final String ergebnis;
+
+ private final String gen;
+
+ private final String exon;
+
+ private final String pathogenitaetsklasse;
+
+ private Variant(
+ final int id,
+ final String ergebnis,
+ final String gen,
+ final String exon,
+ final String pathogenitaetsklasse
+ ) {
+ this.id = id;
+ this.ergebnis = ergebnis;
+ this.gen = gen;
+ this.exon = exon;
+ this.pathogenitaetsklasse = pathogenitaetsklasse;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getErgebnis() {
+ return ergebnis;
+ }
+
+ public String getGen() {
+ return gen;
+ }
+
+ public String getExon() {
+ return exon;
+ }
+
+ public String getPathogenitaetsklasse() {
+ return pathogenitaetsklasse;
+ }
+
+ /**
+ * Erstellt ein Optional einer Variante aus einer Prozedur
+ * @param procedure Die zu verwendende Prozedur
+ * @return Das Optional, wenn die Prozedur verwendet werden kann, ansonsten ein leeres Optional
+ */
+ public static Optional fromProcedure(Procedure procedure) {
+ if (!"OS.Molekulargenetische Untersuchung".equals(procedure.getFormName())) {
+ return Optional.empty();
+ }
+
+ var ergebnis = procedure.getValue("Ergebnis");
+ var gene = procedure.getValue("Untersucht");
+ var exon = procedure.getValue("ExonInt");
+ var pathogenitaetsklasse = procedure.getValue("Pathogenitaetsklasse");
+
+ if (null == gene) {
+ return Optional.empty();
+ }
+
+ if (ergebnis.getString().equals("P")) {
+ return Optional.of(
+ new Variant(
+ procedure.getId(),
+ "Einfache Variante (Mutation)",
+ gene.getString().isBlank() ? "-" : gene.getString(),
+ null == exon || exon.getString().isBlank() ? "-" : exon.getString(),
+ null == pathogenitaetsklasse || pathogenitaetsklasse.getString().isBlank() ? "-" : pathogenitaetsklasse.getString()
+ )
+ );
+ } else if (ergebnis.getString().equals("CNV")) {
+ return Optional.of(
+ new Variant(
+ procedure.getId(),
+ "Copy Number Variation (CNV)",
+ gene.getString().isBlank() ? "-" : gene.getString(),
+ null == exon || exon.getString().isBlank() ? "-" : exon.getString(),
+ null == pathogenitaetsklasse || pathogenitaetsklasse.getString().isBlank() ? "-" : pathogenitaetsklasse.getString()
+ )
+ );
+ } else if (ergebnis.getString().equals("F")) {
+ return Optional.of(
+ new Variant(
+ procedure.getId(),
+ "Fusion (Translokation Inversion Insertion)",
+ gene.getString().isBlank() ? "-" : gene.getString(),
+ null == exon || exon.getString().isBlank() ? "-" : exon.getString(),
+ null == pathogenitaetsklasse || pathogenitaetsklasse.getString().isBlank() ? "-" : pathogenitaetsklasse.getString()
+ )
+ );
+ } else {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/src/main/java/dev/dnpm/exceptions/FormException.java b/src/main/java/dev/dnpm/exceptions/FormException.java
new file mode 100644
index 0000000..9ce0176
--- /dev/null
+++ b/src/main/java/dev/dnpm/exceptions/FormException.java
@@ -0,0 +1,9 @@
+package dev.dnpm.exceptions;
+
+public class FormException extends RuntimeException {
+
+ public FormException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/security/AbstractDelegatedPermissionEvaluator.java b/src/main/java/dev/dnpm/security/AbstractDelegatedPermissionEvaluator.java
new file mode 100644
index 0000000..11bdcb6
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/AbstractDelegatedPermissionEvaluator.java
@@ -0,0 +1,23 @@
+package dev.dnpm.security;
+
+import de.itc.onkostar.api.IOnkostarApi;
+import de.itc.onkostar.api.Patient;
+import de.itc.onkostar.api.Procedure;
+import org.springframework.security.access.PermissionEvaluator;
+
+public abstract class AbstractDelegatedPermissionEvaluator implements PermissionEvaluator {
+
+ protected static final String PATIENT = Patient.class.getSimpleName();
+
+ protected static final String PROCEDURE = Procedure.class.getSimpleName();
+
+ protected final IOnkostarApi onkostarApi;
+
+ protected final SecurityService securityService;
+
+ protected AbstractDelegatedPermissionEvaluator(final IOnkostarApi onkostarApi, final SecurityService securityService) {
+ this.onkostarApi = onkostarApi;
+ this.securityService = securityService;
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/security/DelegatingDataBasedPermissionEvaluator.java b/src/main/java/dev/dnpm/security/DelegatingDataBasedPermissionEvaluator.java
new file mode 100644
index 0000000..09c212b
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/DelegatingDataBasedPermissionEvaluator.java
@@ -0,0 +1,56 @@
+package dev.dnpm.security;
+
+import org.springframework.security.access.PermissionEvaluator;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * PermissionEvaluator zur Gesamtprüfung der Zugriffsberechtigung.
+ * Die konkrete Berechtigungsprüfung wird an die nachgelagerten PermissionEvaluatoren delegiert,
+ * welche jeweils einzeln dem Zugriff zustimmen müssen.
+ */
+@Component
+public class DelegatingDataBasedPermissionEvaluator implements PermissionEvaluator {
+
+ private final List permissionEvaluators;
+
+ public DelegatingDataBasedPermissionEvaluator(final List permissionEvaluators) {
+ this.permissionEvaluators = permissionEvaluators;
+ }
+
+ /**
+ * Auswertung der Zugriffsberechtigung für authentifizierten Benutzer auf Zielobjekt mit angeforderter Berechtigung.
+ * Hierbei wird die Berechtigungsprüfung an alle nachgelagerten PermissionEvaluatoren delegiert.
+ * Alle müssen dem Zugriff zustimmen.
+ *
+ * @param authentication Das Authentication Objekt
+ * @param targetObject Das Zielobjekt
+ * @param permissionType Die angeforderte Berechtigung
+ * @return Gibt true zurück, wenn der Benutzer die Berechtigung hat
+ */
+ @Override
+ public boolean hasPermission(Authentication authentication, Object targetObject, Object permissionType) {
+ return permissionEvaluators.stream()
+ .allMatch(permissionEvaluator -> permissionEvaluator.hasPermission(authentication, targetObject, permissionType));
+ }
+
+ /**
+ * Auswertung anhand der ID und des Namens des Zielobjekts.
+ * Hierbei wird die Berechtigungsprüfung an alle nachgelagerten PermissionEvaluatoren delegiert.
+ * Alle müssen dem Zugriff zustimmen.
+ *
+ * @param authentication Authentication-Object
+ * @param targetId ID des Objekts
+ * @param targetType Name der Zielobjektklasse
+ * @param permissionType Die angeforderte Berechtigung
+ * @return Gibt true zurück, wenn der Benutzer die Berechtigung hat
+ */
+ @Override
+ public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permissionType) {
+ return permissionEvaluators.stream()
+ .allMatch(permissionEvaluator -> permissionEvaluator.hasPermission(authentication, targetId, targetType, permissionType));
+ }
+}
diff --git a/src/main/java/dev/dnpm/security/FormBasedPermissionEvaluator.java b/src/main/java/dev/dnpm/security/FormBasedPermissionEvaluator.java
new file mode 100644
index 0000000..64f6833
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/FormBasedPermissionEvaluator.java
@@ -0,0 +1,59 @@
+package dev.dnpm.security;
+
+import de.itc.onkostar.api.IOnkostarApi;
+import de.itc.onkostar.api.Procedure;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+
+/**
+ * Permission-Evaluator zur Auswertung der Berechtigung auf Objekte aufgrund der Formularberechtigung
+ */
+@Component
+public class FormBasedPermissionEvaluator extends AbstractDelegatedPermissionEvaluator {
+
+ public FormBasedPermissionEvaluator(final IOnkostarApi onkostarApi, final SecurityService securityService) {
+ super(onkostarApi, securityService);
+ }
+
+ /**
+ * Auswertung der Zugriffsberechtigung für authentifizierten Benutzer auf Zielobjekt mit angeforderter Berechtigung.
+ * Zugriff auf Objekte vom Typ "Patient" wird immer gewährt.
+ *
+ * @param authentication Das Authentication Objekt
+ * @param targetObject Das Zielobjekt
+ * @param permissionType Die angeforderte Berechtigung
+ * @return Gibt true zurück, wenn der Benutzer die Berechtigung hat
+ */
+ @Override
+ public boolean hasPermission(Authentication authentication, Object targetObject, Object permissionType) {
+ if (permissionType instanceof PermissionType && targetObject instanceof Procedure) {
+ return this.securityService.getFormNamesForPermission(authentication, (PermissionType)permissionType)
+ .contains(((Procedure)targetObject).getFormName());
+ }
+ return true;
+ }
+
+ /**
+ * Auswertung anhand der ID und des Namens des Zielobjekts.
+ * Zugriff auf Objekte vom Typ "Patient" wird immer gewährt.
+ *
+ * @param authentication Authentication-Object
+ * @param targetId ID des Objekts
+ * @param targetType Name der Zielobjektklasse
+ * @param permissionType Die angeforderte Berechtigung
+ * @return Gibt true zurück, wenn der Benutzer die Berechtigung hat
+ */
+ @Override
+ public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permissionType) {
+ if (permissionType instanceof PermissionType && targetId instanceof Integer && PROCEDURE.equals(targetType)) {
+ var procedure = this.onkostarApi.getProcedure((int)targetId);
+ if (null != procedure) {
+ return this.securityService.getFormNamesForPermission(authentication, (PermissionType) permissionType).contains(procedure.getFormName());
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/security/FormBasedSecurityAspects.java b/src/main/java/dev/dnpm/security/FormBasedSecurityAspects.java
new file mode 100644
index 0000000..eb80d47
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/FormBasedSecurityAspects.java
@@ -0,0 +1,51 @@
+package dev.dnpm.security;
+
+import de.itc.onkostar.api.Procedure;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.Arrays;
+
+// TODO Disabled for now - check bytecode reported incompatibility for older OS installations
+//@Component
+@Aspect
+public class FormBasedSecurityAspects {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ private final FormBasedPermissionEvaluator permissionEvaluator;
+
+ public FormBasedSecurityAspects(
+ final FormBasedPermissionEvaluator permissionEvaluator
+ ) {
+ this.permissionEvaluator = permissionEvaluator;
+ }
+
+ @AfterReturning(value = "@annotation(dev.dnpm.security.FormSecuredResult)", returning = "procedure")
+ public void afterProcedureFormBased(Procedure procedure) {
+ if (
+ null != procedure
+ && ! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), procedure, PermissionType.READ_WRITE)
+ ) {
+ logger.warn("Rückgabe von Prozedur blockiert: {}", procedure.getId());
+ throw new IllegalSecuredObjectAccessException();
+ }
+ }
+
+ @Before(value = "@annotation(dev.dnpm.security.FormSecured)")
+ public void beforeProcedureFormBased(JoinPoint jp) {
+ Arrays.stream(jp.getArgs())
+ .filter(arg -> arg instanceof Procedure)
+ .forEach(procedure -> {
+ if (! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), procedure, PermissionType.READ_WRITE)) {
+ logger.warn("Zugriff auf Prozedur blockiert: {}", ((Procedure)procedure).getId());
+ throw new IllegalSecuredObjectAccessException();
+ }
+ });
+ }
+}
diff --git a/src/main/java/dev/dnpm/security/FormSecured.java b/src/main/java/dev/dnpm/security/FormSecured.java
new file mode 100644
index 0000000..0ea277e
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/FormSecured.java
@@ -0,0 +1,14 @@
+package dev.dnpm.security;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface FormSecured {
+
+ PermissionType value() default PermissionType.READ_WRITE;
+
+}
diff --git a/src/main/java/dev/dnpm/security/FormSecuredResult.java b/src/main/java/dev/dnpm/security/FormSecuredResult.java
new file mode 100644
index 0000000..64f9bce
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/FormSecuredResult.java
@@ -0,0 +1,14 @@
+package dev.dnpm.security;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface FormSecuredResult {
+
+ PermissionType value() default PermissionType.READ_WRITE;
+
+}
diff --git a/src/main/java/dev/dnpm/security/IllegalSecuredObjectAccessException.java b/src/main/java/dev/dnpm/security/IllegalSecuredObjectAccessException.java
new file mode 100644
index 0000000..69cd456
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/IllegalSecuredObjectAccessException.java
@@ -0,0 +1,13 @@
+package dev.dnpm.security;
+
+public class IllegalSecuredObjectAccessException extends RuntimeException {
+
+ public IllegalSecuredObjectAccessException() {
+ super();
+ }
+
+ public IllegalSecuredObjectAccessException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/security/PermissionType.java b/src/main/java/dev/dnpm/security/PermissionType.java
new file mode 100644
index 0000000..da3c471
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/PermissionType.java
@@ -0,0 +1,6 @@
+package dev.dnpm.security;
+
+public enum PermissionType {
+ READ,
+ READ_WRITE
+}
diff --git a/src/main/java/dev/dnpm/security/PersonPoolBasedPermissionEvaluator.java b/src/main/java/dev/dnpm/security/PersonPoolBasedPermissionEvaluator.java
new file mode 100644
index 0000000..293614b
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/PersonPoolBasedPermissionEvaluator.java
@@ -0,0 +1,81 @@
+package dev.dnpm.security;
+
+import de.itc.onkostar.api.IOnkostarApi;
+import de.itc.onkostar.api.Patient;
+import de.itc.onkostar.api.Procedure;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+
+/**
+ * Permission-Evaluator zur Auswertung der Berechtigung auf Objekte aufgrund der Personenstammberechtigung
+ */
+@Component
+public class PersonPoolBasedPermissionEvaluator extends AbstractDelegatedPermissionEvaluator {
+
+ public PersonPoolBasedPermissionEvaluator(final IOnkostarApi onkostarApi, final SecurityService securityService) {
+ super(onkostarApi, securityService);
+ }
+
+ /**
+ * Auswertung der Zugriffsberechtigung für authentifizierten Benutzer auf Zielobjekt mit angeforderter Berechtigung.
+ * @param authentication Das Authentication Objekt
+ * @param targetObject Das Zielobjekt
+ * @param permissionType Die angeforderte Berechtigung
+ * @return Gibt true zurück, wenn der Benutzer die Berechtigung hat
+ */
+ @Override
+ public boolean hasPermission(Authentication authentication, Object targetObject, Object permissionType) {
+ if (permissionType instanceof PermissionType) {
+ if (targetObject instanceof Patient) {
+ return this.securityService.getPersonPoolIdsForPermission(authentication, (PermissionType)permissionType)
+ .contains(((Patient)targetObject).getPersonPoolCode());
+ } else if (targetObject instanceof Procedure) {
+ return this.securityService.getPersonPoolIdsForPermission(authentication, (PermissionType)permissionType)
+ .contains(((Procedure)targetObject).getPatient().getPersonPoolCode());
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Auswertung anhand der ID und des Namens des Zielobjekts.
+ * @param authentication Authentication-Object
+ * @param targetId ID des Objekts
+ * @param targetType Name der Zielobjektklasse
+ * @param permissionType Die angeforderte Berechtigung
+ * @return Gibt true zurück, wenn der Benutzer die Berechtigung hat
+ */
+ @Override
+ public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permissionType) {
+ if (targetId instanceof Integer && permissionType instanceof PermissionType) {
+ var personPoolCode = getPersonPoolCode((int)targetId, targetType);
+ if (null != personPoolCode) {
+ return this.securityService.getPersonPoolIdsForPermission(authentication, (PermissionType) permissionType).contains(personPoolCode);
+ }
+ }
+ return false;
+ }
+
+ private String getPersonPoolCode(int id, String type) {
+ Patient patient = null;
+
+ if (PATIENT.equals(type)) {
+ patient = onkostarApi.getPatient(id);
+ } else if (PROCEDURE.equals(type)) {
+ var procedure = onkostarApi.getProcedure(id);
+ if (null != procedure) {
+ patient = procedure.getPatient();
+ }
+ }
+
+ if (null != patient) {
+ return patient.getPersonPoolCode();
+ }
+
+ return null;
+ }
+
+
+}
diff --git a/src/main/java/dev/dnpm/security/PersonPoolBasedSecurityAspects.java b/src/main/java/dev/dnpm/security/PersonPoolBasedSecurityAspects.java
new file mode 100644
index 0000000..66179f7
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/PersonPoolBasedSecurityAspects.java
@@ -0,0 +1,74 @@
+package dev.dnpm.security;
+
+import de.itc.onkostar.api.Patient;
+import de.itc.onkostar.api.Procedure;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+
+@Component
+@Aspect
+public class PersonPoolBasedSecurityAspects {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ private final PersonPoolBasedPermissionEvaluator permissionEvaluator;
+
+ public PersonPoolBasedSecurityAspects(PersonPoolBasedPermissionEvaluator permissionEvaluator) {
+ this.permissionEvaluator = permissionEvaluator;
+ }
+
+ @AfterReturning(value = "@annotation(dev.dnpm.security.PersonPoolSecuredResult) ", returning = "patient")
+ public void afterPatient(Patient patient) {
+ if (
+ null != patient
+ && ! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), patient, PermissionType.READ_WRITE)
+ ) {
+ logger.warn("Rückgabe von Patient blockiert: {}", patient.getId());
+ throw new IllegalSecuredObjectAccessException();
+ }
+ }
+
+ @AfterReturning(value = "@annotation(dev.dnpm.security.PersonPoolSecuredResult)", returning = "procedure")
+ public void afterProcedure(Procedure procedure) {
+ if (
+ null != procedure
+ && ! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), procedure, PermissionType.READ_WRITE)
+ ) {
+ logger.warn("Rückgabe von Prozedur blockiert: {}", procedure.getId());
+ throw new IllegalSecuredObjectAccessException();
+ }
+ }
+
+ @Before(value = "@annotation(dev.dnpm.security.PersonPoolSecured)")
+ public void beforePatient(JoinPoint jp) {
+ Arrays.stream(jp.getArgs())
+ .filter(arg -> arg instanceof Patient)
+ .forEach(patient -> {
+ if (! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), patient, PermissionType.READ_WRITE)) {
+ logger.warn("Zugriff auf Patient blockiert: {}", ((Patient)patient).getId());
+ throw new IllegalSecuredObjectAccessException();
+ }
+ });
+ }
+
+ @Before(value = "@annotation(dev.dnpm.security.PersonPoolSecured)")
+ public void beforeProcedure(JoinPoint jp) {
+ Arrays.stream(jp.getArgs())
+ .filter(arg -> arg instanceof Procedure)
+ .forEach(procedure -> {
+ if (! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), procedure, PermissionType.READ_WRITE)) {
+ logger.warn("Zugriff auf Prozedur blockiert: {}", ((Procedure)procedure).getId());
+ throw new IllegalSecuredObjectAccessException();
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/security/PersonPoolSecured.java b/src/main/java/dev/dnpm/security/PersonPoolSecured.java
new file mode 100644
index 0000000..8a5709c
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/PersonPoolSecured.java
@@ -0,0 +1,14 @@
+package dev.dnpm.security;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface PersonPoolSecured {
+
+ PermissionType value() default PermissionType.READ_WRITE;
+
+}
diff --git a/src/main/java/dev/dnpm/security/PersonPoolSecuredResult.java b/src/main/java/dev/dnpm/security/PersonPoolSecuredResult.java
new file mode 100644
index 0000000..36e19da
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/PersonPoolSecuredResult.java
@@ -0,0 +1,14 @@
+package dev.dnpm.security;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface PersonPoolSecuredResult {
+
+ PermissionType value() default PermissionType.READ_WRITE;
+
+}
diff --git a/src/main/java/dev/dnpm/security/SecurityService.java b/src/main/java/dev/dnpm/security/SecurityService.java
new file mode 100644
index 0000000..ba80a9d
--- /dev/null
+++ b/src/main/java/dev/dnpm/security/SecurityService.java
@@ -0,0 +1,60 @@
+package dev.dnpm.security;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Service;
+
+import javax.sql.DataSource;
+import java.util.List;
+
+/**
+ * Service mit Methoden zum Feststellen von sicherheitsrelevanten Informationen eines Benutzers
+ */
+@Service
+public class SecurityService {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ public SecurityService(final DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ List getPersonPoolIdsForPermission(Authentication authentication, PermissionType permissionType) {
+ var sql = "SELECT p.kennung FROM personenstamm_zugriff " +
+ " JOIN usergroup u ON personenstamm_zugriff.benutzergruppe_id = u.id " +
+ " JOIN akteur_usergroup au ON u.id = au.usergroup_id " +
+ " JOIN akteur a ON au.akteur_id = a.id " +
+ " JOIN personenstamm p on personenstamm_zugriff.personenstamm_id = p.id " +
+ " WHERE a.login = ? AND a.aktiv AND a.anmelden_moeglich ";
+
+ if (PermissionType.READ_WRITE == permissionType) {
+ sql += " AND personenstamm_zugriff.bearbeiten ";
+ }
+
+ var userDetails = (UserDetails)authentication.getPrincipal();
+
+ return jdbcTemplate
+ .query(sql, new Object[]{userDetails.getUsername()}, (rs, rowNum) -> rs.getString("kennung"));
+ }
+
+ List getFormNamesForPermission(Authentication authentication, PermissionType permissionType) {
+
+ var sql = "SELECT df.name FROM formular_usergroup_zugriff " +
+ " JOIN data_form df ON formular_usergroup_zugriff.formular_id = df.id " +
+ " JOIN usergroup u ON formular_usergroup_zugriff.usergroup_id = u.id " +
+ " JOIN akteur_usergroup au ON u.id = au.usergroup_id " +
+ " JOIN akteur a on au.akteur_id = a.id " +
+ " WHERE a.login = ? AND a.aktiv AND a.anmelden_moeglich ";
+
+ if (PermissionType.READ_WRITE == permissionType) {
+ sql += " AND formular_usergroup_zugriff.bearbeiten ";
+ }
+
+ var userDetails = (UserDetails)authentication.getPrincipal();
+
+ return jdbcTemplate
+ .query(sql, new Object[]{userDetails.getUsername()}, (rs, rowNum) -> rs.getString("name"));
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/services/DefaultFormService.java b/src/main/java/dev/dnpm/services/DefaultFormService.java
new file mode 100644
index 0000000..4240809
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/DefaultFormService.java
@@ -0,0 +1,37 @@
+package dev.dnpm.services;
+
+import dev.dnpm.exceptions.FormException;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+import java.util.List;
+
+/**
+ * Standardimplementierung zum Ermitteln von Unter- und Hauptformularen
+ *
+ * @since 0.0.2
+ */
+public class DefaultFormService implements FormService {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ public DefaultFormService(final DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ @Override
+ public int getMainFormProcedureId(int procedureId) throws FormException {
+ var sql = "SELECT hauptprozedur_id FROM prozedur WHERE id = ?";
+ try {
+ return jdbcTemplate.queryForObject(sql, (resultSet, i) -> resultSet.getInt("hauptprozedur_id"), procedureId);
+ } catch (Exception e) {
+ throw new FormException(String.format("No main form found for subform with ID '%d'", procedureId));
+ }
+ }
+
+ @Override
+ public List getSubFormProcedureIds(int procedureId) {
+ var sql = "SELECT id FROM prozedur WHERE hauptprozedur_id = ?";
+ return jdbcTemplate.queryForList(sql, Integer.class, procedureId);
+ }
+}
diff --git a/src/main/java/dev/dnpm/services/DefaultStudienService.java b/src/main/java/dev/dnpm/services/DefaultStudienService.java
new file mode 100644
index 0000000..5c65842
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/DefaultStudienService.java
@@ -0,0 +1,78 @@
+package dev.dnpm.services;
+
+import dev.dnpm.dto.Studie;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Standardimplementierung zum Ermitteln von Studien
+ *
+ * @since 0.0.2
+ */
+public class DefaultStudienService implements StudienService {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ public DefaultStudienService(final DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ @Override
+ public List findAll() {
+ var sql = "SELECT pcc.name, pcv.version_number, TRIM(studie.studien_nummer) AS studien_nummer, studie.startdatum, studie.endedatum, pcve.code, pcve.shortdesc, pcve.description, studie.aktiv FROM studie "
+ + "LEFT JOIN category_entry ce ON ce.version_entry_id = studie.property_version_entry "
+ + "LEFT JOIN property_catalogue_category pcc ON pcc.id = ce.category_id "
+ + "JOIN property_catalogue_version_entry pcve ON pcve.id = studie.property_version_entry "
+ + "JOIN property_catalogue_version pcv ON pcv.id = pcve.property_version_id "
+ + "JOIN property_catalogue pc ON pc.id = pcv.datacatalog_id "
+ + "WHERE pc.name = 'OS.Studien'"
+ + "ORDER BY TRIM(studie.studien_nummer)";
+
+ return this.jdbcTemplate.query(sql, (resultSet, i) -> new Studie(
+ resultSet.getString("name"),
+ resultSet.getInt("version_number"),
+ resultSet.getString("code"),
+ resultSet.getString("studien_nummer"),
+ resultSet.getString("shortdesc"),
+ resultSet.getString("description"),
+ resultSet.getBoolean("aktiv")
+ ));
+ }
+
+ @Override
+ public List findByQuery(String query) {
+ var sql = "SELECT pcc.name, pcv.version_number, TRIM(studie.studien_nummer) AS studien_nummer, studie.startdatum, studie.endedatum, pcve.code, pcve.shortdesc, pcve.description, studie.aktiv FROM studie "
+ + "LEFT JOIN category_entry ce ON ce.version_entry_id = studie.property_version_entry "
+ + "LEFT JOIN property_catalogue_category pcc ON pcc.id = ce.category_id "
+ + "JOIN property_catalogue_version_entry pcve ON pcve.id = studie.property_version_entry "
+ + "JOIN property_catalogue_version pcv ON pcv.id = pcve.property_version_id "
+ + "JOIN property_catalogue pc ON pc.id = pcv.datacatalog_id "
+ + "WHERE pc.name = 'OS.Studien' AND (pcve.shortdesc LIKE ? OR pcve.description LIKE ? OR studie.studien_nummer LIKE ?)"
+ + "ORDER BY TRIM(studie.studien_nummer)";
+
+ var like = String.format("%%%s%%", query);
+
+ return this.jdbcTemplate.query(sql, new Object[]{like, like, like}, (resultSet, i) -> new Studie(
+ resultSet.getString("name"),
+ resultSet.getInt("version_number"),
+ resultSet.getString("code"),
+ resultSet.getString("studien_nummer"),
+ resultSet.getString("shortdesc"),
+ resultSet.getString("description"),
+ resultSet.getBoolean("aktiv")
+ ));
+ }
+
+ @Override
+ public List findActive() {
+ return findAll().stream().filter(Studie::isActive).collect(Collectors.toList());
+ }
+
+ @Override
+ public List findActiveByQuery(String query) {
+ return findByQuery(query).stream().filter(Studie::isActive).collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/dev/dnpm/services/FormService.java b/src/main/java/dev/dnpm/services/FormService.java
new file mode 100644
index 0000000..e400516
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/FormService.java
@@ -0,0 +1,51 @@
+package dev.dnpm.services;
+
+import dev.dnpm.exceptions.FormException;
+import de.itc.onkostar.api.Procedure;
+import de.itc.onkostar.api.constants.JaNeinUnbekannt;
+
+import java.util.List;
+
+public interface FormService {
+
+ /**
+ * Diese Methode übergibt die Prozedur-ID des zugehörigen Hauptformulars zu einem Unterformular
+ * Siehe auch: FormInfoService.java
+ *
+ * @param procedureId Die Prozedur-ID des Unterformulars
+ * @return Die Prozedur-ID des zugehörigen Hauptformulars
+ * @throws FormException Wird geworfen, wenn ein Fehler auftrat
+ */
+ int getMainFormProcedureId(int procedureId) throws FormException;
+
+ /**
+ * Diese Methode übergibt die Prozedur-IDs von Unterformularen zu einem Formular
+ * Siehe auch: FormInfoService.java
+ *
+ * @param procedureId Die Prozedur-ID des Formulars
+ * @return Eine Liste mit Prozedur-IDs der Unterformulare
+ */
+ List getSubFormProcedureIds(int procedureId);
+
+ /**
+ * Prüft, ob ein Formularfeld in der Prozedur einen Wert hat oder null ist
+ * @param procedure Die zu prüfende Prozedur
+ * @param fieldName Der Formularfeldname
+ * @return Gibt true zurück, wenn das Feld einen Wert hat
+ */
+ static boolean hasValue(final Procedure procedure, final String fieldName) {
+ return null != procedure.getValue(fieldName);
+ }
+
+ /**
+ * Prüft, ob ein Formularfeld mit Ja/Nein/Unbekannt den Wert Ja hat
+ * @param procedure Die zu prüfende Prozedur
+ * @param fieldName Der Formularfeldname
+ * @return Gibt true zurück, wenn das Feld den Wert "Ja" hat
+ */
+ static boolean isYes(final Procedure procedure, final String fieldName) {
+ return hasValue(procedure, fieldName)
+ && procedure.getValue(fieldName).getString().equals(JaNeinUnbekannt.JA.getCode());
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/services/SettingsService.java b/src/main/java/dev/dnpm/services/SettingsService.java
new file mode 100644
index 0000000..8aa5f6b
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/SettingsService.java
@@ -0,0 +1,47 @@
+package dev.dnpm.services;
+
+import dev.dnpm.database.SettingsRepository;
+
+import java.util.Optional;
+
+/**
+ * Implementiert den Dienst zur Ermittlung von Systemeinstellungen
+ */
+public class SettingsService {
+
+ private final SettingsRepository settingsRepository;
+
+ public SettingsService(final SettingsRepository settingsRepository) {
+ this.settingsRepository = settingsRepository;
+ }
+
+ /**
+ * Übergibt ein Optional für die Einstellung mit angegebenen Namen
+ * @param name Name der Einstellung
+ * @return Optional mit Wert der Einstellung oder ein leeres Optional, wenn Einstellung nicht gefunden
+ */
+ public Optional getSetting(String name) {
+ var sid = settingsRepository.findByName(name);
+ if (null == sid) {
+ return Optional.empty();
+ }
+ return Optional.of(sid.getValue());
+ }
+
+ /**
+ * Übergibt die SID als Optional
+ * @return Optional mit Wert der SID
+ */
+ public Optional getSID() {
+ return getSetting("SID");
+ }
+
+ /**
+ * Übergibt die Einstellung für mehrere_mtb_in_mtbepisode
+ * @return Übergibt true, wenn mehrere_mtb_in_mtbepisode auf "Ja" gesetzt ist.
+ */
+ public boolean multipleMtbsInMtbEpisode() {
+ var setting = getSetting("mehrere_mtb_in_mtbepisode");
+ return setting.isPresent() && setting.get().equals("true");
+ }
+}
diff --git a/src/main/java/dev/dnpm/services/StudienService.java b/src/main/java/dev/dnpm/services/StudienService.java
new file mode 100644
index 0000000..c07b1c7
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/StudienService.java
@@ -0,0 +1,39 @@
+package dev.dnpm.services;
+
+import dev.dnpm.dto.Studie;
+
+import java.util.List;
+
+public interface StudienService {
+
+ /**
+ * Übergibt eine Liste mit allen Studien
+ *
+ * @return Liste mit allen Studien
+ */
+ List findAll();
+
+ /**
+ * Übergibt eine Liste mit Studien, deren (Kurz-)Beschreibung oder Studiennummer den übergebenen Wert enthalten
+ *
+ * @param query Wert der enthalten sein muss
+ * @return Gefilterte Liste mit Studien
+ */
+ List findByQuery(String query);
+
+ /**
+ * Übergibt eine Liste mit aktiven Studien
+ *
+ * @return Liste mit aktiven Studien
+ */
+ List findActive();
+
+ /**
+ * Übergibt eine Liste mit aktiven Studien, deren (Kurz-)Beschreibung oder Studiennummer den übergebenen Wert enthalten
+ *
+ * @param query Wert der enthalten sein muss
+ * @return Gefilterte Liste mit aktiven Studien
+ */
+ List findActiveByQuery(String query);
+
+}
diff --git a/src/main/java/dev/dnpm/services/TherapieMitEcogService.java b/src/main/java/dev/dnpm/services/TherapieMitEcogService.java
new file mode 100644
index 0000000..865d11a
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/TherapieMitEcogService.java
@@ -0,0 +1,30 @@
+package dev.dnpm.services;
+
+import dev.dnpm.dto.EcogStatusWithDate;
+import de.itc.onkostar.api.Patient;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Schnittstelle zum Ermitteln von ECOG-Statusinformationen
+ *
+ * @since 0.6.0
+ */
+public interface TherapieMitEcogService {
+
+ /**
+ * Ermittelt den letzten bekannten ECOG-Status aus allen Therapieformularen des Patienten
+ * @param patient Der zu verwendende Patient
+ * @return Der ECOG-Status als String oder leeres Optional
+ */
+ Optional latestEcogStatus(Patient patient);
+
+ /**
+ * Ermittelt jeden bekannten ECOG-Status aus allen Therapieformularen des Patienten
+ * @param patient Der zu verwendende Patient
+ * @return Eine Liste mit Datum und ECOG-Status als String
+ */
+ List ecogStatus(Patient patient);
+
+}
diff --git a/src/main/java/dev/dnpm/services/consent/ConsentManagerService.java b/src/main/java/dev/dnpm/services/consent/ConsentManagerService.java
new file mode 100644
index 0000000..abbc702
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/consent/ConsentManagerService.java
@@ -0,0 +1,27 @@
+package dev.dnpm.services.consent;
+
+import de.itc.onkostar.api.Procedure;
+
+/**
+ * Schnittstelle für die Anwendung von Consent-Änderungen
+ *
+ * @since 0.2.0
+ */
+public interface ConsentManagerService {
+
+ /**
+ * Wende Consent an, wenn dieses Consent-Formular gespeichert wird
+ * @param procedure Prozedur des Consent-Formulars
+ */
+ void applyConsent(Procedure procedure);
+
+ /**
+ * Optionale Prüfung, ob die angegebene Prozedur angewendet werden kann.
+ * @param procedure Anzuwendende Prozedur
+ * @return Gibt true zurück, wenn die Prozedur angewendet werden kann.
+ */
+ default boolean canApply(Procedure procedure) {
+ return null != procedure;
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/services/consent/ConsentManagerServiceFactory.java b/src/main/java/dev/dnpm/services/consent/ConsentManagerServiceFactory.java
new file mode 100644
index 0000000..b3f9497
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/consent/ConsentManagerServiceFactory.java
@@ -0,0 +1,28 @@
+package dev.dnpm.services.consent;
+
+import de.itc.onkostar.api.IOnkostarApi;
+
+public class ConsentManagerServiceFactory {
+
+ private final IOnkostarApi onkostarApi;
+
+ public ConsentManagerServiceFactory(
+ final IOnkostarApi onkostarApi
+ ) {
+ this.onkostarApi = onkostarApi;
+ }
+
+ public ConsentManagerService currentUsableInstance() {
+ var consentFormName = onkostarApi.getGlobalSetting("consentform");
+
+ switch (consentFormName) {
+ case "Excel-Formular":
+ return new UkwConsentManagerService(this.onkostarApi);
+ case "MR.Consent":
+ return new MrConsentManagerService(this.onkostarApi);
+ default:
+ return procedure -> {};
+ }
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/services/consent/MrConsentManagerService.java b/src/main/java/dev/dnpm/services/consent/MrConsentManagerService.java
new file mode 100644
index 0000000..e4d88e2
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/consent/MrConsentManagerService.java
@@ -0,0 +1,126 @@
+package dev.dnpm.services.consent;
+
+import dev.dnpm.VerweisVon;
+import de.itc.onkostar.api.IOnkostarApi;
+import de.itc.onkostar.api.Item;
+import de.itc.onkostar.api.Procedure;
+import org.hibernate.SQLQuery;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.transform.Transformers;
+import org.hibernate.type.StandardBasicTypes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Detailimplementierung für das Formular `MR.Consent`
+ *
+ * @since 0.2.0
+ */
+public class MrConsentManagerService implements ConsentManagerService {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ private final IOnkostarApi onkostarApi;
+
+ public MrConsentManagerService(final IOnkostarApi onkostarApi) {
+ this.onkostarApi = onkostarApi;
+ }
+
+ @Override
+ public boolean canApply(Procedure procedure) {
+ return null != procedure && procedure.getFormName().equals("MR.Consent");
+ }
+
+ /**
+ * Wende Consent an, wenn dieses Consent-Formular gespeichert wird
+ *
+ * @param procedure Prozedur des Consent-Formulars
+ */
+ @Override
+ public void applyConsent(Procedure procedure) {
+ int value = 0;
+ try {
+ SessionFactory sessionFactory = onkostarApi.getSessionFactory();
+ Session session = sessionFactory.getCurrentSession();
+ // geänderte Werte checken
+ String sql1 = "select id, max(timestamp) AS datum from aenderungsprotokoll where entity_id = '" + procedure.getId() + "'";
+ SQLQuery query1 = session.createSQLQuery(sql1)
+ .addScalar("id", StandardBasicTypes.INTEGER)
+ .addScalar("datum", StandardBasicTypes.TIMESTAMP);
+ logger.info("Wert-Check: {}", query1.uniqueResult());
+
+ String sql = "SELECT prozedur.id AS procedure_id, prozedur.data_form_id, data_catalogue.name AS data_catalogue, data_catalogue_entry.name AS data_catalogue_entry, data_form.description AS formname, prozedur.beginndatum AS datum " +
+ "FROM prozedur " +
+ "LEFT JOIN data_form_data_catalogue ON data_form_data_catalogue.data_form_id = prozedur.data_form_id " +
+ "LEFT JOIN data_catalogue_entry ON data_catalogue_entry.data_catalogue_id = data_form_data_catalogue.data_catalogue_id " +
+ "LEFT JOIN data_catalogue ON data_catalogue.id = data_catalogue_entry.data_catalogue_id " +
+ "LEFT JOIN data_form ON data_form.id = prozedur.data_form_id " +
+ "WHERE patient_id = " + procedure.getPatientId() + " " +
+ "AND geloescht = 0 " +
+ "AND data_catalogue_entry.type = 'formReference' " +
+ "GROUP BY prozedur.id, prozedur.data_form_id, data_catalogue.name, data_catalogue_entry.name";
+
+ SQLQuery query = session.createSQLQuery(sql)
+ .addScalar("procedure_id", StandardBasicTypes.INTEGER)
+ .addScalar("data_form_id", StandardBasicTypes.INTEGER)
+ .addScalar("data_catalogue", StandardBasicTypes.STRING)
+ .addScalar("data_catalogue_entry", StandardBasicTypes.STRING)
+ .addScalar("formname", StandardBasicTypes.STRING)
+ .addScalar("datum", StandardBasicTypes.DATE);
+
+ query.setResultTransformer(Transformers.aliasToBean(VerweisVon.class));
+
+ @SuppressWarnings("unchecked")
+ List result = query.list();
+
+ for (VerweisVon verweisVon : result) {
+ sql = verweisVon.getSQL();
+ query = session.createSQLQuery(sql)
+ .addScalar("value", StandardBasicTypes.INTEGER);
+ if (query.uniqueResult() != null) {
+ value = (Integer) query.uniqueResult();
+ }
+ if (value == procedure.getId()) {
+ saveReferencedProcedure(procedure, verweisVon);
+ value = 0;
+ }
+ }
+ } catch (RuntimeException e) {
+ logger.error("Sonstiger Fehler bei der Ausführung von analyze()", e);
+ }
+ }
+
+ private void saveReferencedProcedure(Procedure prozedur, VerweisVon verweisVon) {
+ Procedure andereprozedur = onkostarApi.getProcedure(verweisVon.getProcedure_id());
+ try {
+ Map felder = prozedur.getAllValues();
+ for (Map.Entry feld : felder.entrySet()) {
+ if (feld.getKey().startsWith("Consent")) {
+ if (feld.getKey().equals("ConsentStatusEinwilligungDNPM")) {
+ switch (feld.getValue().getValue().toString()) {
+ case "z":
+ andereprozedur.setValue(feld.getKey(), new Item(feld.getKey(), "active"));
+ break;
+ case "a":
+ case "w":
+ andereprozedur.setValue(feld.getKey(), new Item(feld.getKey(), "rejected"));
+ break;
+ default:
+ break;
+ }
+ } else {
+ andereprozedur.setValue(feld.getKey(), prozedur.getValue(feld.getKey()));
+ }
+ }
+ }
+ onkostarApi.saveProcedure(andereprozedur);
+ } catch (Exception e) {
+ logger.error("Kann Prozedur nicht speichern", e);
+ }
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/services/consent/UkwConsentManagerService.java b/src/main/java/dev/dnpm/services/consent/UkwConsentManagerService.java
new file mode 100644
index 0000000..d6697d6
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/consent/UkwConsentManagerService.java
@@ -0,0 +1,72 @@
+package dev.dnpm.services.consent;
+
+import de.itc.onkostar.api.IOnkostarApi;
+import de.itc.onkostar.api.Item;
+import de.itc.onkostar.api.Procedure;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Comparator;
+
+/**
+ * Detailimplementierung für das Formular `Excel-Formular`
+ *
+ * @since 0.2.0
+ */
+public class UkwConsentManagerService implements ConsentManagerService {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ private final IOnkostarApi onkostarApi;
+
+ public UkwConsentManagerService(final IOnkostarApi onkostarApi) {
+ this.onkostarApi = onkostarApi;
+ }
+
+ @Override
+ public boolean canApply(Procedure procedure) {
+ return null != procedure && procedure.getFormName().equals("Excel-Formular");
+ }
+
+ /**
+ * Wende Consent an, wenn dieses Consent-Formular gespeichert wird
+ *
+ * @param procedure Prozedur des Consent-Formulars
+ */
+ @Override
+ public void applyConsent(Procedure procedure) {
+ var refdnpmklinikanamnese = procedure.getValue("refdnpmklinikanamnese").getInt();
+ var dnpmKlinikAnamnese = this.onkostarApi.getProcedure(refdnpmklinikanamnese);
+
+ if (null == dnpmKlinikAnamnese) {
+ return;
+ }
+
+ var consents = procedure.getSubProceduresMap().get("ufdnpmconsent");
+
+ if (null == consents) {
+ return;
+ }
+
+ consents.stream()
+ .max(Comparator.comparing(Procedure::getStartDate))
+ .ifPresent(lastConsent -> {
+ var date = lastConsent.getStartDate();
+ var status = lastConsent.getValue("status");
+ if (null == date || null == status || status.getString().isBlank()) {
+ logger.warn("Kein DNPM-Einwilligungstatus angegeben");
+ return;
+ }
+
+ dnpmKlinikAnamnese.setValue("ConsentStatusEinwilligungDNPM", new Item("Einwilligung", status.getString()));
+ dnpmKlinikAnamnese.setValue("ConsentDatumEinwilligungDNPM", new Item("DatumEinwilligung", date));
+
+ try {
+ onkostarApi.saveProcedure(dnpmKlinikAnamnese, false);
+ } catch (Exception e) {
+ logger.error("Kann DNPM-Einwilligungstatus nicht aktualisieren", e);
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/services/molekulargenetik/MolekulargenetikFormService.java b/src/main/java/dev/dnpm/services/molekulargenetik/MolekulargenetikFormService.java
new file mode 100644
index 0000000..127c24a
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/molekulargenetik/MolekulargenetikFormService.java
@@ -0,0 +1,20 @@
+package dev.dnpm.services.molekulargenetik;
+
+import dev.dnpm.dto.Variant;
+import de.itc.onkostar.api.Procedure;
+
+import java.util.List;
+
+/**
+ * Schnittstellenbeschreibung für Methoden zum Formular "OS.Molekulargenetik"
+ */
+public interface MolekulargenetikFormService {
+
+ /**
+ * Ermittelt alle (unterstützten) Varianten zur Prozedur eines Formulars "OS.Molekulargenetik"
+ * @param procedure Die Prozedur zum Formular "OS.Molekulargenetik"
+ * @return Die unterstützten Varianten oder eine leere Liste, wenn keine Varianten gefunden wurden.
+ */
+ List getVariants(Procedure procedure);
+
+}
diff --git a/src/main/java/dev/dnpm/services/molekulargenetik/OsMolekulargenetikFormService.java b/src/main/java/dev/dnpm/services/molekulargenetik/OsMolekulargenetikFormService.java
new file mode 100644
index 0000000..1a9ed11
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/molekulargenetik/OsMolekulargenetikFormService.java
@@ -0,0 +1,45 @@
+package dev.dnpm.services.molekulargenetik;
+
+import dev.dnpm.dto.Variant;
+import de.itc.onkostar.api.Procedure;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class OsMolekulargenetikFormService implements MolekulargenetikFormService {
+
+ /**
+ * Ermittelt alle (unterstützten) Varianten zur Prozedur eines Formulars "OS.Molekulargenetik" (oder Variante)
+ * Unterstützte Varianten sind:
+ *
+ * - Einfache Variante
+ *
- CNV
+ *
- Fusion
+ * @param procedure Die Prozedur zum Formular "OS.Molekulargenetik" (oder Variante)
+ * @return Die unterstützten Varianten oder eine leere Liste, wenn keine Varianten gefunden wurden.
+ */
+ @Override
+ public List getVariants(Procedure procedure) {
+ if (! procedureWithUsableFormVariant(procedure)) {
+ return List.of();
+ }
+
+ var subforms = procedure.getSubProceduresMap().get("MolekulargenetischeUntersuchung");
+ if (null == subforms) {
+ return List.of();
+ }
+
+ return subforms.stream()
+ .map(Variant::fromProcedure)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(Collectors.toList());
+
+ }
+
+ private boolean procedureWithUsableFormVariant(Procedure procedure) {
+ return "OS.Molekulargenetik".equals(procedure.getFormName())
+ || "UKER.Molekulargenetik".equals(procedure.getFormName());
+ }
+}
diff --git a/src/main/java/dev/dnpm/services/mtb/DefaultMtbService.java b/src/main/java/dev/dnpm/services/mtb/DefaultMtbService.java
new file mode 100644
index 0000000..d5433b8
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/mtb/DefaultMtbService.java
@@ -0,0 +1,70 @@
+package dev.dnpm.services.mtb;
+
+import de.itc.onkostar.api.IOnkostarApi;
+import de.itc.onkostar.api.Procedure;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Standardimplementierung des MtbService
+ *
+ * @since 0.0.2
+ */
+public class DefaultMtbService implements MtbService {
+
+ private final IOnkostarApi onkostarApi;
+
+ public DefaultMtbService(final IOnkostarApi onkostarApi) {
+ this.onkostarApi = onkostarApi;
+ }
+
+ /**
+ * Zusammenfassung der Prozeduren.
+ * Dabei werden alle Prozeduren sortiert, mit ermitteltem Mapper in {@link Optional} eines {@link String}s
+ * gewandelt und, wenn dies erfolgreich war, die Zeichenkette extrahiert.
+ * Im Anschluss wird die Abfolge der Zeichenketten mit den einzelnen Prozedur-Zusammenfassungen in eine
+ * einzige Zusammenfassung zusammengefügt.
+ * @param procedures Prozeduren, die zusammen gefasst werden sollen
+ * @return Text mit Zusammenfassung aller übergebenen Prozeduren
+ */
+ @Override
+ public String getProtocol(List procedures) {
+ return this.sortedDistinctProcedureProtocolList(procedures.stream())
+ .collect(Collectors.joining("\n\n"));
+ }
+
+ private Stream sortedDistinctProcedureProtocolList(Stream procedures) {
+ return procedures
+ .sorted(Comparator.comparing(Procedure::getStartDate))
+ .map(this::selectAndApplyMapper)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .distinct();
+ }
+
+ /**
+ * Übergibt anzuwendenden Mapper für eine Prozedur.
+ * Wurde keine Implementierung festgelegt, wird ein Mapper zurückgegeben, der eine
+ * Prozedur in ein leeres {@link Optional} zurück gibt, übergeben.
+ * @param procedure Prozedur, für die ein Mapper ermittelt werden soll
+ * @return Mapper für diese Prozedur
+ */
+ @Override
+ public ProcedureToProtocolMapper procedureToProtocolMapper(Procedure procedure) {
+ switch (procedure.getFormName()) {
+ case "OS.Tumorkonferenz":
+ return new OsTumorkonferenzToProtocolMapper();
+ case "OS.Tumorkonferenz.VarianteUKW":
+ return new OsTumorkonferenzVarianteUkwToProtocolMapper();
+ case "MR.MTB_Anmeldung":
+ return new MrMtbAnmeldungToProtocolMapper(this.onkostarApi);
+ default:
+ return p -> Optional.empty();
+ }
+ }
+
+}
diff --git a/src/main/java/dev/dnpm/services/mtb/MrMtbAnmeldungToProtocolMapper.java b/src/main/java/dev/dnpm/services/mtb/MrMtbAnmeldungToProtocolMapper.java
new file mode 100644
index 0000000..d705606
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/mtb/MrMtbAnmeldungToProtocolMapper.java
@@ -0,0 +1,63 @@
+package dev.dnpm.services.mtb;
+
+import de.itc.onkostar.api.IOnkostarApi;
+import de.itc.onkostar.api.Procedure;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Optional;
+
+public class MrMtbAnmeldungToProtocolMapper implements ProcedureToProtocolMapper {
+
+ private final IOnkostarApi onkostarApi;
+
+ public MrMtbAnmeldungToProtocolMapper(final IOnkostarApi onkostarApi) {
+ this.onkostarApi = onkostarApi;
+ }
+
+ /**
+ * Wandelt eine Prozedur mit Formularnamen "MR.MTB_Anmeldung" in ein {@link Optional} mit einer
+ * Zeichenkette oder im Fehlerfall in ein leeres Optional um.
+ *
+ * @param procedure Die Prozedur, für die eine Zusammenfassung ermittelt werden soll.
+ * @return Das {@link Optional} mit, im Erfolgsfall, der Zusammenfassung für die Prozedur.
+ */
+ @Override
+ public Optional apply(Procedure procedure) {
+ if ((!procedure.getFormName().equals("MR.MTB_Anmeldung"))) {
+ throw new AssertionError("Procedure is not of form type 'MR.MTB_Anmeldung'");
+ }
+
+ var resultParts = new ArrayList();
+
+ var fragestellung = procedure.getValue("Fragestellung");
+ if (null != fragestellung && !fragestellung.getString().isBlank()) {
+ resultParts.add(String.format("Fragestellung:%n%s", fragestellung.getString()));
+ }
+
+ var refEmpfehlung = procedure.getValue("Empfehlung");
+ if (null != refEmpfehlung && refEmpfehlung.getInt() > 0) {
+ var empfehlungsProzedur = onkostarApi.getProcedure(refEmpfehlung.getInt());
+ var refEinzelempfehlungen = onkostarApi.getSubprocedures(empfehlungsProzedur.getId());
+
+ if (null != refEinzelempfehlungen) {
+ refEinzelempfehlungen.stream()
+ .sorted(Comparator.comparingInt(proc -> proc.getValue("Prioritaet").getInt()))
+ .forEach(proc -> {
+ if (proc.getFormName().equals("MR.MTB_Einzelempfehlung")) {
+ var empfehlung = proc.getValue("Empfehlung");
+ if (null != empfehlung && !empfehlung.getString().isBlank()) {
+ resultParts.add(String.format("Empfehlung:%n%s", empfehlung.getString()));
+ }
+ }
+ });
+ }
+ }
+
+ if (resultParts.isEmpty()) {
+ return Optional.empty();
+ }
+
+ return Optional.of(String.join("\n\n", resultParts));
+ }
+}
diff --git a/src/main/java/dev/dnpm/services/mtb/MtbService.java b/src/main/java/dev/dnpm/services/mtb/MtbService.java
new file mode 100644
index 0000000..bc79b37
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/mtb/MtbService.java
@@ -0,0 +1,31 @@
+package dev.dnpm.services.mtb;
+
+import de.itc.onkostar.api.Procedure;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface MtbService {
+ /**
+ * Zusammenfassung der Prozeduren
+ * @param procedures Prozeduren, die zusammen gefasst werden sollen
+ * @return Text mit Zusammenfassung der Prozeduren
+ */
+ String getProtocol(List procedures);
+
+ /**
+ * Übergibt anzuwendenden Mapper für eine Prozedur
+ * @param procedure Prozedur, für die ein Mapper ermittelt werden soll
+ * @return Mapper für diese Prozedur
+ */
+ ProcedureToProtocolMapper procedureToProtocolMapper(Procedure procedure);
+
+ /**
+ * Select mapper using method {@link #procedureToProtocolMapper(Procedure)} and apply procedure
+ * @param procedure The Procedure to select mapper for and apply
+ * @return {@link Optional} with protocol or empty {@link Optional}
+ */
+ default Optional selectAndApplyMapper(Procedure procedure) {
+ return this.procedureToProtocolMapper(procedure).apply(procedure);
+ }
+}
diff --git a/src/main/java/dev/dnpm/services/mtb/OsTumorkonferenzToProtocolMapper.java b/src/main/java/dev/dnpm/services/mtb/OsTumorkonferenzToProtocolMapper.java
new file mode 100644
index 0000000..1473431
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/mtb/OsTumorkonferenzToProtocolMapper.java
@@ -0,0 +1,43 @@
+package dev.dnpm.services.mtb;
+
+import de.itc.onkostar.api.Procedure;
+
+import java.util.Optional;
+
+
+/**
+ * Mapper zum Ermitteln des Protokollauszugs für Formular "OS.Tumorkonferenz"
+ *
+ * @since 0.0.2
+ */
+public class OsTumorkonferenzToProtocolMapper implements ProcedureToProtocolMapper {
+
+ /**
+ * Wandelt eine Prozedur mit Formularnamen "OS.Tumorkonferenz" in ein {@link Optional} mit einer
+ * Zeichenkette oder im Fehlerfall in ein leeres Optional um.
+ * @param procedure Die Prozedur, für die eine Zusammenfassung ermittelt werden soll.
+ * @return Das {@link Optional} mit, im Erfolgsfall, der Zusammenfassung für die Prozedur.
+ */
+ @Override
+ public Optional apply(Procedure procedure) {
+ if ((!procedure.getFormName().equals("OS.Tumorkonferenz"))) {
+ throw new AssertionError("Procedure is not of form type 'OS.Tumorkonferenz'");
+ }
+
+ var fragestellung = procedure.getValue("Fragestellung");
+ var empfehlung = procedure.getValue("Empfehlung");
+
+ if (
+ null != fragestellung && !fragestellung.getString().isBlank()
+ && null != empfehlung && !empfehlung.getString().isBlank()
+ ) {
+ return Optional.of(String.format("Fragestellung:%n%s%n%nEmpfehlung:%n%s", fragestellung.getString(), empfehlung.getString()));
+ } else if (null != fragestellung && !fragestellung.getString().isBlank()) {
+ return Optional.of(fragestellung.getString());
+ } else if (null != empfehlung && !empfehlung.getString().isBlank()) {
+ return Optional.of(empfehlung.getString());
+ }
+
+ return Optional.empty();
+ }
+}
diff --git a/src/main/java/dev/dnpm/services/mtb/OsTumorkonferenzVarianteUkwToProtocolMapper.java b/src/main/java/dev/dnpm/services/mtb/OsTumorkonferenzVarianteUkwToProtocolMapper.java
new file mode 100644
index 0000000..5d1d178
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/mtb/OsTumorkonferenzVarianteUkwToProtocolMapper.java
@@ -0,0 +1,42 @@
+package dev.dnpm.services.mtb;
+
+import de.itc.onkostar.api.Procedure;
+
+import java.util.Optional;
+
+/**
+ * Mapper zum Ermitteln des Protokollauszugs für Formular "OS.Tumorkonferenz.VarianteUKW"
+ *
+ * @since 0.0.2
+ */
+public class OsTumorkonferenzVarianteUkwToProtocolMapper implements ProcedureToProtocolMapper {
+
+ /**
+ * Wandelt eine Prozedur mit Formularnamen "OS.Tumorkonferenz.VarianteUKW" in ein {@link Optional} mit einer
+ * Zeichenkette oder im Fehlerfall in ein leeres Optional um.
+ * @param procedure Die Prozedur, für die eine Zusammenfassung ermittelt werden soll.
+ * @return Das {@link Optional} mit, im Erfolgsfall, der Zusammenfassung für die Prozedur.
+ */
+ @Override
+ public Optional apply(Procedure procedure) {
+ if ((!procedure.getFormName().equals("OS.Tumorkonferenz.VarianteUKW"))) {
+ throw new AssertionError("Procedure is not of form type 'OS.Tumorkonferenz.VarianteUKW'");
+ }
+
+
+ var fragestellung = procedure.getValue("Fragestellung");
+ var empfehlung = procedure.getValue("Empfehlung");
+
+ if (
+ null != fragestellung && !fragestellung.getString().isBlank()
+ && null != empfehlung && !empfehlung.getString().isBlank()
+ ) {
+ return Optional.of(String.format("Fragestellung:%n%s%n%nEmpfehlung:%n%s", fragestellung.getString().trim(), empfehlung.getString().trim()));
+ } else if (null != fragestellung && !fragestellung.getString().isBlank()) {
+ return Optional.of(fragestellung.getString().trim());
+ } else if (null != empfehlung && !empfehlung.getString().isBlank()) {
+ return Optional.of(empfehlung.getString().trim());
+ }
+ return Optional.empty();
+ }
+}
diff --git a/src/main/java/dev/dnpm/services/mtb/ProcedureToProtocolMapper.java b/src/main/java/dev/dnpm/services/mtb/ProcedureToProtocolMapper.java
new file mode 100644
index 0000000..bf3197a
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/mtb/ProcedureToProtocolMapper.java
@@ -0,0 +1,9 @@
+package dev.dnpm.services.mtb;
+
+import de.itc.onkostar.api.Procedure;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+@FunctionalInterface
+public interface ProcedureToProtocolMapper extends Function> {}
diff --git a/src/main/java/dev/dnpm/services/strahlentherapie/DefaultStrahlentherapieService.java b/src/main/java/dev/dnpm/services/strahlentherapie/DefaultStrahlentherapieService.java
new file mode 100644
index 0000000..96a70dd
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/strahlentherapie/DefaultStrahlentherapieService.java
@@ -0,0 +1,74 @@
+package dev.dnpm.services.strahlentherapie;
+
+import dev.dnpm.dto.EcogStatusWithDate;
+import dev.dnpm.services.SettingsService;
+import de.itc.onkostar.api.IOnkostarApi;
+import de.itc.onkostar.api.Patient;
+import de.itc.onkostar.api.Procedure;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Standardimplementierung des StrahlentherapieServices
+ *
+ * @since 0.6.0
+ */
+public class DefaultStrahlentherapieService implements StrahlentherapieService {
+
+ private static final String ECOG_FIELD = "ECOGvorTherapie";
+
+ private final IOnkostarApi onkostarApi;
+
+ private final SettingsService settingsService;
+
+ public DefaultStrahlentherapieService(final IOnkostarApi onkostarApi, final SettingsService settingsService) {
+ this.onkostarApi = onkostarApi;
+ this.settingsService = settingsService;
+ }
+
+ /**
+ * Ermittelt den letzten bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten
+ *
+ * @param patient Der zu verwendende Patient
+ * @return Der ECOG-Status als String oder leeres Optional
+ */
+ @Override
+ public Optional latestEcogStatus(Patient patient) {
+ return ecogStatus(patient).stream()
+ .max(Comparator.comparing(EcogStatusWithDate::getDate))
+ .map(EcogStatusWithDate::getStatus);
+ }
+
+ /**
+ * Ermittelt jeden bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten
+ *
+ * @param patient Der zu verwendende Patient
+ * @return Eine Liste mit Datum und ECOG-Status als String
+ */
+ @Override
+ public List ecogStatus(Patient patient) {
+ return patient.getDiseases().stream()
+ .flatMap(disease -> onkostarApi.getProceduresForDiseaseByForm(disease.getId(), getFormName()).stream())
+ .filter(procedure -> null != procedure.getStartDate())
+ .sorted(Comparator.comparing(Procedure::getStartDate))
+ .map(procedure -> {
+ try {
+ return new EcogStatusWithDate(procedure.getStartDate(), procedure.getValue(ECOG_FIELD).getString());
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ private String getFormName() {
+ return settingsService
+ .getSetting("strahlentherapieform")
+ .orElse("OS.Strahlentherapie");
+ }
+}
diff --git a/src/main/java/dev/dnpm/services/strahlentherapie/StrahlentherapieService.java b/src/main/java/dev/dnpm/services/strahlentherapie/StrahlentherapieService.java
new file mode 100644
index 0000000..80e2db2
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/strahlentherapie/StrahlentherapieService.java
@@ -0,0 +1,10 @@
+package dev.dnpm.services.strahlentherapie;
+
+import dev.dnpm.services.TherapieMitEcogService;
+
+/**
+ * Service für Systemtherapieformulare
+ *
+ * @since 0.6.0
+ */
+public interface StrahlentherapieService extends TherapieMitEcogService {}
diff --git a/src/main/java/dev/dnpm/services/systemtherapie/DefaultSystemtherapieService.java b/src/main/java/dev/dnpm/services/systemtherapie/DefaultSystemtherapieService.java
new file mode 100644
index 0000000..143195e
--- /dev/null
+++ b/src/main/java/dev/dnpm/services/systemtherapie/DefaultSystemtherapieService.java
@@ -0,0 +1,98 @@
+package dev.dnpm.services.systemtherapie;
+
+import dev.dnpm.dto.EcogStatusWithDate;
+import dev.dnpm.services.SettingsService;
+import de.itc.onkostar.api.IOnkostarApi;
+import de.itc.onkostar.api.Patient;
+import de.itc.onkostar.api.Procedure;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Standardimplementierung des Systemtherapieservices
+ *
+ * @since 0.2.0
+ */
+public class DefaultSystemtherapieService implements SystemtherapieService {
+
+ private static final String ECOG_FIELD = "ECOGvorTherapie";
+
+ private final IOnkostarApi onkostarApi;
+
+ private final SettingsService settingsService;
+
+ public DefaultSystemtherapieService(final IOnkostarApi onkostarApi, final SettingsService settingsService) {
+ this.onkostarApi = onkostarApi;
+ this.settingsService = settingsService;
+ }
+
+ /**
+ * Ermittelt eine Zusammenfassung der systemischen Therapien für eine Erkrankung
+ *
+ * @param diseaseId Die ID der Erkrankung
+ * @return Zusammenfassung der systemischen Therapien
+ */
+ @Override
+ public List