Skip to content

Commit 88b9a71

Browse files
authored
Merge pull request #13936 from SORMAS-Foundation/13887-exposure-form-redesign
re-introduce exposure type field
2 parents 4d01e4f + 1c38c57 commit 88b9a71

8 files changed

Lines changed: 156 additions & 84 deletions

File tree

sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureDto.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Set;
2121

2222
import javax.validation.Valid;
23+
import javax.validation.constraints.NotNull;
2324
import javax.validation.constraints.Size;
2425

2526
import de.symeda.sormas.api.CountryHelper;
@@ -153,6 +154,7 @@ public class ExposureDto extends PseudonymizableDto {
153154
@SensitiveData
154155
@Size(max = FieldConstraints.CHARACTER_LIMIT_TEXT, message = Validations.textTooLong)
155156
private String description;
157+
@NotNull(message = Validations.requiredField)
156158
private ExposureType exposureType;
157159
@SensitiveData
158160
@Size(max = FieldConstraints.CHARACTER_LIMIT_TEXT, message = Validations.textTooLong)

sormas-api/src/main/java/de/symeda/sormas/api/exposure/ExposureType.java

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@
1515

1616
package de.symeda.sormas.api.exposure;
1717

18+
import java.util.Arrays;
19+
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.EnumSet;
22+
import java.util.List;
23+
import java.util.Set;
24+
import java.util.stream.Collectors;
25+
1826
import de.symeda.sormas.api.Disease;
1927
import de.symeda.sormas.api.i18n.I18nProperties;
2028
import de.symeda.sormas.api.utils.Diseases;
@@ -24,63 +32,94 @@ public enum ExposureType {
2432
@Diseases(value = {
2533
Disease.CRYPTOSPORIDIOSIS,
2634
Disease.GIARDIASIS }, hide = true)
27-
WORK,
35+
WORK(true),
2836
@Diseases({
2937
Disease.CRYPTOSPORIDIOSIS,
3038
Disease.GIARDIASIS })
31-
TRAVEL,
39+
TRAVEL(true),
3240
@Diseases(value = {
3341
Disease.CRYPTOSPORIDIOSIS,
3442
Disease.GIARDIASIS }, hide = true)
35-
SPORT,
43+
SPORT(false),
3644
@Diseases(value = {
3745
Disease.CRYPTOSPORIDIOSIS,
3846
Disease.GIARDIASIS }, hide = true)
39-
VISIT,
47+
VISIT(false),
4048
@Diseases(value = {
4149
Disease.CRYPTOSPORIDIOSIS,
4250
Disease.GIARDIASIS }, hide = true)
43-
GATHERING,
51+
GATHERING(true),
4452
@Diseases(value = {
4553
Disease.CRYPTOSPORIDIOSIS,
4654
Disease.GIARDIASIS }, hide = true)
47-
HABITATION,
55+
HABITATION(false),
4856
@Diseases(value = {
4957
Disease.CRYPTOSPORIDIOSIS,
5058
Disease.GIARDIASIS }, hide = true)
51-
PERSONAL_SERVICES,
59+
PERSONAL_SERVICES(false),
5260
@Diseases(value = {
5361
Disease.RESPIRATORY_SYNCYTIAL_VIRUS })
54-
CHILDCARE_FACILITY,
62+
CHILDCARE_FACILITY(false),
5563
@Diseases(value = {
5664
Disease.CORONAVIRUS,
5765
Disease.GIARDIASIS,
5866
Disease.CRYPTOSPORIDIOSIS }, hide = true)
59-
BURIAL,
67+
BURIAL(false),
6068
@Diseases(value = {
6169
Disease.CORONAVIRUS }, hide = true)
62-
ANIMAL_CONTACT,
70+
ANIMAL_CONTACT(false),
6371
@Diseases({
6472
Disease.GIARDIASIS,
6573
Disease.CRYPTOSPORIDIOSIS })
66-
RECREATIONAL_WATER,
74+
RECREATIONAL_WATER(false, ExposureCategory.WATER_BORNE),
6775
@Diseases({
6876
Disease.GIARDIASIS,
6977
Disease.CRYPTOSPORIDIOSIS })
70-
FOOD,
78+
FOOD(false, ExposureCategory.FOOD_BORNE),
7179
@Diseases({
7280
Disease.GIARDIASIS,
7381
Disease.CRYPTOSPORIDIOSIS })
74-
SEXUAL_CONTACT,
82+
SEXUAL_CONTACT(false, ExposureCategory.DIRECT_CONTACT),
7583
@Diseases({
7684
Disease.CRYPTOSPORIDIOSIS })
77-
SYMPTOMATIC_CONTACT,
85+
SYMPTOMATIC_CONTACT(false, ExposureCategory.DIRECT_CONTACT),
7886
@Diseases({
7987
Disease.CRYPTOSPORIDIOSIS,
8088
Disease.GIARDIASIS })
81-
FLOOD_EXPOSURE,
82-
OTHER,
83-
UNKNOWN;
89+
FLOOD_EXPOSURE(false, ExposureCategory.WATER_BORNE),
90+
OTHER(true),
91+
UNKNOWN(true);
92+
93+
private final boolean defaultType;
94+
private final Set<ExposureCategory> categories;
95+
96+
ExposureType(boolean defaultType, ExposureCategory... categories) {
97+
this.defaultType = defaultType;
98+
this.categories = categories.length == 0 ? Collections.emptySet() : Collections.unmodifiableSet(EnumSet.copyOf(Arrays.asList(categories)));
99+
}
100+
101+
public boolean isDefaultType() {
102+
return defaultType;
103+
}
104+
105+
public Set<ExposureCategory> getCategories() {
106+
return categories;
107+
}
108+
109+
public static List<ExposureType> getValues(Collection<ExposureCategory> diseaseCategories) {
110+
boolean hasConfig = diseaseCategories != null && !diseaseCategories.isEmpty();
111+
Set<ExposureCategory> configured = hasConfig ? EnumSet.copyOf(diseaseCategories) : EnumSet.noneOf(ExposureCategory.class);
112+
113+
return Arrays.stream(values()).filter(type -> {
114+
if (type.isDefaultType()) {
115+
return true;
116+
}
117+
if (!hasConfig) {
118+
return false;
119+
}
120+
return type.getCategories().stream().anyMatch(configured::contains);
121+
}).collect(Collectors.toList());
122+
}
84123

85124
@Override
86125
public String toString() {

sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3381,6 +3381,7 @@ public interface Captions {
33813381
String titleDiseaseConfigurationGeneral = "titleDiseaseConfigurationGeneral";
33823382
String titleExposureActivitySection = "titleExposureActivitySection";
33833383
String titleExposureLocationSection = "titleExposureLocationSection";
3384+
String titleExposuresGeneralSection = "titleExposuresGeneralSection";
33843385
String titleExposuresSection = "titleExposuresSection";
33853386
String titleNoComplications = "titleNoComplications";
33863387
String to = "to";

sormas-api/src/main/resources/captions.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,6 +1622,7 @@ Exposure.travelPurposeDetails=Reason for travel details
16221622
Exposure.eatingOutVenues=Eating out venues
16231623
Exposure.eatingOutVenueOther=Other venue (please specify)
16241624
Exposure.shoppingForFoodDetails=Shopping for food (location/details)
1625+
titleExposuresGeneralSection=General information
16251626
titleExposuresSection=Exposure details
16261627
titleExposureActivitySection=Activity details
16271628
titleExposureLocationSection=Location of exposure

sormas-backend/src/main/java/de/symeda/sormas/backend/exposure/Exposure.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ public void setDescription(String description) {
262262
}
263263

264264
@Enumerated(EnumType.STRING)
265+
@Column(nullable = false)
265266
public ExposureType getExposureType() {
266267
return exposureType;
267268
}

sormas-backend/src/main/resources/sql/sormas_schema.sql

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15996,7 +15996,16 @@ ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS flatulence text;
1599615996
ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS lossofappetite text;
1599715997
ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS smellyburps text;
1599815998

15999-
INSERT INTO schema_version (version_number, comment)
16000-
VALUES (629, '#13832 - External Survey integration');
15999+
INSERT INTO schema_version (version_number, comment) VALUES (629, '#13832 - External Survey integration');
16000+
16001+
UPDATE featureconfiguration
16002+
SET properties = json_build_object(
16003+
'FETCH_MODE', false,
16004+
'FORCE_AUTOMATIC_PROCESSING', true,
16005+
'SURVEY_FETCH_ENABLED', true
16006+
)
16007+
WHERE featuretype = 'EXTERNAL_MESSAGES';
16008+
16009+
INSERT INTO schema_version (version_number, comment) VALUES (630, 'Fix corrupt JSON in featureconfiguration.properties for EXTERNAL_MESSAGES from 629');
1600116010

1600216011
-- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. ***

sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.EnumSet;
2929
import java.util.HashMap;
3030
import java.util.HashSet;
31+
import java.util.LinkedHashSet;
3132
import java.util.List;
3233
import java.util.Map;
3334
import java.util.Set;
@@ -66,6 +67,7 @@
6667
import de.symeda.sormas.api.exposure.ExposureProtectiveMeasure;
6768
import de.symeda.sormas.api.exposure.ExposureSetting;
6869
import de.symeda.sormas.api.exposure.ExposureSubSetting;
70+
import de.symeda.sormas.api.exposure.ExposureType;
6971
import de.symeda.sormas.api.exposure.FomiteTransmissionLocation;
7072
import de.symeda.sormas.api.exposure.ProphylaxisAdherence;
7173
import de.symeda.sormas.api.exposure.TravelPurpose;
@@ -103,9 +105,13 @@ public class ExposureForm extends AbstractEditForm<ExposureDto> {
103105
private static final String UUID_REPORTING_USER = fluidRowLocs(ExposureDto.UUID, ExposureDto.REPORTING_USER);
104106

105107
//@formatter:off
106-
private static final String EXPOSURE_DETAILS_LAYOUT =
108+
private static final String GENERAL_DETAILS_LAYOUT =
107109
fluidRowLocs(ExposureDto.START_DATE, ExposureDto.END_DATE) +
108-
loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_DETAILS) +
110+
fluidRowLocs(ExposureDto.EXPOSURE_TYPE, ExposureDto.EXPOSURE_TYPE_DETAILS) +
111+
loc(ExposureDto.DESCRIPTION);
112+
113+
private static final String EXPOSURE_DETAILS_LAYOUT =
114+
loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_DETAILS) +
109115
loc(LOC_EXPOSURES_HEADING) +
110116
fluidRowLocs(ExposureDto.EXPOSURE_CATEGORY, ExposureDto.EXPOSURE_SETTING, ExposureDto.EXPOSURE_SETTING_DETAILS) +
111117
fluidRow(
@@ -142,8 +148,7 @@ public class ExposureForm extends AbstractEditForm<ExposureDto> {
142148
ExposureDto.PROTECTIVE_MEASURE_DETAILS
143149
))
144150
) +
145-
loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURES_GENERAL) +
146-
loc(ExposureDto.DESCRIPTION);
151+
loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURES_GENERAL);
147152

