Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Set;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import de.symeda.sormas.api.CountryHelper;
Expand Down Expand Up @@ -153,6 +154,7 @@ public class ExposureDto extends PseudonymizableDto {
@SensitiveData
@Size(max = FieldConstraints.CHARACTER_LIMIT_TEXT, message = Validations.textTooLong)
private String description;
@NotNull(message = Validations.requiredField)
private ExposureType exposureType;
@SensitiveData
@Size(max = FieldConstraints.CHARACTER_LIMIT_TEXT, message = Validations.textTooLong)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@

package de.symeda.sormas.api.exposure;

import java.util.Arrays;
import java.util.Collection;
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.Disease;
import de.symeda.sormas.api.i18n.I18nProperties;
import de.symeda.sormas.api.utils.Diseases;
Expand All @@ -24,63 +32,94 @@ public enum ExposureType {
@Diseases(value = {
Disease.CRYPTOSPORIDIOSIS,
Disease.GIARDIASIS }, hide = true)
WORK,
WORK(true),
@Diseases({
Disease.CRYPTOSPORIDIOSIS,
Disease.GIARDIASIS })
TRAVEL,
TRAVEL(true),
@Diseases(value = {
Disease.CRYPTOSPORIDIOSIS,
Disease.GIARDIASIS }, hide = true)
SPORT,
SPORT(false),
@Diseases(value = {
Disease.CRYPTOSPORIDIOSIS,
Disease.GIARDIASIS }, hide = true)
VISIT,
VISIT(false),
@Diseases(value = {
Disease.CRYPTOSPORIDIOSIS,
Disease.GIARDIASIS }, hide = true)
GATHERING,
GATHERING(true),
@Diseases(value = {
Disease.CRYPTOSPORIDIOSIS,
Disease.GIARDIASIS }, hide = true)
HABITATION,
HABITATION(false),
@Diseases(value = {
Disease.CRYPTOSPORIDIOSIS,
Disease.GIARDIASIS }, hide = true)
PERSONAL_SERVICES,
PERSONAL_SERVICES(false),
@Diseases(value = {
Disease.RESPIRATORY_SYNCYTIAL_VIRUS })
CHILDCARE_FACILITY,
CHILDCARE_FACILITY(false),
@Diseases(value = {
Disease.CORONAVIRUS,
Disease.GIARDIASIS,
Disease.CRYPTOSPORIDIOSIS }, hide = true)
BURIAL,
BURIAL(false),
@Diseases(value = {
Disease.CORONAVIRUS }, hide = true)
ANIMAL_CONTACT,
ANIMAL_CONTACT(false),
@Diseases({
Disease.GIARDIASIS,
Disease.CRYPTOSPORIDIOSIS })
RECREATIONAL_WATER,
RECREATIONAL_WATER(false, ExposureCategory.WATER_BORNE),
@Diseases({
Disease.GIARDIASIS,
Disease.CRYPTOSPORIDIOSIS })
FOOD,
FOOD(false, ExposureCategory.FOOD_BORNE),
@Diseases({
Disease.GIARDIASIS,
Disease.CRYPTOSPORIDIOSIS })
SEXUAL_CONTACT,
SEXUAL_CONTACT(false, ExposureCategory.DIRECT_CONTACT),
@Diseases({
Disease.CRYPTOSPORIDIOSIS })
SYMPTOMATIC_CONTACT,
SYMPTOMATIC_CONTACT(false, ExposureCategory.DIRECT_CONTACT),
@Diseases({
Disease.CRYPTOSPORIDIOSIS,
Disease.GIARDIASIS })
FLOOD_EXPOSURE,
OTHER,
UNKNOWN;
FLOOD_EXPOSURE(false, ExposureCategory.WATER_BORNE),
OTHER(true),
UNKNOWN(true);

private final boolean defaultType;
private final Set<ExposureCategory> categories;

ExposureType(boolean defaultType, ExposureCategory... categories) {
this.defaultType = defaultType;
this.categories = categories.length == 0 ? Collections.emptySet() : Collections.unmodifiableSet(EnumSet.copyOf(Arrays.asList(categories)));
}

public boolean isDefaultType() {
return defaultType;
}

public Set<ExposureCategory> getCategories() {
return categories;
}

public static List<ExposureType> getValues(Collection<ExposureCategory> diseaseCategories) {
boolean hasConfig = diseaseCategories != null && !diseaseCategories.isEmpty();
Set<ExposureCategory> configured = hasConfig ? EnumSet.copyOf(diseaseCategories) : EnumSet.noneOf(ExposureCategory.class);

return Arrays.stream(values()).filter(type -> {
if (type.isDefaultType()) {
return true;
}
if (!hasConfig) {
return false;
}
return type.getCategories().stream().anyMatch(configured::contains);
}).collect(Collectors.toList());
}

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3381,6 +3381,7 @@ public interface Captions {
String titleDiseaseConfigurationGeneral = "titleDiseaseConfigurationGeneral";
String titleExposureActivitySection = "titleExposureActivitySection";
String titleExposureLocationSection = "titleExposureLocationSection";
String titleExposuresGeneralSection = "titleExposuresGeneralSection";
String titleExposuresSection = "titleExposuresSection";
String titleNoComplications = "titleNoComplications";
String to = "to";
Expand Down
1 change: 1 addition & 0 deletions sormas-api/src/main/resources/captions.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,7 @@ Exposure.travelPurposeDetails=Reason for travel details
Exposure.eatingOutVenues=Eating out venues
Exposure.eatingOutVenueOther=Other venue (please specify)
Exposure.shoppingForFoodDetails=Shopping for food (location/details)
titleExposuresGeneralSection=General information
titleExposuresSection=Exposure details
titleExposureActivitySection=Activity details
titleExposureLocationSection=Location of exposure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ public void setDescription(String description) {
}

@Enumerated(EnumType.STRING)
@Column(nullable = false)
public ExposureType getExposureType() {
return exposureType;
}
Expand Down
13 changes: 11 additions & 2 deletions sormas-backend/src/main/resources/sql/sormas_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15996,7 +15996,16 @@ ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS flatulence text;
ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS lossofappetite text;
ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS smellyburps text;

INSERT INTO schema_version (version_number, comment)
VALUES (629, '#13832 - External Survey integration');
INSERT INTO schema_version (version_number, comment) VALUES (629, '#13832 - External Survey integration');

UPDATE featureconfiguration
SET properties = json_build_object(
'FETCH_MODE', false,
'FORCE_AUTOMATIC_PROCESSING', true,
'SURVEY_FETCH_ENABLED', true
)
WHERE featuretype = 'EXTERNAL_MESSAGES';

INSERT INTO schema_version (version_number, comment) VALUES (630, 'Fix corrupt JSON in featureconfiguration.properties for EXTERNAL_MESSAGES from 629');

-- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. ***
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -66,6 +67,7 @@
import de.symeda.sormas.api.exposure.ExposureProtectiveMeasure;
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.ProphylaxisAdherence;
import de.symeda.sormas.api.exposure.TravelPurpose;
Expand Down Expand Up @@ -103,9 +105,13 @@ public class ExposureForm extends AbstractEditForm<ExposureDto> {
private static final String UUID_REPORTING_USER = fluidRowLocs(ExposureDto.UUID, ExposureDto.REPORTING_USER);

//@formatter:off
private static final String EXPOSURE_DETAILS_LAYOUT =
private static final String GENERAL_DETAILS_LAYOUT =
fluidRowLocs(ExposureDto.START_DATE, ExposureDto.END_DATE) +
loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_DETAILS) +
fluidRowLocs(ExposureDto.EXPOSURE_TYPE, ExposureDto.EXPOSURE_TYPE_DETAILS) +
loc(ExposureDto.DESCRIPTION);

private static final String EXPOSURE_DETAILS_LAYOUT =
loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_DETAILS) +
loc(LOC_EXPOSURES_HEADING) +
fluidRowLocs(ExposureDto.EXPOSURE_CATEGORY, ExposureDto.EXPOSURE_SETTING, ExposureDto.EXPOSURE_SETTING_DETAILS) +
fluidRow(
Expand Down Expand Up @@ -142,8 +148,7 @@ public class ExposureForm extends AbstractEditForm<ExposureDto> {
ExposureDto.PROTECTIVE_MEASURE_DETAILS
))
) +
loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURES_GENERAL) +
loc(ExposureDto.DESCRIPTION);
loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURES_GENERAL);

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

