From ccdf73ca89c06ac49b47e52113d2b177a314f311 Mon Sep 17 00:00:00 2001 From: Obinna Henry <55580796+obinna-h-n@users.noreply.github.com> Date: Sun, 22 Mar 2026 23:28:48 +0100 Subject: [PATCH] add enums, modify entities, dtos required for exposure form redesign --- .../symeda/sormas/api/epidata/EpiDataDto.java | 11 + .../sormas/api/exposure/AnimalCategory.java | 29 +++ .../sormas/api/exposure/ExposureCategory.java | 35 ++++ .../api/exposure/ExposureContactFactor.java | 120 +++++++++++ .../sormas/api/exposure/ExposureDto.java | 150 ++++++++++++++ .../exposure/ExposureProtectiveMeasure.java | 129 ++++++++++++ .../sormas/api/exposure/ExposureSetting.java | 66 ++++++ .../api/exposure/ExposureSubSetting.java | 90 +++++++++ .../exposure/FomiteTransmissionLocation.java | 29 +++ sormas-api/src/main/resources/enum.properties | 114 +++++++++++ .../sormas/backend/epidata/EpiData.java | 12 ++ .../backend/epidata/EpiDataFacadeEjb.java | 57 ++++++ .../sormas/backend/exposure/Exposure.java | 190 ++++++++++++++++++ .../src/main/resources/sql/sormas_schema.sql | 100 +++++++++ 14 files changed, 1132 insertions(+) create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/exposure/AnimalCategory.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureCategory.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureContactFactor.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureProtectiveMeasure.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureSetting.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureSubSetting.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/exposure/FomiteTransmissionLocation.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/epidata/EpiDataDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/epidata/EpiDataDto.java index 8d0bafeb2fe..bad504a8f2a 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/epidata/EpiDataDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/epidata/EpiDataDto.java @@ -68,6 +68,7 @@ public class EpiDataDto extends PseudonymizableDto { public static final String INFECTION_SOURCE_TEXT = "infectionSourceText"; public static final String IMPORTED_CASE = "importedCase"; public static final String COUNTRY = "country"; + public static final String OTHER_DETAILS = "otherDetails"; private YesNoUnknown exposureDetailsKnown; private YesNoUnknown activityAsCaseDetailsKnown; @@ -140,6 +141,8 @@ public class EpiDataDto extends PseudonymizableDto { @Valid private List activitiesAsCase = new ArrayList<>(); + private String otherDetails; + public YesNoUnknown getExposureDetailsKnown() { return exposureDetailsKnown; } @@ -293,6 +296,14 @@ public void setCountry(CountryReferenceDto country) { this.country = country; } + public String getOtherDetails() { + return otherDetails; + } + + public void setOtherDetails(String otherDetails) { + this.otherDetails = otherDetails; + } + @Override public EpiDataDto clone() throws CloneNotSupportedException { EpiDataDto clone = (EpiDataDto) super.clone(); diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/exposure/AnimalCategory.java b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/AnimalCategory.java new file mode 100644 index 00000000000..659db14bcd9 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/AnimalCategory.java @@ -0,0 +1,29 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.api.exposure; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum AnimalCategory { + + DOMESTIC, + WILD; + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureCategory.java b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureCategory.java new file mode 100644 index 00000000000..01a6ff2b7cb --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureCategory.java @@ -0,0 +1,35 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.api.exposure; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum ExposureCategory { + + AIR_BORNE, + ANIMAL_CONTACT, + DIRECT_CONTACT, + FOMITE_TRANSMISSION, + FOOD_BORNE, + VECTOR_BORNE, + VERTICAL_TRANSMISSION, + WATER_BORNE; + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureContactFactor.java b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureContactFactor.java new file mode 100644 index 00000000000..d089809ef8c --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureContactFactor.java @@ -0,0 +1,120 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.api.exposure; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum ExposureContactFactor { + + DURATION_OF_EXPOSURE(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + PROXIMITY_TO_SOURCE(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + TYPE_OF_ACTIVITY(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + POOR_VENTILATION(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + + PROXIMITY_AND_DURATION(ExposureCategory.AIR_BORNE, ExposureSetting.OUTDOOR), + WIND_AND_AIRFLOW(ExposureCategory.AIR_BORNE, ExposureSetting.OUTDOOR), + DENSITY_OF_PEOPLE(ExposureCategory.AIR_BORNE, ExposureSetting.OUTDOOR), + + SKIN_CONTACT(ExposureCategory.DIRECT_CONTACT, ExposureSetting.PERSON_TO_PERSON), + BODY_FLUIDS(ExposureCategory.DIRECT_CONTACT, ExposureSetting.PERSON_TO_PERSON), + + BUTCHERING(ExposureCategory.ANIMAL_CONTACT, null), + COOKING(ExposureCategory.ANIMAL_CONTACT, null), + TOUCHING_CONTACT_WITH_FLUIDS(ExposureCategory.ANIMAL_CONTACT, null), + SCRATCHES_BITES_LICKING(ExposureCategory.ANIMAL_CONTACT, null), + + SHARED_SURFACES(ExposureCategory.FOMITE_TRANSMISSION, null), + + OUTDOOR_ACTIVITIES(ExposureCategory.VECTOR_BORNE, ExposureSetting.OUTDOOR), + STANDING_WATER_PROXIMITY(ExposureCategory.VECTOR_BORNE, ExposureSetting.OUTDOOR), + HIGH_MOSQUITO_ACTIVITY_REGIONS(ExposureCategory.VECTOR_BORNE, ExposureSetting.OUTDOOR), + + UNPROTECTED_HOUSEHOLD(ExposureCategory.VECTOR_BORNE, ExposureSetting.INDOOR), + + MOSQUITO_ACTIVITY_TIME_OF_DAY(ExposureCategory.VECTOR_BORNE, ExposureSetting.MOSQUITO_BORNE), + CLOTHING_COVERAGE(ExposureCategory.VECTOR_BORNE, ExposureSetting.MOSQUITO_BORNE), + + DURATION_OUTDOORS(ExposureCategory.VECTOR_BORNE, ExposureSetting.TICK_BORNE), + EXPOSED_SKIN(ExposureCategory.VECTOR_BORNE, ExposureSetting.TICK_BORNE), + + DRINKING_CONTAMINATED_WATER(ExposureCategory.WATER_BORNE, ExposureSetting.DRINKING_WATER), + ICE_AND_FOOD_PREPARATION(ExposureCategory.WATER_BORNE, ExposureSetting.DRINKING_WATER), + + SWALLOWING_WATER(ExposureCategory.WATER_BORNE, ExposureSetting.RECREATIONAL_WATER), + CONTACT_WITH_OPEN_WOUNDS(ExposureCategory.WATER_BORNE, ExposureSetting.RECREATIONAL_WATER), + + EGG(ExposureCategory.FOOD_BORNE, null), + MEAT(ExposureCategory.FOOD_BORNE, null), + FISH_SEAFOOD(ExposureCategory.FOOD_BORNE, null), + DAIRY(ExposureCategory.FOOD_BORNE, null), + FRUIT(ExposureCategory.FOOD_BORNE, null), + RAW_VEGETABLES(ExposureCategory.FOOD_BORNE, null), + + UNKNOWN(null, null), + OTHER(null, null); + + private final ExposureCategory category; + private final ExposureSetting setting; + + ExposureContactFactor(ExposureCategory category, ExposureSetting setting) { + this.category = category; + this.setting = setting; + } + + public ExposureCategory getCategory() { + return category; + } + + public ExposureSetting getSetting() { + return setting; + } + + /** + * Returns contact factors for a given category and setting combination. + *
    + *
  • Factors with matching category AND setting are included.
  • + *
  • Factors with matching category and null setting (category-wide) are included.
  • + *
  • UNKNOWN and OTHER (null category) are always included.
  • + *
+ * + * Because INDOOR/OUTDOOR are shared settings, the category parameter + * ensures (AIR_BORNE, INDOOR) and (VECTOR_BORNE, INDOOR) return different factors. + */ + public static List getValues(ExposureCategory category, ExposureSetting setting) { + if (category == null) { + return Collections.emptyList(); + } + return Arrays.stream(values()).filter(cf -> { + if (cf.category == null) { + return true; // UNKNOWN, OTHER + } + if (cf.category != category) { + return false; + } + return cf.setting == null || cf.setting == setting; + }).collect(Collectors.toList()); + } + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureDto.java index b846050f0c7..7d9eb8ccf94 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureDto.java @@ -16,6 +16,8 @@ package de.symeda.sormas.api.exposure; import java.util.Date; +import java.util.HashSet; +import java.util.Set; import javax.validation.Valid; import javax.validation.constraints.NotNull; @@ -120,6 +122,19 @@ public class ExposureDto extends PseudonymizableDto { public static final String RAW_FOOD_CONTACT = "rawFoodContact"; public static final String RAW_FOOD_CONTACT_TEXT = "rawFoodContactText"; public static final String SYMPTOMATIC_INDIVIDUAL_TEXT = "symptomaticIndividualText"; + public static final String EXPOSURE_CATEGORY = "exposureCategory"; + public static final String EXPOSURE_SETTING = "exposureSetting"; + public static final String EXPOSURE_SETTING_DETAILS = "exposureSettingDetails"; + public static final String EXPOSURE_SUB_SETTING_DETAILS = "exposureSubSettingDetails"; + public static final String CONTACT_FACTOR_DETAILS = "contactFactorDetails"; + public static final String PROTECTIVE_MEASURE_DETAILS = "protectiveMeasureDetails"; + public static final String EXPOSURE_COMMENT = "exposureComment"; + public static final String ANIMAL_CATEGORY = "animalCategory"; + public static final String ANIMAL_CATEGORY_DETAILS = "animalCategoryDetails"; + public static final String FOMITE_TRANSMISSION_LOCATION = "fomiteTransmissionLocation"; + public static final String SUB_SETTINGS = "subSettings"; + public static final String CONTACT_FACTORS = "contactFactors"; + public static final String PROTECTIVE_MEASURES = "protectiveMeasures"; @SensitiveData private UserReferenceDto reportingUser; @@ -401,6 +416,24 @@ public class ExposureDto extends PseudonymizableDto { @Size(max = FieldConstraints.CHARACTER_LIMIT_TEXT, message = Validations.textTooLong) private String symptomaticIndividualText; + private ExposureCategory exposureCategory; + private ExposureSetting exposureSetting; + private String exposureSettingDetails; + private String exposureSubSettingDetails; + private String contactFactorDetails; + private String protectiveMeasureDetails; + private String exposureComment; + + private AnimalCondition conditionOfAnimal; + private AnimalCategory animalCategory; + private String animalCategoryDetails; + + private FomiteTransmissionLocation fomiteTransmissionLocation; + + private Set subSettings; + private Set contactFactors; + private Set protectiveMeasures; + public static ExposureDto build(ExposureType exposureType) { ExposureDto exposure = new ExposureDto(); @@ -408,6 +441,11 @@ public static ExposureDto build(ExposureType exposureType) { exposure.setExposureType(exposureType); LocationDto location = LocationDto.build(); exposure.setLocation(location); + + exposure.setSubSettings(new HashSet<>()); + exposure.setContactFactors(new HashSet<>()); + exposure.setProtectiveMeasures(new HashSet<>()); + return exposure; } @@ -947,6 +985,118 @@ public void setSymptomaticIndividualText(String symptomaticIndividualText) { this.symptomaticIndividualText = symptomaticIndividualText; } + public ExposureCategory getExposureCategory() { + return exposureCategory; + } + + public void setExposureCategory(ExposureCategory exposureCategory) { + this.exposureCategory = exposureCategory; + } + + public ExposureSetting getExposureSetting() { + return exposureSetting; + } + + public void setExposureSetting(ExposureSetting exposureSetting) { + this.exposureSetting = exposureSetting; + } + + public String getExposureSettingDetails() { + return exposureSettingDetails; + } + + public void setExposureSettingDetails(String exposureSettingDetails) { + this.exposureSettingDetails = exposureSettingDetails; + } + + public String getExposureSubSettingDetails() { + return exposureSubSettingDetails; + } + + public void setExposureSubSettingDetails(String exposureSubSettingDetails) { + this.exposureSubSettingDetails = exposureSubSettingDetails; + } + + public String getContactFactorDetails() { + return contactFactorDetails; + } + + public void setContactFactorDetails(String contactFactorDetails) { + this.contactFactorDetails = contactFactorDetails; + } + + public String getProtectiveMeasureDetails() { + return protectiveMeasureDetails; + } + + public void setProtectiveMeasureDetails(String protectiveMeasureDetails) { + this.protectiveMeasureDetails = protectiveMeasureDetails; + } + + public String getExposureComment() { + return exposureComment; + } + + public void setExposureComment(String exposureComment) { + this.exposureComment = exposureComment; + } + + public AnimalCondition getConditionOfAnimal() { + return conditionOfAnimal; + } + + public void setConditionOfAnimal(AnimalCondition conditionOfAnimal) { + this.conditionOfAnimal = conditionOfAnimal; + } + + public AnimalCategory getAnimalCategory() { + return animalCategory; + } + + public void setAnimalCategory(AnimalCategory animalCategory) { + this.animalCategory = animalCategory; + } + + public String getAnimalCategoryDetails() { + return animalCategoryDetails; + } + + public void setAnimalCategoryDetails(String animalCategoryDetails) { + this.animalCategoryDetails = animalCategoryDetails; + } + + public FomiteTransmissionLocation getFomiteTransmissionLocation() { + return fomiteTransmissionLocation; + } + + public void setFomiteTransmissionLocation(FomiteTransmissionLocation fomiteTransmissionLocation) { + this.fomiteTransmissionLocation = fomiteTransmissionLocation; + } + + public Set getSubSettings() { + return subSettings; + } + + public void setSubSettings(Set subSettings) { + this.subSettings = subSettings; + } + + public Set getContactFactors() { + return contactFactors; + } + + public void setContactFactors(Set contactFactors) { + this.contactFactors = contactFactors; + } + + public Set getProtectiveMeasures() { + return protectiveMeasures; + } + + public void setProtectiveMeasures(Set protectiveMeasures) { + this.protectiveMeasures = protectiveMeasures; + } + @Override public ExposureDto clone() throws CloneNotSupportedException { ExposureDto clone = (ExposureDto) super.clone(); diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureProtectiveMeasure.java b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureProtectiveMeasure.java new file mode 100644 index 00000000000..245a0e5bf97 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureProtectiveMeasure.java @@ -0,0 +1,129 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.api.exposure; + +import static de.symeda.sormas.api.exposure.ExposureCategory.AIR_BORNE; +import static de.symeda.sormas.api.exposure.ExposureCategory.ANIMAL_CONTACT; +import static de.symeda.sormas.api.exposure.ExposureCategory.DIRECT_CONTACT; +import static de.symeda.sormas.api.exposure.ExposureCategory.FOMITE_TRANSMISSION; +import static de.symeda.sormas.api.exposure.ExposureCategory.FOOD_BORNE; +import static de.symeda.sormas.api.exposure.ExposureCategory.VECTOR_BORNE; +import static de.symeda.sormas.api.exposure.ExposureCategory.VERTICAL_TRANSMISSION; +import static de.symeda.sormas.api.exposure.ExposureCategory.WATER_BORNE; +import static de.symeda.sormas.api.exposure.ExposureSetting.DRINKING_WATER; +import static de.symeda.sormas.api.exposure.ExposureSetting.INDOOR; +import static de.symeda.sormas.api.exposure.ExposureSetting.MOSQUITO_BORNE; +import static de.symeda.sormas.api.exposure.ExposureSetting.OUTDOOR; +import static de.symeda.sormas.api.exposure.ExposureSetting.PERSON_TO_PERSON; +import static de.symeda.sormas.api.exposure.ExposureSetting.RECREATIONAL_WATER; +import static de.symeda.sormas.api.exposure.ExposureSetting.TICK_BORNE; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum ExposureProtectiveMeasure { + + WEARING_MASK(EnumSet.of(AIR_BORNE), EnumSet.of(INDOOR, OUTDOOR)), + DISTANCE_1_5M(EnumSet.of(AIR_BORNE), EnumSet.of(INDOOR, OUTDOOR)), + + FACE_TO_FACE_15MIN(EnumSet.of(AIR_BORNE), EnumSet.of(INDOOR)), + VENTILATION_HEPA(EnumSet.of(AIR_BORNE), EnumSet.of(INDOOR)), + + HAND_HYGIENE(EnumSet.of(DIRECT_CONTACT), EnumSet.of(PERSON_TO_PERSON)), + AVOID_TOUCHING_FACE(EnumSet.of(DIRECT_CONTACT), EnumSet.of(PERSON_TO_PERSON)), + SAFE_SEX(EnumSet.of(DIRECT_CONTACT), EnumSet.of(PERSON_TO_PERSON)), + + WEARING_PPE(EnumSet.of(DIRECT_CONTACT, ANIMAL_CONTACT, FOMITE_TRANSMISSION), EnumSet.of(PERSON_TO_PERSON)), + VACCINATION(EnumSet.of(ANIMAL_CONTACT, FOMITE_TRANSMISSION, VERTICAL_TRANSMISSION), EnumSet.noneOf(ExposureSetting.class)), + + HAND_WASHING(EnumSet.of(ANIMAL_CONTACT, FOMITE_TRANSMISSION), EnumSet.noneOf(ExposureSetting.class)), + WOUND_WASHING(EnumSet.of(ANIMAL_CONTACT), EnumSet.noneOf(ExposureSetting.class)), + + INSECT_REPELLENT(EnumSet.of(VECTOR_BORNE), EnumSet.of(OUTDOOR)), + HERBS(EnumSet.of(VECTOR_BORNE), EnumSet.of(OUTDOOR)), + COILS(EnumSet.of(VECTOR_BORNE), EnumSet.of(OUTDOOR)), + + SLEEPING_UNDER_BEDNET(EnumSet.of(VECTOR_BORNE), EnumSet.of(INDOOR)), + INDOOR_SPRAYING(EnumSet.of(VECTOR_BORNE), EnumSet.of(INDOOR)), + AIR_CONDITION(EnumSet.of(VECTOR_BORNE), EnumSet.of(INDOOR)), + + PROTECTIVE_CLOTHING(EnumSet.of(VECTOR_BORNE), EnumSet.of(MOSQUITO_BORNE, TICK_BORNE)), + + ENVIRONMENTAL_CONTROL(EnumSet.of(VECTOR_BORNE), EnumSet.of(MOSQUITO_BORNE)), + + TICK_PREVENTION(EnumSet.of(VECTOR_BORNE), EnumSet.of(TICK_BORNE)), + REGULAR_CHECKS(EnumSet.of(VECTOR_BORNE), EnumSet.of(TICK_BORNE)), + + WATER_PURIFICATION(EnumSet.of(WATER_BORNE), EnumSet.of(DRINKING_WATER)), + SAFE_WATER_SOURCES(EnumSet.of(WATER_BORNE), EnumSet.of(DRINKING_WATER)), + AVOID_RAW_FOODS(EnumSet.of(WATER_BORNE), EnumSet.of(DRINKING_WATER)), + + NO_SWIMMING_CONTAMINATED(EnumSet.of(WATER_BORNE), EnumSet.of(RECREATIONAL_WATER)), + SHOWER_AFTER_SWIMMING(EnumSet.of(WATER_BORNE), EnumSet.of(RECREATIONAL_WATER)), + WOUND_COVERAGE(EnumSet.of(WATER_BORNE), EnumSet.of(RECREATIONAL_WATER)), + BATHING_CONTAMINATED_WATER(EnumSet.of(WATER_BORNE), EnumSet.of(RECREATIONAL_WATER)), + + WELL_COOKED(EnumSet.of(FOOD_BORNE), EnumSet.noneOf(ExposureSetting.class)), + COLD_HOT_CHAIN(EnumSet.of(FOOD_BORNE), EnumSet.noneOf(ExposureSetting.class)), + WASHED(EnumSet.of(FOOD_BORNE), EnumSet.noneOf(ExposureSetting.class)), + + MEDICATION(EnumSet.of(VERTICAL_TRANSMISSION), EnumSet.noneOf(ExposureSetting.class)), + C_SECTION(EnumSet.of(VERTICAL_TRANSMISSION), EnumSet.noneOf(ExposureSetting.class)), + + OTHER(EnumSet.noneOf(ExposureCategory.class), EnumSet.noneOf(ExposureSetting.class)); + + private final Set categories; + private final Set settings; + + ExposureProtectiveMeasure(Set categories, Set settings) { + this.categories = categories; + this.settings = settings; + } + + public Set getCategories() { + return categories; + } + + public Set getSettings() { + return settings; + } + + public static List getValues(ExposureCategory category, ExposureSetting setting) { + if (category == null) { + return Collections.emptyList(); + } + return Arrays.stream(values()).filter(pm -> { + if (pm.categories.isEmpty()) { + return true; + } + if (!pm.categories.contains(category)) { + return false; + } + return pm.settings.isEmpty() || pm.settings.contains(setting); + }).collect(Collectors.toList()); + } + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureSetting.java b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureSetting.java new file mode 100644 index 00000000000..19c3a1b496f --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureSetting.java @@ -0,0 +1,66 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.api.exposure; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum ExposureSetting { + + INDOOR(ExposureCategory.AIR_BORNE, ExposureCategory.VECTOR_BORNE), + OUTDOOR(ExposureCategory.AIR_BORNE, ExposureCategory.VECTOR_BORNE), + + PERSON_TO_PERSON(ExposureCategory.DIRECT_CONTACT), + + MOSQUITO_BORNE(ExposureCategory.VECTOR_BORNE), + TICK_BORNE(ExposureCategory.VECTOR_BORNE), + + DRINKING_WATER(ExposureCategory.WATER_BORNE), + RECREATIONAL_WATER(ExposureCategory.WATER_BORNE), + + PREGNANCY_OR_DELIVERY(ExposureCategory.VERTICAL_TRANSMISSION), + + OTHER(ExposureCategory.AIR_BORNE), + UNKNOWN(ExposureCategory.AIR_BORNE); + + private final Set categories; + + ExposureSetting(ExposureCategory... categories) { + this.categories = categories.length > 0 ? EnumSet.copyOf(Arrays.asList(categories)) : EnumSet.noneOf(ExposureCategory.class); + } + + public Set getCategories() { + return categories; + } + + public static List getValues(ExposureCategory category) { + if (category == null) { + return Collections.emptyList(); + } + return Arrays.stream(values()).filter(s -> s.categories.contains(category)).collect(Collectors.toList()); + } + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureSubSetting.java b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureSubSetting.java new file mode 100644 index 00000000000..2678517adf1 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureSubSetting.java @@ -0,0 +1,90 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.api.exposure; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum ExposureSubSetting { + + CLOSED_POORLY_VENTILATED(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + SHARED_HIGH_OCCUPANCY(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + ENCLOSED_LIMITED_CIRCULATION(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + VEHICLES(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + HEALTHCARE_SETTINGS(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + TEMPORARY_SHELTERS(ExposureCategory.AIR_BORNE, ExposureSetting.INDOOR), + + CROWDED_OUTDOOR_LIMITED_AIRFLOW(ExposureCategory.AIR_BORNE, ExposureSetting.OUTDOOR), + + CLOSE_PHYSICAL_CONTACT(ExposureCategory.DIRECT_CONTACT, ExposureSetting.PERSON_TO_PERSON), + HIGH_TOUCH_ENVIRONMENTS(ExposureCategory.DIRECT_CONTACT, ExposureSetting.PERSON_TO_PERSON), + SEXUAL_ACTIVITY(ExposureCategory.DIRECT_CONTACT, ExposureSetting.PERSON_TO_PERSON), + + STANDING_WATER_AREAS(ExposureCategory.VECTOR_BORNE, ExposureSetting.MOSQUITO_BORNE), + HIGH_MOSQUITO_ACTIVITY_REGIONS(ExposureCategory.VECTOR_BORNE, ExposureSetting.MOSQUITO_BORNE), + + FORESTED_GRASSY_RURAL(ExposureCategory.VECTOR_BORNE, ExposureSetting.TICK_BORNE), + WILDLIFE_RESERVOIR_AREAS(ExposureCategory.VECTOR_BORNE, ExposureSetting.TICK_BORNE), + + EATING_AT_HOME(ExposureCategory.FOOD_BORNE, null), + EATING_OUTSIDE(ExposureCategory.FOOD_BORNE, null), + + UNKNOWN(null, null), + OTHER(null, null); + + private final ExposureCategory category; + private final ExposureSetting setting; + + ExposureSubSetting(ExposureCategory category, ExposureSetting setting) { + this.category = category; + this.setting = setting; + } + + public ExposureCategory getCategory() { + return category; + } + + public ExposureSetting getSetting() { + return setting; + } + + /** + * Returns sub-settings for a given category and setting combination. + * UNKNOWN and OTHER are included when the combination has specific sub-settings. + * Returns empty list if no specific sub-settings exist for the combination. + */ + public static List getValues(ExposureCategory category, ExposureSetting setting) { + if (category == null || setting == null) { + return Collections.emptyList(); + } + boolean hasSpecific = Arrays.stream(values()).anyMatch(s -> s.category == category && s.setting == setting); + if (!hasSpecific) { + return Collections.emptyList(); + } + return Arrays.stream(values()) + .filter(s -> (s.category == category && s.setting == setting) || s.category == null) + .collect(Collectors.toList()); + } + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/exposure/FomiteTransmissionLocation.java b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/FomiteTransmissionLocation.java new file mode 100644 index 00000000000..54e9fc1e3ff --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/exposure/FomiteTransmissionLocation.java @@ -0,0 +1,29 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2024 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.api.exposure; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum FomiteTransmissionLocation { + + INSIDE_HOME, + OUTSIDE; + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-api/src/main/resources/enum.properties b/sormas-api/src/main/resources/enum.properties index 2e03986ab1a..e3fd277ef55 100644 --- a/sormas-api/src/main/resources/enum.properties +++ b/sormas-api/src/main/resources/enum.properties @@ -2901,3 +2901,117 @@ EpipulseDiseaseRef.PERT = Pertussis EpipulseDiseaseRef.MEAS = Measles EpipulseDiseaseRef.PNEU = Invasive Pneumococcal Infection EpipulseDiseaseRef.MENI = Invasive Meningococcal Infection + +# ExposureCategory +ExposureCategory.AIR_BORNE=Air-Borne +ExposureCategory.ANIMAL_CONTACT=Animal contact +ExposureCategory.DIRECT_CONTACT=Direct contact +ExposureCategory.FOMITE_TRANSMISSION=Fomite transmission +ExposureCategory.FOOD_BORNE=Food-borne +ExposureCategory.VECTOR_BORNE=Vector-borne +ExposureCategory.VERTICAL_TRANSMISSION=Vertical transmission +ExposureCategory.WATER_BORNE=Water-borne + +# ExposureSetting +ExposureSetting.INDOOR=Indoor +ExposureSetting.OUTDOOR=Outdoor +ExposureSetting.PERSON_TO_PERSON=Person to person +ExposureSetting.MOSQUITO_BORNE=Mosquito-borne +ExposureSetting.TICK_BORNE=Tick-borne +ExposureSetting.DRINKING_WATER=Drinking water +ExposureSetting.RECREATIONAL_WATER=Recreational water +ExposureSetting.PREGNANCY_OR_DELIVERY=Pregnancy or delivery +ExposureSetting.OTHER=Other +ExposureSetting.UNKNOWN=Unknown + +# ExposureSubSetting +ExposureSubSetting.CLOSED_POORLY_VENTILATED=Closed/ Poorly ventilated spaces +ExposureSubSetting.SHARED_HIGH_OCCUPANCY=Shared spaces with high occupancy +ExposureSubSetting.ENCLOSED_LIMITED_CIRCULATION=Enclosed spaces with limited air circulation +ExposureSubSetting.VEHICLES=Vehicles +ExposureSubSetting.HEALTHCARE_SETTINGS=Healthcare settings +ExposureSubSetting.TEMPORARY_SHELTERS=Temporary shelters +ExposureSubSetting.CROWDED_OUTDOOR_LIMITED_AIRFLOW=Crowded outdoor areas with limited airflow +ExposureSubSetting.CLOSE_PHYSICAL_CONTACT=Close physical contact +ExposureSubSetting.HIGH_TOUCH_ENVIRONMENTS=High-touch environments (e.g. childcare centers, healthcare facilities) +ExposureSubSetting.SEXUAL_ACTIVITY=Sexual +ExposureSubSetting.STANDING_WATER_AREAS=Areas with standing water (e.g. ponds, swamps) +ExposureSubSetting.HIGH_MOSQUITO_ACTIVITY_REGIONS=Regions with high mosquito activity, especially in warm climates +ExposureSubSetting.FORESTED_GRASSY_RURAL=Forested, grassy, or rural areas +ExposureSubSetting.WILDLIFE_RESERVOIR_AREAS=Areas with wildlife reservoirs for ticks +ExposureSubSetting.EATING_AT_HOME=Eating at home +ExposureSubSetting.EATING_OUTSIDE=Eating outside +ExposureSubSetting.UNKNOWN=Unknown +ExposureSubSetting.OTHER=Other + +# ExposureContactFactor +ExposureContactFactor.DURATION_OF_EXPOSURE=Duration of Exposure +ExposureContactFactor.PROXIMITY_TO_SOURCE=Proximity to Source +ExposureContactFactor.TYPE_OF_ACTIVITY=Type of Activity +ExposureContactFactor.POOR_VENTILATION=Poor ventilation +ExposureContactFactor.PROXIMITY_AND_DURATION=Proximity and Duration: Extended face-to-face contact within 1.5 meters can still pose a risk, especially in large gatherings +ExposureContactFactor.WIND_AND_AIRFLOW=Wind and Airflow: Natural ventilation reduces risk, but crowded settings with close proximity can still allow transmission +ExposureContactFactor.DENSITY_OF_PEOPLE=Density of People: Higher density increases exposure risk, even outdoors +ExposureContactFactor.SKIN_CONTACT=Direct skin contact +ExposureContactFactor.BODY_FLUIDS=Body fluids +ExposureContactFactor.BUTCHERING=Butchering +ExposureContactFactor.COOKING=Cooking +ExposureContactFactor.TOUCHING_CONTACT_WITH_FLUIDS=Touching/ Being in contact with fluids +ExposureContactFactor.SCRATCHES_BITES_LICKING=Scratches/ Bites/ Licking +ExposureContactFactor.SHARED_SURFACES=Shared Surfaces: Touching contaminated surfaces (Work, Public transport, Supermarket, Gym) +ExposureContactFactor.OUTDOOR_ACTIVITIES=Outdoor activities +ExposureContactFactor.STANDING_WATER_PROXIMITY=Areas with standing water (e.g., ponds, swamps) +ExposureContactFactor.HIGH_MOSQUITO_ACTIVITY_REGIONS=Regions with high mosquito activity, especially in warm climates +ExposureContactFactor.UNPROTECTED_HOUSEHOLD=Unprotected household +ExposureContactFactor.MOSQUITO_ACTIVITY_TIME_OF_DAY=Mosquito activity (morning, daytime, afternoon, evening, night) +ExposureContactFactor.CLOTHING_COVERAGE=Clothing Coverage: Exposed skin increases bite risk +ExposureContactFactor.DURATION_OUTDOORS=Duration Outdoors: Longer time in tick-prone areas increases risk +ExposureContactFactor.EXPOSED_SKIN=Exposed Skin: Contact with vegetation can transfer ticks +ExposureContactFactor.DRINKING_CONTAMINATED_WATER=Drinking contaminated water +ExposureContactFactor.ICE_AND_FOOD_PREPARATION=Ice and Food Preparation +ExposureContactFactor.SWALLOWING_WATER=Swallowing Water +ExposureContactFactor.CONTACT_WITH_OPEN_WOUNDS=Contact with open wounds +ExposureContactFactor.EGG=Egg +ExposureContactFactor.MEAT=Meat +ExposureContactFactor.FISH_SEAFOOD=Fish/Seafood +ExposureContactFactor.DAIRY=Dairy +ExposureContactFactor.FRUIT=Fruit +ExposureContactFactor.RAW_VEGETABLES=Raw Vegetables +ExposureContactFactor.UNKNOWN=Unknown +ExposureContactFactor.OTHER=Other + +# ExposureProtectiveMeasure +ExposureProtectiveMeasure.WEARING_MASK=Wearing mask +ExposureProtectiveMeasure.DISTANCE_1_5M= > 1.5 m distance +ExposureProtectiveMeasure.FACE_TO_FACE_15MIN= > 15 minutes face-to-face contact +ExposureProtectiveMeasure.VENTILATION_HEPA=Ventilation: Open windows, air purifiers with HEPA filters +ExposureProtectiveMeasure.HAND_HYGIENE=Hand Hygiene +ExposureProtectiveMeasure.AVOID_TOUCHING_FACE=Avoid Touching Face +ExposureProtectiveMeasure.SAFE_SEX=Safe sex (e.g. use of condoms) +ExposureProtectiveMeasure.WEARING_PPE=Wearing PPE +ExposureProtectiveMeasure.VACCINATION=Vaccination +ExposureProtectiveMeasure.HAND_WASHING=Handwashing +ExposureProtectiveMeasure.WOUND_WASHING=Wound washing +ExposureProtectiveMeasure.INSECT_REPELLENT=Insect repellent +ExposureProtectiveMeasure.HERBS=Herbs +ExposureProtectiveMeasure.COILS=Coils +ExposureProtectiveMeasure.SLEEPING_UNDER_BEDNET=Sleeping under a bednet +ExposureProtectiveMeasure.INDOOR_SPRAYING=Indoor spraying +ExposureProtectiveMeasure.AIR_CONDITION=Air condition +ExposureProtectiveMeasure.PROTECTIVE_CLOTHING=Protective Clothing +ExposureProtectiveMeasure.ENVIRONMENTAL_CONTROL=Environmental Control e.g. Eliminate standing water near residences +ExposureProtectiveMeasure.TICK_PREVENTION=Tick Prevention: Use permethrin-treated clothing and DEET repellents +ExposureProtectiveMeasure.REGULAR_CHECKS=Regular Checks: Check for ticks after outdoor activities +ExposureProtectiveMeasure.WATER_PURIFICATION=Water Purification: Boiling, filtering, or chlorinating water +ExposureProtectiveMeasure.SAFE_WATER_SOURCES=Safe water Sources: Rely on bottled or tested water sources +ExposureProtectiveMeasure.AVOID_RAW_FOODS=Raw Foods: Avoid uncooked foods prepared with unsafe water +ExposureProtectiveMeasure.NO_SWIMMING_CONTAMINATED=No Swimming in contaminated Waters: Especially after heavy rainfall +ExposureProtectiveMeasure.SHOWER_AFTER_SWIMMING=Shower After Swimming: Rinse off any potential contaminants +ExposureProtectiveMeasure.WOUND_COVERAGE=Wound coverage: Use waterproof bandages for open cuts or sores +ExposureProtectiveMeasure.BATHING_CONTAMINATED_WATER=Bathing in contaminated Water +ExposureProtectiveMeasure.WELL_COOKED=Well cooked +ExposureProtectiveMeasure.COLD_HOT_CHAIN=Cold/ hot chain maintained +ExposureProtectiveMeasure.WASHED=Washed +ExposureProtectiveMeasure.MEDICATION=Medication +ExposureProtectiveMeasure.C_SECTION=C-Section +ExposureProtectiveMeasure.OTHER=Other diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/epidata/EpiData.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/epidata/EpiData.java index db3998f5f8f..70adc736fef 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/epidata/EpiData.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/epidata/EpiData.java @@ -51,6 +51,7 @@ public class EpiData extends AbstractDomainObject { public static final String CONTACT_WITH_SOURCE_CASE_KNOWN = "contactWithSourceCaseKnown"; public static final String EXPOSURES = "exposures"; public static final String ACTIVITIES_AS_CASE = "activitiesAsCase"; + public static final String OTHER_DETAILS = "otherDetails"; private YesNoUnknown exposureDetailsKnown; private YesNoUnknown activityAsCaseDetailsKnown; @@ -78,6 +79,8 @@ public class EpiData extends AbstractDomainObject { @NotExposedToApi private Date changeDateOfEmbeddedLists; + private String otherDetails; + @Enumerated(EnumType.STRING) public YesNoUnknown getExposureDetailsKnown() { return exposureDetailsKnown; @@ -247,4 +250,13 @@ public Country getCountry() { public void setCountry(Country country) { this.country = country; } + + @Column(columnDefinition = "text") + public String getOtherDetails() { + return otherDetails; + } + + public void setOtherDetails(String otherDetails) { + this.otherDetails = otherDetails; + } } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/epidata/EpiDataFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/epidata/EpiDataFacadeEjb.java index d1bfbb9dce2..feb073c54db 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/epidata/EpiDataFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/epidata/EpiDataFacadeEjb.java @@ -21,7 +21,10 @@ import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Optional; +import java.util.Set; import javax.ejb.EJB; import javax.ejb.LocalBean; @@ -30,7 +33,10 @@ import de.symeda.sormas.api.activityascase.ActivityAsCaseDto; import de.symeda.sormas.api.epidata.EpiDataDto; import de.symeda.sormas.api.epidata.EpiDataFacade; +import de.symeda.sormas.api.exposure.ExposureContactFactor; import de.symeda.sormas.api.exposure.ExposureDto; +import de.symeda.sormas.api.exposure.ExposureProtectiveMeasure; +import de.symeda.sormas.api.exposure.ExposureSubSetting; import de.symeda.sormas.api.utils.DataHelper; import de.symeda.sormas.backend.FacadeHelper; import de.symeda.sormas.backend.activityascase.ActivityAsCase; @@ -114,6 +120,7 @@ public EpiData fillOrBuildEntity(EpiDataDto source, EpiData target, boolean chec target.setInfectionSourceText(source.getInfectionSourceText()); target.setCountry(countryService.getByReferenceDto(source.getCountry())); target.setImportedCase(source.getImportedCase()); + target.setOtherDetails(source.getOtherDetails()); return target; } @@ -200,6 +207,39 @@ public Exposure fillOrBuildExposureEntity(ExposureDto source, Exposure target, b target.setRawFoodContactText(source.getRawFoodContactText()); target.setSymptomaticIndividualText(source.getSymptomaticIndividualText()); + target.setExposureCategory(source.getExposureCategory()); + target.setExposureSetting(source.getExposureSetting()); + target.setExposureSettingDetails(source.getExposureSettingDetails()); + target.setExposureSubSettingDetails(source.getExposureSubSettingDetails()); + target.setContactFactorDetails(source.getContactFactorDetails()); + target.setProtectiveMeasureDetails(source.getProtectiveMeasureDetails()); + target.setExposureComment(source.getExposureComment()); + target.setConditionOfAnimal(source.getConditionOfAnimal()); + target.setAnimalCategory(source.getAnimalCategory()); + target.setAnimalCategoryDetails(source.getAnimalCategoryDetails()); + target.setFomiteTransmissionLocation(source.getFomiteTransmissionLocation()); + + Set subSettings = Optional.of(target).map(Exposure::getSubSettings).orElseGet(HashSet::new); + subSettings.clear(); + if (source.getSubSettings() != null) { + subSettings.addAll(source.getSubSettings()); + } + target.setSubSettings(subSettings); + + Set contactFactors = Optional.of(target).map(Exposure::getContactFactors).orElseGet(HashSet::new); + contactFactors.clear(); + if (source.getContactFactors() != null) { + contactFactors.addAll(source.getContactFactors()); + } + target.setContactFactors(contactFactors); + + Set protectiveMeasures = Optional.of(target).map(Exposure::getProtectiveMeasures).orElseGet(HashSet::new); + protectiveMeasures.clear(); + if (source.getProtectiveMeasures() != null) { + protectiveMeasures.addAll(source.getProtectiveMeasures()); + } + target.setProtectiveMeasures(protectiveMeasures); + return target; } @@ -281,6 +321,7 @@ public static EpiDataDto toDto(EpiData epiData) { target.setInfectionSourceText(source.getInfectionSourceText()); target.setCountry(CountryFacadeEjb.toReferenceDto(source.getCountry())); target.setImportedCase(source.getImportedCase()); + target.setOtherDetails(source.getOtherDetails()); return target; } @@ -362,6 +403,22 @@ public static ExposureDto toExposureDto(Exposure source) { target.setRawFoodContactText(source.getRawFoodContactText()); target.setSymptomaticIndividualText(source.getSymptomaticIndividualText()); + target.setExposureCategory(source.getExposureCategory()); + target.setExposureSetting(source.getExposureSetting()); + target.setExposureSettingDetails(source.getExposureSettingDetails()); + target.setExposureSubSettingDetails(source.getExposureSubSettingDetails()); + target.setContactFactorDetails(source.getContactFactorDetails()); + target.setProtectiveMeasureDetails(source.getProtectiveMeasureDetails()); + target.setExposureComment(source.getExposureComment()); + target.setConditionOfAnimal(source.getConditionOfAnimal()); + target.setAnimalCategory(source.getAnimalCategory()); + target.setAnimalCategoryDetails(source.getAnimalCategoryDetails()); + target.setFomiteTransmissionLocation(source.getFomiteTransmissionLocation()); + + target.setSubSettings(new HashSet<>(source.getSubSettings())); + target.setContactFactors(new HashSet<>(source.getContactFactors())); + target.setProtectiveMeasures(new HashSet<>(source.getProtectiveMeasures())); + return target; } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/exposure/Exposure.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/exposure/Exposure.java index 9a4c3db05ca..a86bfd6005d 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/exposure/Exposure.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/exposure/Exposure.java @@ -18,9 +18,13 @@ import static de.symeda.sormas.api.utils.FieldConstraints.CHARACTER_LIMIT_DEFAULT; import java.util.Date; +import java.util.HashSet; +import java.util.Set; import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -30,15 +34,23 @@ import javax.persistence.OneToOne; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; import de.symeda.sormas.api.epidata.AnimalCondition; import de.symeda.sormas.api.epidata.WaterSource; import de.symeda.sormas.api.event.MeansOfTransport; import de.symeda.sormas.api.event.TypeOfPlace; +import de.symeda.sormas.api.exposure.AnimalCategory; import de.symeda.sormas.api.exposure.AnimalContactType; import de.symeda.sormas.api.exposure.AnimalLocation; +import de.symeda.sormas.api.exposure.ExposureCategory; +import de.symeda.sormas.api.exposure.ExposureContactFactor; +import de.symeda.sormas.api.exposure.ExposureProtectiveMeasure; import de.symeda.sormas.api.exposure.ExposureRole; +import de.symeda.sormas.api.exposure.ExposureSetting; +import de.symeda.sormas.api.exposure.ExposureSubSetting; import de.symeda.sormas.api.exposure.ExposureType; +import de.symeda.sormas.api.exposure.FomiteTransmissionLocation; import de.symeda.sormas.api.exposure.GatheringType; import de.symeda.sormas.api.exposure.HabitationType; import de.symeda.sormas.api.exposure.SwimmingLocation; @@ -64,6 +76,19 @@ public class Exposure extends AbstractDomainObject { public static final String LOCATION = "location"; public static final String EXPOSURE_TYPE = "exposureType"; public static final String CONTACT_TO_CASE = "contactToCase"; + public static final String EXPOSURE_CATEGORY = "exposureCategory"; + public static final String EXPOSURE_SETTING = "exposureSetting"; + public static final String EXPOSURE_SETTING_DETAILS = "exposureSettingDetails"; + public static final String EXPOSURE_SUB_SETTING_DETAILS = "exposureSubSettingDetails"; + public static final String CONTACT_FACTOR_DETAILS = "contactFactorDetails"; + public static final String PROTECTIVE_MEASURE_DETAILS = "protectiveMeasureDetails"; + public static final String EXPOSURE_COMMENT = "exposureComment"; + public static final String ANIMAL_CATEGORY = "animalCategory"; + public static final String ANIMAL_CATEGORY_DETAILS = "animalCategoryDetails"; + public static final String FOMITE_TRANSMISSION_LOCATION = "fomiteTransmissionLocation"; + public static final String SUB_SETTINGS = "subSettings"; + public static final String CONTACT_FACTORS = "contactFactors"; + public static final String PROTECTIVE_MEASURES = "protectiveMeasures"; private EpiData epiData; private User reportingUser; @@ -147,6 +172,24 @@ public class Exposure extends AbstractDomainObject { private String rawFoodContactText; private String symptomaticIndividualText; + private ExposureCategory exposureCategory; + private ExposureSetting exposureSetting; + private String exposureSettingDetails; + private String exposureSubSettingDetails; + private String contactFactorDetails; + private String protectiveMeasureDetails; + private String exposureComment; + + private AnimalCondition conditionOfAnimal; + private AnimalCategory animalCategory; + private String animalCategoryDetails; + + private FomiteTransmissionLocation fomiteTransmissionLocation; + + private Set subSettings = new HashSet<>(); + private Set contactFactors = new HashSet<>(); + private Set protectiveMeasures = new HashSet<>(); + @ManyToOne @JoinColumn(nullable = false) public EpiData getEpiData() { @@ -760,4 +803,151 @@ public String getSymptomaticIndividualText() { public void setSymptomaticIndividualText(String symptomaticIndividualText) { this.symptomaticIndividualText = symptomaticIndividualText; } + + @Enumerated(EnumType.STRING) + public ExposureCategory getExposureCategory() { + return exposureCategory; + } + + public void setExposureCategory(ExposureCategory exposureCategory) { + this.exposureCategory = exposureCategory; + } + + @Enumerated(EnumType.STRING) + public ExposureSetting getExposureSetting() { + return exposureSetting; + } + + public void setExposureSetting(ExposureSetting exposureSetting) { + this.exposureSetting = exposureSetting; + } + + @Column(columnDefinition = "text") + public String getExposureSettingDetails() { + return exposureSettingDetails; + } + + public void setExposureSettingDetails(String exposureSettingDetails) { + this.exposureSettingDetails = exposureSettingDetails; + } + + @Column(columnDefinition = "text") + public String getExposureSubSettingDetails() { + return exposureSubSettingDetails; + } + + public void setExposureSubSettingDetails(String exposureSubSettingDetails) { + this.exposureSubSettingDetails = exposureSubSettingDetails; + } + + @Column(columnDefinition = "text") + public String getContactFactorDetails() { + return contactFactorDetails; + } + + public void setContactFactorDetails(String contactFactorDetails) { + this.contactFactorDetails = contactFactorDetails; + } + + @Column(columnDefinition = "text") + public String getProtectiveMeasureDetails() { + return protectiveMeasureDetails; + } + + public void setProtectiveMeasureDetails(String protectiveMeasureDetails) { + this.protectiveMeasureDetails = protectiveMeasureDetails; + } + + @Column(columnDefinition = "text") + public String getExposureComment() { + return exposureComment; + } + + public void setExposureComment(String exposureComment) { + this.exposureComment = exposureComment; + } + + @Enumerated(EnumType.STRING) + public AnimalCondition getConditionOfAnimal() { + return conditionOfAnimal; + } + + public void setConditionOfAnimal(AnimalCondition conditionOfAnimal) { + this.conditionOfAnimal = conditionOfAnimal; + } + + @Enumerated(EnumType.STRING) + public AnimalCategory getAnimalCategory() { + return animalCategory; + } + + public void setAnimalCategory(AnimalCategory animalCategory) { + this.animalCategory = animalCategory; + } + + @Column(columnDefinition = "text") + public String getAnimalCategoryDetails() { + return animalCategoryDetails; + } + + public void setAnimalCategoryDetails(String animalCategoryDetails) { + this.animalCategoryDetails = animalCategoryDetails; + } + + @Enumerated(EnumType.STRING) + public FomiteTransmissionLocation getFomiteTransmissionLocation() { + return fomiteTransmissionLocation; + } + + public void setFomiteTransmissionLocation(FomiteTransmissionLocation fomiteTransmissionLocation) { + this.fomiteTransmissionLocation = fomiteTransmissionLocation; + } + + @ElementCollection(fetch = FetchType.EAGER) + @Enumerated(EnumType.STRING) + @CollectionTable(name = "exposures_subsettings", + joinColumns = @JoinColumn(name = "exposure_id", referencedColumnName = Exposure.ID, nullable = false), + uniqueConstraints = @UniqueConstraint(columnNames = { + "exposure_id", + "subsetting" })) + @Column(name = "subsetting", nullable = false) + public Set getSubSettings() { + return subSettings; + } + + public void setSubSettings(Set subSettings) { + this.subSettings = subSettings; + } + + @ElementCollection(fetch = FetchType.EAGER) + @Enumerated(EnumType.STRING) + @CollectionTable(name = "exposures_contactfactors", + joinColumns = @JoinColumn(name = "exposure_id", referencedColumnName = Exposure.ID, nullable = false), + uniqueConstraints = @UniqueConstraint(columnNames = { + "exposure_id", + "contactfactor" })) + @Column(name = "contactfactor", nullable = false) + public Set getContactFactors() { + return contactFactors; + } + + public void setContactFactors(Set contactFactors) { + this.contactFactors = contactFactors; + } + + @ElementCollection(fetch = FetchType.EAGER) + @Enumerated(EnumType.STRING) + @CollectionTable(name = "exposures_protectivemeasures", + joinColumns = @JoinColumn(name = "exposure_id", referencedColumnName = Exposure.ID, nullable = false), + uniqueConstraints = @UniqueConstraint(columnNames = { + "exposure_id", + "protectivemeasure" })) + @Column(name = "protectivemeasure", nullable = false) + public Set getProtectiveMeasures() { + return protectiveMeasures; + } + + public void setProtectiveMeasures(Set protectiveMeasures) { + this.protectiveMeasures = protectiveMeasures; + } } diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 7e3a07b7007..50cdc690be6 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15367,4 +15367,104 @@ ALTER TABLE externalmessage_history ALTER COLUMN casecomments TYPE text; INSERT INTO schema_version (version_number, comment) VALUES (613, '#13879 - Increased length of case comments'); +-- epidata table update +ALTER TABLE epidata ADD COLUMN otherdetails TEXT; +ALTER TABLE epidata_history ADD COLUMN otherdetails TEXT; + +-- exposure table update +ALTER TABLE exposures ADD COLUMN exposurecategory VARCHAR(255); +ALTER TABLE exposures ADD COLUMN exposuresetting VARCHAR(255); +ALTER TABLE exposures ADD COLUMN exposuresettingdetails TEXT; +ALTER TABLE exposures ADD COLUMN exposuresubsettingdetails TEXT; +ALTER TABLE exposures ADD COLUMN contactfactordetails TEXT; +ALTER TABLE exposures ADD COLUMN protectivemeasuredetails TEXT; +ALTER TABLE exposures ADD COLUMN conditionofanimal VARCHAR(255); +ALTER TABLE exposures ADD COLUMN animalcategory VARCHAR(255); +ALTER TABLE exposures ADD COLUMN animalcategorydetails TEXT; +ALTER TABLE exposures ADD COLUMN fomitetransmissionlocation VARCHAR(255); +ALTER TABLE exposures ADD COLUMN exposurecomment TEXT; + +ALTER TABLE exposures_history ADD COLUMN exposurecategory VARCHAR(255); +ALTER TABLE exposures_history ADD COLUMN exposuresetting VARCHAR(255); +ALTER TABLE exposures_history ADD COLUMN exposuresettingdetails TEXT; +ALTER TABLE exposures_history ADD COLUMN exposuresubsettingdetails TEXT; +ALTER TABLE exposures_history ADD COLUMN contactfactordetails TEXT; +ALTER TABLE exposures_history ADD COLUMN protectivemeasuredetails TEXT; +ALTER TABLE exposures_history ADD COLUMN conditionofanimal VARCHAR(255); +ALTER TABLE exposures_history ADD COLUMN animalcategory VARCHAR(255); +ALTER TABLE exposures_history ADD COLUMN animalcategorydetails TEXT; +ALTER TABLE exposures_history ADD COLUMN fomitetransmissionlocation VARCHAR(255); +ALTER TABLE exposures_history ADD COLUMN exposurecomment TEXT; + +-- exposures_subsettings +CREATE TABLE exposures_subsettings ( + exposure_id BIGINT NOT NULL, + subsetting VARCHAR(255) NOT NULL, + sys_period TSTZRANGE NOT NULL +); + +ALTER TABLE exposures_subsettings OWNER TO sormas_user; +ALTER TABLE exposures_subsettings ADD CONSTRAINT fk_exposures_subsettings_exposure_id FOREIGN KEY (exposure_id) REFERENCES exposures; +ALTER TABLE exposures_subsettings ADD CONSTRAINT unq_exposures_subsettings_0 UNIQUE (exposure_id, subsetting); + +-- exposures_subsettings history +CREATE TABLE exposures_subsettings_history (LIKE exposures_subsettings); +DROP TRIGGER IF EXISTS versioning_trigger ON exposures_subsettings; +CREATE TRIGGER versioning_trigger + BEFORE INSERT OR UPDATE OR DELETE ON exposures_subsettings + FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'exposures_subsettings_history', true); +DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_subsettings; +CREATE TRIGGER delete_history_trigger + AFTER DELETE ON exposures_subsettings + FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('exposures_subsettings_history', 'id'); +ALTER TABLE exposures_subsettings_history OWNER TO sormas_user; + +-- exposures_contactfactors +CREATE TABLE exposures_contactfactors ( + exposure_id BIGINT NOT NULL, + contactfactor VARCHAR(255) NOT NULL, + sys_period TSTZRANGE NOT NULL +); + +ALTER TABLE exposures_contactfactors OWNER TO sormas_user; +ALTER TABLE exposures_contactfactors ADD CONSTRAINT fk_exposures_contactfactors_exposure_id FOREIGN KEY (exposure_id) REFERENCES exposures; +ALTER TABLE exposures_contactfactors ADD CONSTRAINT unq_exposures_contactfactors_0 UNIQUE (exposure_id, contactfactor); + +-- exposures_contactfactors history +CREATE TABLE exposures_contactfactors_history (LIKE exposures_contactfactors); +DROP TRIGGER IF EXISTS versioning_trigger ON exposures_contactfactors; +CREATE TRIGGER versioning_trigger + BEFORE INSERT OR UPDATE OR DELETE ON exposures_contactfactors + FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'exposures_contactfactors_history', true); +DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_contactfactors; +CREATE TRIGGER delete_history_trigger + AFTER DELETE ON exposures_contactfactors + FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('exposures_contactfactors_history', 'id'); +ALTER TABLE exposures_contactfactors_history OWNER TO sormas_user; + +-- exposures_protectivemeasures +CREATE TABLE exposures_protectivemeasures ( + exposure_id BIGINT NOT NULL, + protectivemeasure VARCHAR(255) NOT NULL, + sys_period TSTZRANGE NOT NULL +); + +ALTER TABLE exposures_protectivemeasures OWNER TO sormas_user; +ALTER TABLE exposures_protectivemeasures ADD CONSTRAINT fk_exposures_protectivemeasures_exposure_id FOREIGN KEY (exposure_id) REFERENCES exposures; +ALTER TABLE exposures_protectivemeasures ADD CONSTRAINT unq_exposures_protectivemeasures_0 UNIQUE (exposure_id, protectivemeasure); + +-- exposures_protectivemeasures history +CREATE TABLE exposures_protectivemeasures_history (LIKE exposures_protectivemeasures); +DROP TRIGGER IF EXISTS versioning_trigger ON exposures_protectivemeasures; +CREATE TRIGGER versioning_trigger + BEFORE INSERT OR UPDATE OR DELETE ON exposures_protectivemeasures + FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'exposures_protectivemeasures_history', true); +DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_protectivemeasures; +CREATE TRIGGER delete_history_trigger + AFTER DELETE ON exposures_protectivemeasures + FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('exposures_protectivemeasures_history', 'id'); +ALTER TABLE exposures_protectivemeasures_history OWNER TO sormas_user; + +INSERT INTO schema_version (version_number, comment) VALUES (614, '#13887 - Exposure form redesign'); + -- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. ***