148153
private static final String LOCATION_DETAILS_LAYOUT =
149154
loc(LOC_LOCATION_HEADING) +
@@ -164,6 +169,7 @@ public class ExposureForm extends AbstractEditForm<ExposureDto> {
164169
private final Class<? extends EntityDto> epiDataParentClass;
165170
private final List<ContactReferenceDto> sourceContacts;
166171

172+
private CustomLayout generalDetailsLayout;
167173
private CustomLayout exposureDetailsLayout;
168174
private CustomLayout locationDetailsLayout;
169175

@@ -174,6 +180,8 @@ public class ExposureForm extends AbstractEditForm<ExposureDto> {
174180
private LocationEditForm locationForm;
175181
private Disease disease;
176182

183+
private ComboBox exposureTypeField;
184+
177185
private ComboBox categoryField;
178186
private ComboBox settingField;
179187
private TextField settingDetailsField;
@@ -230,6 +238,9 @@ protected void addFields() {
230238

231239
FormSectionAccordion accordion = new FormSectionAccordion();
232240

241+
generalDetailsLayout = new CustomLayout();
242+
generalDetailsLayout.setTemplateContents(GENERAL_DETAILS_LAYOUT);
243+
233244
exposureDetailsLayout = new CustomLayout();
234245
exposureDetailsLayout.setTemplateContents(EXPOSURE_DETAILS_LAYOUT);
235246

@@ -254,8 +265,6 @@ protected void addFields() {
254265
exposuresGeneralPanel.updateFieldsDisplay();
255266
exposureDetailsLayout.addComponent(exposuresGeneralPanel, LOC_CUSTOMIZABLE_FIELDS_EXPOSURES_GENERAL);
256267

257-
addField(exposureDetailsLayout, ExposureDto.DESCRIPTION, TextArea.class).setRows(5);
258-
259268
locationForm = addField(locationDetailsLayout, ExposureDto.LOCATION, LocationEditForm.class);
260269
locationForm.setCaption(null);
261270
addField(locationDetailsLayout, ExposureDto.CONNECTION_NUMBER, TextField.class);
@@ -275,7 +284,8 @@ protected void addFields() {
275284
}
276285
});
277286

278-
accordion.addFormSectionPanel(Captions.titleExposuresSection, true, exposureDetailsLayout);
287+
accordion.addFormSectionPanel(Captions.titleExposuresGeneralSection, true, generalDetailsLayout);
288+
accordion.addFormSectionPanel(Captions.titleExposuresSection, false, exposureDetailsLayout);
279289
accordion.addFormSectionPanel(Captions.titleExposureLocationSection, false, locationDetailsLayout);
280290

281291
getContent().addComponent(accordion, MAIN_ACCORDION_LOC);
@@ -285,6 +295,8 @@ protected void addFields() {
285295
initializeVisibilitiesAndAllowedVisibilities();
286296
initializeAccessAndAllowedAccesses();
287297

298+
setUpRequirements();
299+
288300
setReadOnly(true, ExposureDto.UUID, ExposureDto.REPORTING_USER);
289301
}
290302

@@ -303,14 +315,19 @@ private void addHeadingsAndInfoTexts() {
303315
private void addBasicFields() {
304316
addFields(ExposureDto.UUID, ExposureDto.REPORTING_USER, ExposureDto.PROBABLE_INFECTION_ENVIRONMENT);
305317

306-
DateTimeField startDate = addField(exposureDetailsLayout, ExposureDto.START_DATE, DateTimeField.class);
307-
DateTimeField endDate = addField(exposureDetailsLayout, ExposureDto.END_DATE, DateTimeField.class);
318+
DateTimeField startDate = addField(generalDetailsLayout, ExposureDto.START_DATE, DateTimeField.class);
319+
DateTimeField endDate = addField(generalDetailsLayout, ExposureDto.END_DATE, DateTimeField.class);
308320

309321
DateComparisonValidator.addStartEndValidators(startDate, endDate, false);
310322

323+
exposureTypeField = addField(generalDetailsLayout, ExposureDto.EXPOSURE_TYPE, ComboBox.class);
324+
exposureTypeField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING);
325+
326+
addField(generalDetailsLayout, ExposureDto.EXPOSURE_TYPE_DETAILS, TextField.class);
327+
addField(generalDetailsLayout, ExposureDto.DESCRIPTION, TextArea.class).setRows(5);
328+
311329
categoryField = addField(exposureDetailsLayout, ExposureDto.EXPOSURE_CATEGORY, ComboBox.class);
312330
categoryField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING);
313-
categoryField.setRequired(true);
314331

315332
settingField = addField(exposureDetailsLayout, ExposureDto.EXPOSURE_SETTING, ComboBox.class);
316333
settingField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING);
@@ -500,6 +517,7 @@ private void addBasicFields() {
500517
}
501518

502519
private void setUpVisibilityDependencies() {
520+
FieldHelper.setVisibleWhen(getFieldGroup(), ExposureDto.EXPOSURE_TYPE_DETAILS, ExposureDto.EXPOSURE_TYPE, ExposureType.OTHER, true);
503521
FieldHelper.setVisibleWhen(getFieldGroup(), ExposureDto.TYPE_OF_PLACE_DETAILS, ExposureDto.TYPE_OF_PLACE, TypeOfPlace.OTHER, true);
504522
FieldHelper.setVisibleWhen(
505523
getFieldGroup(),
@@ -536,6 +554,15 @@ private void setUpVisibilityDependencies() {
536554
locationForm.setContinentFieldsVisibility();
537555
}
538556

557+
private void setUpRequirements() {
558+
setRequired(true, ExposureDto.EXPOSURE_TYPE);
559+
FieldHelper.setRequiredWhen(
560+
getFieldGroup(),
561+
ExposureDto.EXPOSURE_TYPE,
562+
Collections.singletonList(ExposureDto.EXPOSURE_TYPE_DETAILS),
563+
Collections.singletonList(ExposureType.OTHER));
564+
}
565+
539566
private void updateSettingFieldItems(ExposureCategory category) {
540567
List<ExposureSetting> settings = ExposureSetting.getValues(category);
541568
FieldHelper.updateItems(settingField, settings);
@@ -550,14 +577,12 @@ private void updateSettingFieldItems(ExposureCategory category) {
550577
settingDetailsField.setValue(null);
551578
settingDetailsField.setVisible(false);
552579

553-
if (category != null) {
554-
if (category.hasNoSetting()) {
555-
settingField.setVisible(false);
556-
settingField.setRequired(false);
557-
} else {
558-
settingField.setVisible(true);
559-
settingField.setRequired(true);
560-
}
580+
if (category == null || category.hasNoSetting()) {
581+
settingField.setVisible(false);
582+
settingField.setRequired(false);
583+
} else {
584+
settingField.setVisible(true);
585+
settingField.setRequired(true);
561586
}
562587
}
563588

@@ -647,6 +672,7 @@ private void updateFomiteTransmissionField(ExposureCategory category) {
647672
public void setValue(ExposureDto newFieldValue) throws ReadOnlyException, Converter.ConversionException {
648673
super.setValue(newFieldValue);
649674

675+
populateExposureTypes(newFieldValue);
650676
populateExposureCategories(newFieldValue);
651677

652678
if (newFieldValue != null) {
@@ -783,6 +809,29 @@ public void setValue(ExposureDto newFieldValue) throws ReadOnlyException, Conver
783809
locationForm.discard();
784810
}
785811

812+
private void populateExposureTypes(ExposureDto exposure) {
813+
// Get disease configuration
814+
DiseaseConfigurationDto diseaseConfig = null;
815+
if (disease != null) {
816+
diseaseConfig = FacadeProvider.getDiseaseConfigurationFacade().getDiseaseConfiguration(disease);
817+
}
818+
819+
Set<ExposureCategory> diseaseCategories = diseaseConfig != null && diseaseConfig.getExposureCategories() != null
820+
? new HashSet<>(diseaseConfig.getExposureCategories())
821+
: Collections.emptySet();
822+
823+
// defaults (+ types matching the disease's configured categories, if any)
824+
List<ExposureType> filteredTypes = ExposureType.getValues(diseaseCategories);
825+
826+
// Preserve existing record's value even if it is no longer in the filtered set (legacy data)
827+
Set<ExposureType> finalTypes = new LinkedHashSet<>(filteredTypes);
828+
if (exposure != null && exposure.getExposureType() != null) {
829+
finalTypes.add(exposure.getExposureType());
830+
}
831+
832+
FieldHelper.updateItems(exposureTypeField, new ArrayList<>(finalTypes));
833+
}
834+
786835
private void populateExposureCategories(ExposureDto exposure) {
787836
Set<ExposureCategory> categories;
788837

0 commit comments

Comments
 (0)