private CustomLayout generalDetailsLayout;
private CustomLayout exposureDetailsLayout;
private CustomLayout locationDetailsLayout;

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

private ComboBox exposureTypeField;

private ComboBox categoryField;
private ComboBox settingField;
private TextField settingDetailsField;
Expand Down Expand Up @@ -230,6 +238,9 @@ protected void addFields() {

FormSectionAccordion accordion = new FormSectionAccordion();

generalDetailsLayout = new CustomLayout();
generalDetailsLayout.setTemplateContents(GENERAL_DETAILS_LAYOUT);

exposureDetailsLayout = new CustomLayout();
exposureDetailsLayout.setTemplateContents(EXPOSURE_DETAILS_LAYOUT);

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

addField(exposureDetailsLayout, ExposureDto.DESCRIPTION, TextArea.class).setRows(5);

locationForm = addField(locationDetailsLayout, ExposureDto.LOCATION, LocationEditForm.class);
locationForm.setCaption(null);
addField(locationDetailsLayout, ExposureDto.CONNECTION_NUMBER, TextField.class);
Expand All @@ -275,7 +284,8 @@ protected void addFields() {
}
});

accordion.addFormSectionPanel(Captions.titleExposuresSection, true, exposureDetailsLayout);
accordion.addFormSectionPanel(Captions.titleExposuresGeneralSection, true, generalDetailsLayout);
accordion.addFormSectionPanel(Captions.titleExposuresSection, false, exposureDetailsLayout);
accordion.addFormSectionPanel(Captions.titleExposureLocationSection, false, locationDetailsLayout);

getContent().addComponent(accordion, MAIN_ACCORDION_LOC);
Expand All @@ -285,6 +295,8 @@ protected void addFields() {
initializeVisibilitiesAndAllowedVisibilities();
initializeAccessAndAllowedAccesses();

setUpRequirements();

setReadOnly(true, ExposureDto.UUID, ExposureDto.REPORTING_USER);
}

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

DateTimeField startDate = addField(exposureDetailsLayout, ExposureDto.START_DATE, DateTimeField.class);
DateTimeField endDate = addField(exposureDetailsLayout, ExposureDto.END_DATE, DateTimeField.class);
DateTimeField startDate = addField(generalDetailsLayout, ExposureDto.START_DATE, DateTimeField.class);
DateTimeField endDate = addField(generalDetailsLayout, ExposureDto.END_DATE, DateTimeField.class);

DateComparisonValidator.addStartEndValidators(startDate, endDate, false);

exposureTypeField = addField(generalDetailsLayout, ExposureDto.EXPOSURE_TYPE, ComboBox.class);
exposureTypeField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING);

addField(generalDetailsLayout, ExposureDto.EXPOSURE_TYPE_DETAILS, TextField.class);
addField(generalDetailsLayout, ExposureDto.DESCRIPTION, TextArea.class).setRows(5);

categoryField = addField(exposureDetailsLayout, ExposureDto.EXPOSURE_CATEGORY, ComboBox.class);
categoryField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING);
categoryField.setRequired(true);

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

private void setUpVisibilityDependencies() {
FieldHelper.setVisibleWhen(getFieldGroup(), ExposureDto.EXPOSURE_TYPE_DETAILS, ExposureDto.EXPOSURE_TYPE, ExposureType.OTHER, true);
FieldHelper.setVisibleWhen(getFieldGroup(), ExposureDto.TYPE_OF_PLACE_DETAILS, ExposureDto.TYPE_OF_PLACE, TypeOfPlace.OTHER, true);
FieldHelper.setVisibleWhen(
getFieldGroup(),
Expand Down Expand Up @@ -536,6 +554,15 @@ private void setUpVisibilityDependencies() {
locationForm.setContinentFieldsVisibility();
}

private void setUpRequirements() {
setRequired(true, ExposureDto.EXPOSURE_TYPE);
FieldHelper.setRequiredWhen(
getFieldGroup(),
ExposureDto.EXPOSURE_TYPE,
Collections.singletonList(ExposureDto.EXPOSURE_TYPE_DETAILS),
Collections.singletonList(ExposureType.OTHER));
}

private void updateSettingFieldItems(ExposureCategory category) {
List<ExposureSetting> settings = ExposureSetting.getValues(category);
FieldHelper.updateItems(settingField, settings);
Expand All @@ -550,14 +577,12 @@ private void updateSettingFieldItems(ExposureCategory category) {
settingDetailsField.setValue(null);
settingDetailsField.setVisible(false);

if (category != null) {
if (category.hasNoSetting()) {
settingField.setVisible(false);
settingField.setRequired(false);
} else {
settingField.setVisible(true);
settingField.setRequired(true);
}
if (category == null || category.hasNoSetting()) {
settingField.setVisible(false);
settingField.setRequired(false);
} else {
settingField.setVisible(true);
settingField.setRequired(true);
}
}

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

populateExposureTypes(newFieldValue);
populateExposureCategories(newFieldValue);

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

private void populateExposureTypes(ExposureDto exposure) {
// Get disease configuration
DiseaseConfigurationDto diseaseConfig = null;
if (disease != null) {
diseaseConfig = FacadeProvider.getDiseaseConfigurationFacade().getDiseaseConfiguration(disease);
}

Set<ExposureCategory> diseaseCategories = diseaseConfig != null && diseaseConfig.getExposureCategories() != null
? new HashSet<>(diseaseConfig.getExposureCategories())
: Collections.emptySet();

// defaults (+ types matching the disease's configured categories, if any)
List<ExposureType> filteredTypes = ExposureType.getValues(diseaseCategories);

// Preserve existing record's value even if it is no longer in the filtered set (legacy data)
Set<ExposureType> finalTypes = new LinkedHashSet<>(filteredTypes);
if (exposure != null && exposure.getExposureType() != null) {
finalTypes.add(exposure.getExposureType());
}

FieldHelper.updateItems(exposureTypeField, new ArrayList<>(finalTypes));
}

private void populateExposureCategories(ExposureDto exposure) {
Set<ExposureCategory> categories;

Expand Down
Loading
Loading