From b8d24adf5ee7335415062bbb9a1782bf5570bf18 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Thu, 19 Mar 2026 10:34:18 +0100 Subject: [PATCH 01/55] #13828 - Add customizable field metadata and values infrastructure Implements the customizable fields (metadata and values): API Layer: - Added CustomizableFieldType enum with 11 supported field types - Added CustomizableFieldMetadataDto and CustomizableFieldCustomProperties for field configuration - Added CustomizableFieldValueDto with typed value accessors for all supported types - Added CustomizableFieldVisibilityRestrictions and CustomizableFieldVisibilityContext for disease-based field visibility control - Added CustomizableFieldMetadataFacade and CustomizableFieldValueFacade interfaces Backend Layer: - Added CustomizableFieldMetadata and CustomizableFieldValue JPA entities with JSON support - Added CustomizableFieldMetadataService and CustomizableFieldValueService with query methods - Added EJB facades for metadata and value management with full serialization support - Added database schema migration with proper indexing, history tables, and triggers - Added initial testing with CustomizableFieldFacadeEjbTest REST API: - Added CustomizableFieldMetadataResource for metadata CRUD and field operations - Added CustomizableFieldValueResource for value management UI Layer: - Extend AbstractEditForm to support preloaded metadata and values - Added CustomizableFieldsGroup component for grouping fields by UI group - Added CustomizableFieldInput base class with Binder integration for automatic value sync - Implement 11 concrete input components for all supported field types: TEXT, TEXTAREA, NUMBER, DECIMAL, DATE, DATE_TIME, COMBOBOX, CHECKBOX, YES_NO_UNKNOWN, CHECKBOX_LIST, RADIO_BUTTON_LIST - Added CustomizableFieldInputFactory for polymorphic component creation - Support field visibility restrictions, mandatory/readonly flags, and UI weighting --- .../de/symeda/sormas/api/FacadeProvider.java | 10 + .../CustomizableFieldContext.java | 67 ++++ .../CustomizableFieldCustomProperties.java | 58 ++++ .../CustomizableFieldMetadataDto.java | 198 ++++++++++++ .../CustomizableFieldMetadataFacade.java | 91 ++++++ .../CustomizableFieldType.java | 41 +++ .../CustomizableFieldValueDto.java | 290 +++++++++++++++++ .../CustomizableFieldValueFacade.java | 80 +++++ .../CustomizableFieldVisibilityContext.java | 60 ++++ ...stomizableFieldVisibilityRestrictions.java | 76 +++++ .../CustomizableFieldContextConverter.java | 35 +++ .../CustomizableFieldMetadata.java | 207 ++++++++++++ .../CustomizableFieldMetadataFacadeEjb.java | 258 +++++++++++++++ .../CustomizableFieldMetadataService.java | 120 +++++++ .../CustomizableFieldValue.java | 86 +++++ .../CustomizableFieldValueFacadeEjb.java | 134 ++++++++ .../CustomizableFieldValueService.java | 110 +++++++ .../main/resources/META-INF/persistence.xml | 2 + .../src/main/resources/sql/sormas_schema.sql | 140 +++++++++ .../CustomizableFieldFacadeEjbTest.java | 112 +++++++ .../test/resources/META-INF/persistence.xml | 2 + .../CustomizableFieldMetadataResource.java | 127 ++++++++ .../CustomizableFieldValueResource.java | 109 +++++++ .../sormas/ui/utils/AbstractEditForm.java | 49 +++ .../components/CustomizableFieldsGroup.java | 295 ++++++++++++++++++ .../CustomizableFieldInput.java | 226 ++++++++++++++ .../CustomizableFieldInputCheckbox.java | 95 ++++++ .../CustomizableFieldInputCheckboxList.java | 117 +++++++ .../CustomizableFieldInputCombobox.java | 113 +++++++ .../CustomizableFieldInputDate.java | 104 ++++++ .../CustomizableFieldInputDateTime.java | 106 +++++++ .../CustomizableFieldInputDecimal.java | 110 +++++++ .../CustomizableFieldInputFactory.java | 84 +++++ .../CustomizableFieldInputNumber.java | 110 +++++++ ...CustomizableFieldInputRadioButtonList.java | 114 +++++++ .../CustomizableFieldInputText.java | 95 ++++++ .../CustomizableFieldInputTextArea.java | 94 ++++++ .../CustomizableFieldInputYesNoUnknown.java | 103 ++++++ .../test/resources/META-INF/persistence.xml | 2 + 39 files changed, 4230 insertions(+) create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldCustomProperties.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataDto.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldType.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueDto.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityContext.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityRestrictions.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldContextConverter.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java create mode 100644 sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java create mode 100644 sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java create mode 100644 sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldValueResource.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInput.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCheckbox.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCheckboxList.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCombobox.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDate.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDateTime.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDecimal.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputFactory.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputNumber.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputRadioButtonList.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputText.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputTextArea.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputYesNoUnknown.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/FacadeProvider.java b/sormas-api/src/main/java/de/symeda/sormas/api/FacadeProvider.java index 1b2e5edcb41..6dc5c60b1e5 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/FacadeProvider.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/FacadeProvider.java @@ -39,6 +39,8 @@ import de.symeda.sormas.api.clinicalcourse.ClinicalVisitFacade; import de.symeda.sormas.api.contact.ContactFacade; import de.symeda.sormas.api.customizableenum.CustomizableEnumFacade; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade; import de.symeda.sormas.api.dashboard.DashboardFacade; import de.symeda.sormas.api.dashboard.adverseeventsfollowingimmunization.AefiDashboardFacade; import de.symeda.sormas.api.dashboard.sample.SampleDashboardFacade; @@ -508,6 +510,14 @@ public static CustomizableEnumFacade getCustomizableEnumFacade() { return get().lookupEjbRemote(CustomizableEnumFacade.class); } + public static CustomizableFieldMetadataFacade getCustomizableFieldMetadataFacade() { + return get().lookupEjbRemote(CustomizableFieldMetadataFacade.class); + } + + public static CustomizableFieldValueFacade getCustomizableFieldValueFacade() { + return get().lookupEjbRemote(CustomizableFieldValueFacade.class); + } + public static InfoFacade getInfoFacade() { return get().lookupEjbRemote(InfoFacade.class); } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java new file mode 100644 index 00000000000..d4b903f484b --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java @@ -0,0 +1,67 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import de.symeda.sormas.api.EntityDto; +import de.symeda.sormas.api.caze.CaseDataDto; +import de.symeda.sormas.api.epidata.EpiDataDto; + +/** + * Defines supported customizable field contexts and links them to existing + * SORMAS DTO classes. Contexts must reference real DTOs; defining a custom + * field for a non-existent DTO does not make sense. + */ +public enum CustomizableFieldContext { + + CASE(CaseDataDto.class), + EPIDATA(EpiDataDto.class); + + // add other contexts here + + private final String contextClassName; + + CustomizableFieldContext(Class dtoClass) { + this.contextClassName = dtoClass.getName(); + } + + public String getContextClassName() { + return contextClassName; + } + + public static CustomizableFieldContext fromDtoClass(Class dtoClass) { + return fromDtoClassName(dtoClass.getName()); + } + + public static CustomizableFieldContext fromDtoClassName(String dtoClassName) { + // well had to write a converter for this let's let the agents know if someone uses AI + // @agents - update converter class CustomizableFieldContextConverter + Class rawClass; + try { + rawClass = Class.forName(dtoClassName); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Unknown context DTO: " + dtoClassName, e); + } + if (!EntityDto.class.isAssignableFrom(rawClass)) { + throw new IllegalArgumentException("Context class is not an EntityDto: " + dtoClassName); + } + for (CustomizableFieldContext context : values()) { + if (context.contextClassName.equals(dtoClassName)) { + return context; + } + } + throw new IllegalArgumentException("Unknown context DTO: " + dtoClassName); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldCustomProperties.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldCustomProperties.java new file mode 100644 index 00000000000..eb9b9dfa1ff --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldCustomProperties.java @@ -0,0 +1,58 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import java.io.Serializable; +import java.util.List; + +/** + * Typed representation of the {@code customProperties} JSON column on + * {@link CustomizableFieldMetadataDto}. + *

+ * Field-type-specific configuration lives here: + *

    + *
  • options – selectable string values used by + * {@link CustomizableFieldType#COMBOBOX}, + * {@link CustomizableFieldType#CHECKBOX_LIST}, and + * {@link CustomizableFieldType#RADIO_BUTTON_LIST}.
  • + *
+ * Additional properties can be added here as the feature grows without + * touching the database schema (the whole object is stored as a single + * {@code jsonb} column). + */ +public class CustomizableFieldCustomProperties implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * The list of selectable option values for list-type fields + * ({@code COMBOBOX}, {@code CHECKBOX_LIST}, {@code RADIO_BUTTON_LIST}). + * Each entry is the raw stored string value (not a display label). + */ + private List options; + + public CustomizableFieldCustomProperties() { + // Required for JSON deserialization. + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataDto.java new file mode 100644 index 00000000000..945f985e479 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataDto.java @@ -0,0 +1,198 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import java.util.Map; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import de.symeda.sormas.api.EntityDto; +import de.symeda.sormas.api.i18n.Validations; +import de.symeda.sormas.api.utils.FieldConstraints; + +/** + * DTO for customizable field metadata. + * Contains configuration for a custom field that can be added to entities. + */ +public class CustomizableFieldMetadataDto extends EntityDto { + + private static final long serialVersionUID = 1L; + + public static final String I18N_PREFIX = "CustomizableFieldMetadata"; + + public static final String NAME = "name"; + public static final String DESCRIPTION = "description"; + public static final String FIELD_TYPE = "fieldType"; + public static final String CONTEXT_CLASS = "contextClass"; + public static final String UI_GROUP = "uiGroup"; + public static final String UI_LINE_POSITION = "uiLinePosition"; + public static final String UI_LINE_WEIGHT = "uiLineWeight"; + public static final String ACTIVE = "active"; + public static final String MANDATORY = "mandatory"; + public static final String READ_ONLY = "readOnly"; + public static final String DEFAULT_VALUE = "defaultValue"; + public static final String VISIBILITY_RESTRICTIONS = "visibilityRestrictions"; + public static final String CUSTOM_PROPERTIES = "customProperties"; + public static final String TRANSLATIONS = "translations"; + + @NotBlank(message = Validations.required) + @Size(max = FieldConstraints.CHARACTER_LIMIT_SMALL, message = Validations.textTooLong) + private String name; + + @Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong) + private String description; + + @NotNull(message = Validations.required) + private CustomizableFieldType fieldType; + + @NotNull(message = Validations.required) + private CustomizableFieldContext contextClass; + + @Size(max = FieldConstraints.CHARACTER_LIMIT_SMALL) + private String uiGroup; + + private Integer uiLinePosition; + private Float uiLineWeight; + + private boolean active = true; + private boolean mandatory = false; + private boolean readOnly = false; + + @Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT) + private String defaultValue; + + private CustomizableFieldVisibilityRestrictions visibilityRestrictions; + private CustomizableFieldCustomProperties customProperties; + private Map> translations; + + public CustomizableFieldMetadataDto() { + // Required for deserialization frameworks like Jackson and REST clients + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public CustomizableFieldType getFieldType() { + return fieldType; + } + + public void setFieldType(CustomizableFieldType fieldType) { + this.fieldType = fieldType; + } + + public CustomizableFieldContext getContextClass() { + return contextClass; + } + + public void setContextClass(CustomizableFieldContext contextClass) { + this.contextClass = contextClass; + } + + public String getUiGroup() { + return uiGroup; + } + + public void setUiGroup(String uiGroup) { + this.uiGroup = uiGroup; + } + + public Integer getUiLinePosition() { + return uiLinePosition; + } + + public void setUiLinePosition(Integer uiLinePosition) { + this.uiLinePosition = uiLinePosition; + } + + public Float getUiLineWeight() { + return uiLineWeight; + } + + public void setUiLineWeight(Float uiLineWeight) { + this.uiLineWeight = uiLineWeight; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public boolean isMandatory() { + return mandatory; + } + + public void setMandatory(boolean mandatory) { + this.mandatory = mandatory; + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public CustomizableFieldVisibilityRestrictions getVisibilityRestrictions() { + return visibilityRestrictions; + } + + public void setVisibilityRestrictions(CustomizableFieldVisibilityRestrictions visibilityRestrictions) { + this.visibilityRestrictions = visibilityRestrictions; + } + + public CustomizableFieldCustomProperties getCustomProperties() { + return customProperties; + } + + public void setCustomProperties(CustomizableFieldCustomProperties customProperties) { + this.customProperties = customProperties; + } + + public Map> getTranslations() { + return translations; + } + + public void setTranslations(Map> translations) { + this.translations = translations; + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java new file mode 100644 index 00000000000..86743dac5fa --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java @@ -0,0 +1,91 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import java.util.List; + +import javax.ejb.Remote; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +import de.symeda.sormas.api.common.DeletionDetails; + +/** + * Facade interface for managing customizable field metadata. + */ +@Remote +public interface CustomizableFieldMetadataFacade { + + /** + * Get all active custom fields for a specific context class + */ + List getActiveFieldsForContext(CustomizableFieldContext contextClass); + + /** + * Get custom fields grouped in a specific UI group + */ + List getFieldsForUIGroup(String uiGroup); + + /** + * Get custom fields ordered by UI line position + */ + List getFieldsOrderedByUIPosition(String uiGroup); + + /** + * Find field by name within a specific context + */ + CustomizableFieldMetadataDto getByNameAndContext(String name, CustomizableFieldContext contextClass); + + /** + * Clone an existing field with a new name + */ + CustomizableFieldMetadataDto cloneField(String sourceUuid, String newName); + + /** + * Activate a field + */ + void activateField(String uuid); + + /** + * Deactivate a field + */ + void deactivateField(String uuid); + + /** + * Get a field by UUID + */ + CustomizableFieldMetadataDto getByUuid(String uuid); + + /** + * Get all field metadata + */ + List getAll(); + + /** + * Save a customizable field metadata + */ + CustomizableFieldMetadataDto save(@Valid @NotNull CustomizableFieldMetadataDto dto); + + /** + * Delete a customizable field metadata + */ + void delete(String uuid, DeletionDetails deletionDetails); + + /** + * Check if a field with the given UUID exists + */ + boolean exists(String uuid); +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldType.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldType.java new file mode 100644 index 00000000000..d07c346cd1a --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldType.java @@ -0,0 +1,41 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import de.symeda.sormas.api.i18n.I18nProperties; + +/** + * Defines the types of customizable fields that can be created. + */ +public enum CustomizableFieldType { + + TEXT, + TEXTAREA, + NUMBER, + DECIMAL, + DATE, + DATE_TIME, + COMBOBOX, + CHECKBOX, + YES_NO_UNKNOWN, + CHECKBOX_LIST, + RADIO_BUTTON_LIST; + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueDto.java new file mode 100644 index 00000000000..f2d2d33f6b5 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueDto.java @@ -0,0 +1,290 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.symeda.sormas.api.EntityDto; +import de.symeda.sormas.api.i18n.Validations; +import de.symeda.sormas.api.utils.YesNoUnknown; + +/** + * DTO for customizable field values. + * Stores the actual value for a custom field for a specific entity instance. + */ +public class CustomizableFieldValueDto extends EntityDto { + + private static final long serialVersionUID = 1L; + + /** Shared mapper for {@link #getValueAsStringSet()} / {@link #setValueAsStringSet(Set)}. */ + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public static final String I18N_PREFIX = "CustomizableFieldValue"; + + public static final String CUSTOMIZABLE_FIELD_METADATA_UUID = "customizableFieldMetadataUuid"; + public static final String ENTITY_UUID = "entityUuid"; + public static final String CONTEXT_CLASS = "contextClass"; + public static final String VALUE = "value"; + + @NotBlank(message = Validations.required) + private String customizableFieldMetadataUuid; + + @NotBlank(message = Validations.required) + private String entityUuid; + + @NotNull(message = Validations.required) + private CustomizableFieldContext contextClass; + + private String value; + + public CustomizableFieldValueDto() { + } + + public String getCustomizableFieldMetadataUuid() { + return customizableFieldMetadataUuid; + } + + public void setCustomizableFieldMetadataUuid(String customizableFieldMetadataUuid) { + this.customizableFieldMetadataUuid = customizableFieldMetadataUuid; + } + + public String getEntityUuid() { + return entityUuid; + } + + public void setEntityUuid(String entityUuid) { + this.entityUuid = entityUuid; + } + + public CustomizableFieldContext getContextClass() { + return contextClass; + } + + public void setContextClass(CustomizableFieldContext contextClass) { + this.contextClass = contextClass; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + // ------------------------------------------------------------------------- + // Typed accessors – parse from the raw ISO string stored in {@link #value}. + // Setters serialise back to the canonical ISO representation. + // All getters return {@code null} when the value is absent or unparseable. + // ------------------------------------------------------------------------- + + /** + * Parses {@link #value} as an ISO date ({@code yyyy-MM-dd}). + * + * @return the date, or {@code null} if the value is absent or not a valid ISO date + */ + public LocalDate getValueAsDate() { + if (value == null || value.isEmpty()) { + return null; + } + try { + return LocalDate.parse(value); + } catch (DateTimeParseException e) { + return null; + } + } + + /** + * Stores a {@link LocalDate} as an ISO date string ({@code yyyy-MM-dd}). + * Passing {@code null} clears the value. + */ + public void setValueAsDate(LocalDate date) { + this.value = date != null ? date.toString() : null; + } + + /** + * Parses {@link #value} as an ISO date-time ({@code yyyy-MM-ddTHH:mm} or longer). + * + * @return the date-time, or {@code null} if the value is absent or not a valid ISO date-time + */ + public LocalDateTime getValueAsDateTime() { + if (value == null || value.isEmpty()) { + return null; + } + try { + return LocalDateTime.parse(value); + } catch (DateTimeParseException e) { + return null; + } + } + + /** + * Stores a {@link LocalDateTime} as an ISO date-time string, truncated to minutes + * ({@code yyyy-MM-ddTHH:mm}). + * Passing {@code null} clears the value. + */ + public void setValueAsDateTime(LocalDateTime dateTime) { + this.value = dateTime != null ? dateTime.withSecond(0).withNano(0).toString() : null; + } + + /** + * Parses {@link #value} as a whole number. + * + * @return the integer value, or {@code null} if the value is absent or not a valid integer + */ + public Integer getValueAsInteger() { + if (value == null || value.isEmpty()) { + return null; + } + try { + return Integer.parseInt(value.trim()); + } catch (NumberFormatException e) { + return null; + } + } + + /** + * Stores an {@link Integer} as a plain decimal string. + * Passing {@code null} clears the value. + */ + public void setValueAsInteger(Integer number) { + this.value = number != null ? number.toString() : null; + } + + /** + * Parses {@link #value} as a decimal number. + * + * @return the decimal value, or {@code null} if the value is absent or not a valid number + */ + public BigDecimal getValueAsDecimal() { + if (value == null || value.isEmpty()) { + return null; + } + try { + return new BigDecimal(value.trim().replace(',', '.')); + } catch (NumberFormatException e) { + return null; + } + } + + /** + * Stores a {@link BigDecimal} as a plain decimal string. + * Passing {@code null} clears the value. + */ + public void setValueAsDecimal(BigDecimal decimal) { + this.value = decimal != null ? decimal.toPlainString() : null; + } + + /** + * Parses {@link #value} as a boolean ({@code "true"} / {@code "false"}, case-insensitive). + * + * @return {@link Boolean#TRUE} if the stored value equals {@code "true"} (ignoring case), + * {@link Boolean#FALSE} if it equals {@code "false"} (ignoring case), + * or {@code null} if the value is absent or does not match either token + */ + public Boolean getValueAsBoolean() { + if (value == null || value.isEmpty()) { + return null; + } + if (Boolean.TRUE.toString().equalsIgnoreCase(value.trim())) { + return Boolean.TRUE; + } + if (Boolean.FALSE.toString().equalsIgnoreCase(value.trim())) { + return Boolean.FALSE; + } + return null; + } + + /** + * Stores a {@link Boolean} as {@code "true"} or {@code "false"}. + * Passing {@code null} clears the value. + */ + public void setValueAsBoolean(Boolean bool) { + this.value = bool != null ? bool.toString() : null; + } + + /** + * Parses {@link #value} as a {@link YesNoUnknown} enum constant (case-insensitive name). + * + * @return the matching constant, or {@code null} if the value is absent or not a valid name + */ + public YesNoUnknown getValueAsYesNoUnknown() { + if (value == null || value.isEmpty()) { + return null; + } + try { + return YesNoUnknown.valueOf(value.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + return null; + } + } + + /** + * Stores a {@link YesNoUnknown} constant as its {@link Enum#name()} ({@code "YES"}, {@code "NO"} or {@code "UNKNOWN"}). + * Passing {@code null} clears the value. + */ + public void setValueAsYesNoUnknown(YesNoUnknown yesNoUnknown) { + this.value = yesNoUnknown != null ? yesNoUnknown.name() : null; + } + + /** + * Parses {@link #value} as a JSON array of strings, as used by + * {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#CHECKBOX_LIST}. + * + * @return a mutable {@link LinkedHashSet} of the stored strings (preserving insertion order), + * or an empty set if the value is absent or cannot be parsed + */ + public Set getValueAsStringSet() { + if (value == null || value.isEmpty()) { + return new LinkedHashSet<>(); + } + try { + return MAPPER.readValue(value, new TypeReference>() { + }); + } catch (IOException e) { + return new LinkedHashSet<>(); + } + } + + /** + * Serialises a set of strings to a JSON array and stores it in {@link #value}. + * Passing {@code null} or an empty set clears the value. + */ + public void setValueAsStringSet(Set set) { + if (set == null || set.isEmpty()) { + this.value = null; + return; + } + try { + this.value = MAPPER.writeValueAsString(set); + } catch (JsonProcessingException e) { + this.value = null; + } + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java new file mode 100644 index 00000000000..2c1590f9e26 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java @@ -0,0 +1,80 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import java.util.List; +import java.util.Map; + +import javax.ejb.Remote; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +import de.symeda.sormas.api.common.DeletionDetails; + +/** + * Facade interface for managing customizable field values. + */ +@Remote +public interface CustomizableFieldValueFacade { + + /** + * Load all custom field values for a specific entity + * Returns a map of field metadata UUID -> field value DTO + */ + Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass); + + /** + * Save all custom field values for an entity in a single context. + * fieldValues: map of field metadata UUID -> value DTO (the raw string is read from {@link CustomizableFieldValueDto#getValue()}) + */ + default void saveEntityCustomFields( + String entityUuid, + CustomizableFieldContext contextClass, + Map fieldValues) { + saveEntityCustomFields(entityUuid, Map.of(contextClass, fieldValues)); + } + + /** + * Save all custom field values for an entity across multiple contexts in one call. + * fieldsByContext: map of context -> (field metadata UUID -> value DTO) + */ + void saveEntityCustomFields(String entityUuid, Map> fieldsByContext); + + /** + * Delete all custom field values for an entity + */ + void deleteValuesForEntity(String entityUuid, CustomizableFieldContext contextClass); + + /** + * Get a field value by UUID + */ + CustomizableFieldValueDto getByUuid(String uuid); + + /** + * Get all field values + */ + List getAll(); + + /** + * Save a single customizable field value + */ + CustomizableFieldValueDto save(@Valid @NotNull CustomizableFieldValueDto dto); + + /** + * Delete a customizable field value + */ + void delete(String uuid, DeletionDetails deletionDetails); +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityContext.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityContext.java new file mode 100644 index 00000000000..b6439e91d45 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityContext.java @@ -0,0 +1,60 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import java.util.Optional; + +import de.symeda.sormas.api.Disease; + +/** + * Runtime context against which {@link CustomizableFieldVisibilityRestrictions} are evaluated. + *

+ * Where {@link CustomizableFieldVisibilityRestrictions} represents the stored what should + * restrict this field configuration, this class carries the current runtime values + * (e.g. the disease of the case being viewed) that are tested against those restrictions. + *

+ * New criteria can be added as additional fields without changing the evaluation contract; + * {@link CustomizableFieldVisibilityRestrictions#matches(CustomizableFieldVisibilityContext)} + * handles all criteria in one place. + *

+ * Builder-style fluent setters allow convenient one-liner construction: + * + *

+ * 
+ * CustomizableFieldVisibilityContext ctx = new CustomizableFieldVisibilityContext().withDisease(caze.getDisease());
+ * 
+ */ +public class CustomizableFieldVisibilityContext { + + private Optional disease = Optional.empty(); + + public CustomizableFieldVisibilityContext() { + // no-arg constructor required by deserialization frameworks + } + + public Optional getDisease() { + return disease; + } + + public void setDisease(Optional disease) { + this.disease = disease; + } + + public CustomizableFieldVisibilityContext withDisease(Disease disease) { + this.disease = Optional.ofNullable(disease); + return this; + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityRestrictions.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityRestrictions.java new file mode 100644 index 00000000000..2c14c489989 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityRestrictions.java @@ -0,0 +1,76 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import java.io.Serializable; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; + +import de.symeda.sormas.api.Disease; + +/** + * Typed representation of the {@code visibilityRestrictions} JSON column on + * {@link CustomizableFieldMetadataDto}. + *

+ * Controls when a customizable field is visible to the user: + *

    + *
  • diseases – when set, the field is only visible for cases/entities + * associated with one of the listed {@link Disease} values. When {@code null} + * or empty, the field is visible regardless of disease.
  • + *
+ * Additional visibility criteria can be added here without touching the + * database schema (the whole object is stored as a single {@code jsonb} column). + */ +public class CustomizableFieldVisibilityRestrictions implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * The diseases for which this field should be visible. + * When {@code null} or empty, the field is visible for all diseases. + */ + private List diseases; + + public CustomizableFieldVisibilityRestrictions() { + // Required for JSON deserialization. + } + + public List getDiseases() { + return diseases; + } + + public void setDiseases(List diseases) { + this.diseases = diseases; + } + + /** + * Returns {@code true} when the given runtime context satisfies all restrictions defined + * on this object. A restriction criterion is considered satisfied when either: + *
    + *
  • the restriction list for that criterion is {@code null} or empty (= no restriction), or
  • + *
  • the context's value for that criterion is contained in the restriction list.
  • + *
+ * + * @param context + * the runtime context to evaluate; {@code null} is treated as an empty context + * (all criteria with non-empty restriction lists will fail) + * @return {@code true} if the field should be visible given this context + */ + public boolean matches(CustomizableFieldVisibilityContext context) { + return CollectionUtils.isEmpty(diseases) || (context != null && context.getDisease().filter(diseases::contains).isPresent()); + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldContextConverter.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldContextConverter.java new file mode 100644 index 00000000000..ff4c59920f9 --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldContextConverter.java @@ -0,0 +1,35 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; + +@Converter(autoApply = false) +public class CustomizableFieldContextConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(CustomizableFieldContext attribute) { + return attribute != null ? attribute.getContextClassName() : null; + } + + @Override + public CustomizableFieldContext convertToEntityAttribute(String dbData) { + return dbData != null ? CustomizableFieldContext.fromDtoClassName(dbData) : null; + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java new file mode 100644 index 00000000000..491dcc67684 --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java @@ -0,0 +1,207 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; + +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.TypeDefs; + +import com.vladmihalcea.hibernate.type.json.JsonBinaryType; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldType; +import de.symeda.sormas.backend.common.DeletableAdo; + +/** + * Entity class for customizable field metadata. + * Stores the configuration for custom fields that can be added to entities. + */ +@Entity(name = "customizablefieldmetadata") +@TypeDefs({ + @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) }) +public class CustomizableFieldMetadata extends DeletableAdo { + + private static final long serialVersionUID = 1L; + + public static final String TABLE_NAME = "customizablefieldmetadata"; + + public static final String NAME = "name"; + public static final String DESCRIPTION = "description"; + public static final String FIELD_TYPE = "fieldType"; + public static final String CONTEXT_CLASS = "contextClass"; + public static final String UI_GROUP = "uiGroup"; + public static final String UI_LINE_POSITION = "uiLinePosition"; + public static final String UI_LINE_WEIGHT = "uiLineWeight"; + public static final String ACTIVE = "active"; + public static final String MANDATORY = "mandatory"; + public static final String READ_ONLY = "readOnly"; + public static final String DEFAULT_VALUE = "defaultValue"; + + private String name; + private String description; + private CustomizableFieldType fieldType; + private CustomizableFieldContext contextClass; + private String uiGroup; + private Integer uiLinePosition; + private Float uiLineWeight; + private boolean active = true; + private boolean mandatory = false; + private boolean readOnly = false; + private String defaultValue; + + // JSON-serialized complex properties + private String visibilityRestrictions; + private String customProperties; + private String translations; + + @Column(length = 512, nullable = false) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Column(columnDefinition = "text") + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + public CustomizableFieldType getFieldType() { + return fieldType; + } + + public void setFieldType(CustomizableFieldType fieldType) { + this.fieldType = fieldType; + } + + @Column(length = 256, nullable = false) + @Convert(converter = CustomizableFieldContextConverter.class) + public CustomizableFieldContext getContextClass() { + return contextClass; + } + + public void setContextClass(CustomizableFieldContext contextClass) { + this.contextClass = contextClass; + } + + @Column(length = 256) + public String getUiGroup() { + return uiGroup; + } + + public void setUiGroup(String uiGroup) { + this.uiGroup = uiGroup; + } + + @Column + public Integer getUiLinePosition() { + return uiLinePosition; + } + + public void setUiLinePosition(Integer uiLinePosition) { + this.uiLinePosition = uiLinePosition; + } + + @Column + public Float getUiLineWeight() { + return uiLineWeight; + } + + public void setUiLineWeight(Float uiLineWeight) { + this.uiLineWeight = uiLineWeight; + } + + @Column(nullable = false) + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + @Column + public boolean isMandatory() { + return mandatory; + } + + public void setMandatory(boolean mandatory) { + this.mandatory = mandatory; + } + + @Column + public boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + @Column(columnDefinition = "text") + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + // JSON-serialized properties + @Column(columnDefinition = "jsonb") + @Type(type = "jsonb") + public String getVisibilityRestrictions() { + return visibilityRestrictions; + } + + public void setVisibilityRestrictions(String visibilityRestrictions) { + this.visibilityRestrictions = visibilityRestrictions; + } + + @Column(columnDefinition = "jsonb") + @Type(type = "jsonb") + public String getCustomProperties() { + return customProperties; + } + + public void setCustomProperties(String customProperties) { + this.customProperties = customProperties; + } + + @Column(columnDefinition = "jsonb") + @Type(type = "jsonb") + public String getTranslations() { + return translations; + } + + public void setTranslations(String translations) { + this.translations = translations; + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java new file mode 100644 index 00000000000..80906bdcf44 --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java @@ -0,0 +1,258 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.ejb.EJB; +import javax.ejb.LocalBean; +import javax.ejb.Stateless; + +import org.apache.commons.lang3.StringUtils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.symeda.sormas.api.common.DeletionDetails; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldCustomProperties; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; +import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityRestrictions; +import de.symeda.sormas.backend.util.DtoHelper; + +/** + * Facade EJB implementation for customizable field metadata. + */ +@Stateless(name = "CustomizableFieldMetadataFacade") +public class CustomizableFieldMetadataFacadeEjb implements CustomizableFieldMetadataFacade { + + private static final ObjectMapper mapper = new ObjectMapper(); + + @EJB + private CustomizableFieldMetadataService customizableFieldMetadataService; + + @Override + public List getActiveFieldsForContext(CustomizableFieldContext contextClass) { + return customizableFieldMetadataService.getActiveFieldsForContext(contextClass) + .stream() + .map(CustomizableFieldMetadataFacadeEjb::toDto) + .collect(Collectors.toList()); + } + + @Override + public List getFieldsForUIGroup(String uiGroup) { + return customizableFieldMetadataService.getFieldsForUIGroup(uiGroup) + .stream() + .map(CustomizableFieldMetadataFacadeEjb::toDto) + .collect(Collectors.toList()); + } + + @Override + public List getFieldsOrderedByUIPosition(String uiGroup) { + return getFieldsForUIGroup(uiGroup); // Already ordered in service + } + + @Override + public CustomizableFieldMetadataDto getByNameAndContext(String name, CustomizableFieldContext contextClass) { + CustomizableFieldMetadata entity = customizableFieldMetadataService.getByNameAndContext(name, contextClass); + return entity != null ? toDto(entity) : null; + } + + @Override + public CustomizableFieldMetadataDto cloneField(String sourceUuid, String newName) { + CustomizableFieldMetadata cloned = customizableFieldMetadataService.cloneField(sourceUuid, newName); + return toDto(cloned); + } + + @Override + public void activateField(String uuid) { + CustomizableFieldMetadata field = customizableFieldMetadataService.getByUuid(uuid); + if (field != null) { + field.setActive(true); + customizableFieldMetadataService.ensurePersisted(field); + } + } + + @Override + public void deactivateField(String uuid) { + CustomizableFieldMetadata field = customizableFieldMetadataService.getByUuid(uuid); + if (field != null) { + field.setActive(false); + customizableFieldMetadataService.ensurePersisted(field); + } + } + + @Override + public CustomizableFieldMetadataDto getByUuid(String uuid) { + CustomizableFieldMetadata entity = customizableFieldMetadataService.getByUuid(uuid); + return entity != null ? toDto(entity) : null; + } + + @Override + public List getAll() { + return customizableFieldMetadataService.getAll().stream().map(CustomizableFieldMetadataFacadeEjb::toDto).collect(Collectors.toList()); + } + + @Override + public CustomizableFieldMetadataDto save(CustomizableFieldMetadataDto dto) { + CustomizableFieldMetadata entity = customizableFieldMetadataService.getByUuid(dto.getUuid()); + entity = fillOrBuildEntity(dto, entity, true); + customizableFieldMetadataService.ensurePersisted(entity); + return toDto(entity); + } + + @Override + public void delete(String uuid, DeletionDetails deletionDetails) { + CustomizableFieldMetadata entity = customizableFieldMetadataService.getByUuid(uuid); + if (entity != null) { + customizableFieldMetadataService.deletePermanent(entity); + } + } + + @Override + public boolean exists(String uuid) { + return customizableFieldMetadataService.exists(uuid); + } + + public CustomizableFieldMetadata fillOrBuildEntity( + CustomizableFieldMetadataDto source, + CustomizableFieldMetadata target, + boolean checkChangeDate) { + if (source == null) { + return null; + } + + target = DtoHelper.fillOrBuildEntity(source, target, CustomizableFieldMetadata::new, checkChangeDate); + + target.setName(source.getName()); + target.setDescription(source.getDescription()); + target.setFieldType(source.getFieldType()); + target.setContextClass(source.getContextClass()); + target.setUiGroup(source.getUiGroup()); + target.setUiLinePosition(source.getUiLinePosition()); + target.setUiLineWeight(source.getUiLineWeight()); + target.setActive(source.isActive()); + target.setMandatory(source.isMandatory()); + target.setReadOnly(source.isReadOnly()); + target.setDefaultValue(source.getDefaultValue()); + + // Serialize complex properties to JSON + if (source.getVisibilityRestrictions() != null) { + try { + target.setVisibilityRestrictions(mapper.writeValueAsString(source.getVisibilityRestrictions())); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to serialize visibilityRestrictions", e); + } + } else { + target.setVisibilityRestrictions(null); + } + + if (source.getCustomProperties() != null) { + try { + target.setCustomProperties(mapper.writeValueAsString(source.getCustomProperties())); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to serialize customProperties", e); + } + } else { + target.setCustomProperties(null); + } + if (source.getTranslations() != null) { + try { + target.setTranslations(mapper.writeValueAsString(source.getTranslations())); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to serialize translations", e); + } + } else { + target.setTranslations(null); + } + + return target; + } + + public static CustomizableFieldMetadataDto toDto(CustomizableFieldMetadata source) { + if (source == null) { + return null; + } + + CustomizableFieldMetadataDto target = new CustomizableFieldMetadataDto(); + DtoHelper.fillDto(target, source); + + target.setName(source.getName()); + target.setDescription(source.getDescription()); + target.setFieldType(source.getFieldType()); + target.setContextClass(source.getContextClass()); + target.setUiGroup(source.getUiGroup()); + target.setUiLinePosition(source.getUiLinePosition()); + target.setUiLineWeight(source.getUiLineWeight()); + target.setActive(source.isActive()); + target.setMandatory(source.isMandatory()); + target.setReadOnly(source.isReadOnly()); + target.setDefaultValue(source.getDefaultValue()); + + // Deserialize JSON to maps + target.setVisibilityRestrictions(parseVisibilityRestrictions(source.getVisibilityRestrictions())); + target.setCustomProperties(parseCustomProperties(source.getCustomProperties())); + target.setTranslations(parseTranslations(source.getTranslations())); + + return target; + } + + private static CustomizableFieldVisibilityRestrictions parseVisibilityRestrictions(String json) { + if (StringUtils.isBlank(json)) { + return null; + } + try { + return mapper.readValue(json, CustomizableFieldVisibilityRestrictions.class); + } catch (IOException e) { + throw new IllegalStateException("Failed to parse visibilityRestrictions JSON", e); + } + } + + private static CustomizableFieldCustomProperties parseCustomProperties(String json) { + if (StringUtils.isBlank(json)) { + return null; + } + try { + return mapper.readValue(json, CustomizableFieldCustomProperties.class); + } catch (IOException e) { + throw new IllegalStateException("Failed to parse customProperties JSON", e); + } + } + + private static Map> parseTranslations(String json) { + if (StringUtils.isBlank(json)) { + // we could return a empty map but it might be interpreted as no translaton and postgres might create a '{}' jsonb entry + return Collections.emptyMap(); + } + try { + return mapper.readValue(json, new TypeReference>>() { + }); + } catch (IOException e) { + throw new IllegalStateException("Failed to parse translations JSON", e); + } + } + + @LocalBean + @Stateless + public static class CustomizableFieldMetadataFacadeEjbLocal extends CustomizableFieldMetadataFacadeEjb { + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java new file mode 100644 index 00000000000..06be823c98f --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java @@ -0,0 +1,120 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import java.util.List; + +import javax.ejb.LocalBean; +import javax.ejb.Stateless; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.backend.common.AdoServiceWithUserFilterAndJurisdiction; + +/** + * Service class for customizable field metadata. + */ +@Stateless +@LocalBean +public class CustomizableFieldMetadataService extends AdoServiceWithUserFilterAndJurisdiction { + + public CustomizableFieldMetadataService() { + super(CustomizableFieldMetadata.class); + } + + @Override + @SuppressWarnings("rawtypes") + public Predicate createUserFilter(CriteriaBuilder cb, CriteriaQuery cq, From from) { + // No jurisdiction filtering for customizable fields + return null; + } + + public List getActiveFieldsForContext(CustomizableFieldContext contextClass) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(CustomizableFieldMetadata.class); + Root root = cq.from(CustomizableFieldMetadata.class); + + Predicate predicate = cb.and( + cb.equal(root.get(CustomizableFieldMetadata.CONTEXT_CLASS), contextClass), + cb.equal(root.get(CustomizableFieldMetadata.ACTIVE), true)); + + cq.where(predicate); + cq.orderBy(cb.asc(root.get(CustomizableFieldMetadata.UI_LINE_POSITION))); + + return em.createQuery(cq).getResultList(); + } + + public List getFieldsForUIGroup(String uiGroup) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(CustomizableFieldMetadata.class); + Root root = cq.from(CustomizableFieldMetadata.class); + + Predicate predicate = cb.equal(root.get(CustomizableFieldMetadata.UI_GROUP), uiGroup); + + cq.where(predicate); + cq.orderBy(cb.asc(root.get(CustomizableFieldMetadata.UI_LINE_POSITION))); + + return em.createQuery(cq).getResultList(); + } + + public CustomizableFieldMetadata getByNameAndContext(String name, CustomizableFieldContext contextClass) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(CustomizableFieldMetadata.class); + Root root = cq.from(CustomizableFieldMetadata.class); + + Predicate predicate = cb + .and(cb.equal(root.get(CustomizableFieldMetadata.NAME), name), cb.equal(root.get(CustomizableFieldMetadata.CONTEXT_CLASS), contextClass)); + + cq.where(predicate); + + return em.createQuery(cq).getResultStream().findFirst().orElse(null); + } + + public CustomizableFieldMetadata cloneField(String sourceUuid, String newName) { + CustomizableFieldMetadata source = getByUuid(sourceUuid); + if (source == null) { + throw new IllegalArgumentException("Source field not found: " + sourceUuid); + } + + // Validate new name is unique within context + if (getByNameAndContext(newName, source.getContextClass()) != null) { + throw new IllegalArgumentException("Field name already exists in this context: " + newName); + } + + CustomizableFieldMetadata clone = new CustomizableFieldMetadata(); + clone.setName(newName); + clone.setDescription(source.getDescription()); + clone.setFieldType(source.getFieldType()); + clone.setContextClass(source.getContextClass()); + clone.setUiGroup(source.getUiGroup()); + clone.setUiLinePosition(source.getUiLinePosition()); + clone.setUiLineWeight(source.getUiLineWeight()); + clone.setActive(source.isActive()); + clone.setMandatory(source.isMandatory()); + clone.setReadOnly(source.isReadOnly()); + clone.setDefaultValue(source.getDefaultValue()); + clone.setVisibilityRestrictions(source.getVisibilityRestrictions()); + clone.setCustomProperties(source.getCustomProperties()); + clone.setTranslations(source.getTranslations()); + + ensurePersisted(clone); + return clone; + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java new file mode 100644 index 00000000000..d4176b5681e --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java @@ -0,0 +1,86 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.backend.common.DeletableAdo; + +/** + * Entity class for customizable field values. + * Stores the actual value for a custom field for a specific entity instance. + */ +@Entity(name = "customizablefieldvalue") +public class CustomizableFieldValue extends DeletableAdo { + + private static final long serialVersionUID = 1L; + + public static final String TABLE_NAME = "customizablefieldvalue"; + + public static final String CUSTOMIZABLE_FIELD_METADATA = "customizableFieldMetadata"; + public static final String ENTITY_UUID = "entityUuid"; + public static final String CONTEXT_CLASS = "contextClass"; + public static final String VALUE = "value"; + + private CustomizableFieldMetadata customizableFieldMetadata; + private String entityUuid; + private CustomizableFieldContext contextClass; + private String value; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "customizablefieldmetadata_id", nullable = false) + public CustomizableFieldMetadata getCustomizableFieldMetadata() { + return customizableFieldMetadata; + } + + public void setCustomizableFieldMetadata(CustomizableFieldMetadata customizableFieldMetadata) { + this.customizableFieldMetadata = customizableFieldMetadata; + } + + @Column(length = 36, nullable = false) + public String getEntityUuid() { + return entityUuid; + } + + public void setEntityUuid(String entityUuid) { + this.entityUuid = entityUuid; + } + + @Column(length = 256, nullable = false) + @Convert(converter = CustomizableFieldContextConverter.class) + public CustomizableFieldContext getContextClass() { + return contextClass; + } + + public void setContextClass(CustomizableFieldContext contextClass) { + this.contextClass = contextClass; + } + + @Column(columnDefinition = "text") + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java new file mode 100644 index 00000000000..d6659ea648e --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java @@ -0,0 +1,134 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.ejb.EJB; +import javax.ejb.LocalBean; +import javax.ejb.Stateless; + +import de.symeda.sormas.api.common.DeletionDetails; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade; +import de.symeda.sormas.backend.util.DtoHelper; + +/** + * Facade EJB implementation for customizable field values. + */ +@Stateless(name = "CustomizableFieldValueFacade") +public class CustomizableFieldValueFacadeEjb implements CustomizableFieldValueFacade { + + @EJB + private CustomizableFieldValueService customizableFieldValueService; + + @EJB + private CustomizableFieldMetadataService customizableFieldMetadataService; + + @Override + public Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { + Map entities = customizableFieldValueService.getValuesForEntity(entityUuid, contextClass); + + Map result = new HashMap<>(); + for (Map.Entry entry : entities.entrySet()) { + result.put(entry.getKey(), toDto(entry.getValue())); + } + return result; + } + + @Override + public void saveEntityCustomFields(String entityUuid, Map> fieldsByContext) { + fieldsByContext.forEach((context, fields) -> customizableFieldValueService.saveEntityValues(entityUuid, context, fields)); + } + + @Override + public void deleteValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { + customizableFieldValueService.deleteValuesForEntity(entityUuid, contextClass); + } + + @Override + public CustomizableFieldValueDto getByUuid(String uuid) { + CustomizableFieldValue entity = customizableFieldValueService.getByUuid(uuid); + return entity != null ? toDto(entity) : null; + } + + @Override + public List getAll() { + return customizableFieldValueService.getAll().stream().map(CustomizableFieldValueFacadeEjb::toDto).collect(Collectors.toList()); + } + + @Override + public CustomizableFieldValueDto save(CustomizableFieldValueDto dto) { + CustomizableFieldValue entity = customizableFieldValueService.getByUuid(dto.getUuid()); + entity = fillOrBuildEntity(dto, entity, true); + customizableFieldValueService.ensurePersisted(entity); + return toDto(entity); + } + + @Override + public void delete(String uuid, DeletionDetails deletionDetails) { + CustomizableFieldValue entity = customizableFieldValueService.getByUuid(uuid); + if (entity != null) { + customizableFieldValueService.deletePermanent(entity); + } + } + + public CustomizableFieldValue fillOrBuildEntity(CustomizableFieldValueDto source, CustomizableFieldValue target, boolean checkChangeDate) { + if (source == null) { + return null; + } + + target = DtoHelper.fillOrBuildEntity(source, target, CustomizableFieldValue::new, checkChangeDate); + + // Load the metadata reference + CustomizableFieldMetadata metadata = customizableFieldMetadataService.getByUuid(source.getCustomizableFieldMetadataUuid()); + if (metadata == null) { + throw new IllegalArgumentException("Field metadata not found: " + source.getCustomizableFieldMetadataUuid()); + } + + target.setCustomizableFieldMetadata(metadata); + target.setEntityUuid(source.getEntityUuid()); + target.setContextClass(source.getContextClass()); + target.setValue(source.getValue()); + + return target; + } + + public static CustomizableFieldValueDto toDto(CustomizableFieldValue source) { + if (source == null) { + return null; + } + + CustomizableFieldValueDto target = new CustomizableFieldValueDto(); + DtoHelper.fillDto(target, source); + + target.setCustomizableFieldMetadataUuid(source.getCustomizableFieldMetadata().getUuid()); + target.setEntityUuid(source.getEntityUuid()); + target.setContextClass(source.getContextClass()); + target.setValue(source.getValue()); + + return target; + } + + @LocalBean + @Stateless + public static class CustomizableFieldValueFacadeEjbLocal extends CustomizableFieldValueFacadeEjb { + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java new file mode 100644 index 00000000000..bcf5d1f7e8f --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java @@ -0,0 +1,110 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ejb.EJB; +import javax.ejb.LocalBean; +import javax.ejb.Stateless; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.backend.common.AdoServiceWithUserFilterAndJurisdiction; + +/** + * Service class for customizable field values. + */ +@Stateless +@LocalBean +public class CustomizableFieldValueService extends AdoServiceWithUserFilterAndJurisdiction { + + @EJB + private CustomizableFieldMetadataService customizableFieldMetadataService; + + public CustomizableFieldValueService() { + super(CustomizableFieldValue.class); + } + + @Override + @SuppressWarnings("rawtypes") + public Predicate createUserFilter(CriteriaBuilder cb, CriteriaQuery cq, From from) { + // No jurisdiction filtering for customizable field values + return null; + } + + public Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(CustomizableFieldValue.class); + Root root = cq.from(CustomizableFieldValue.class); + + Predicate predicate = cb.and( + cb.equal(root.get(CustomizableFieldValue.ENTITY_UUID), entityUuid), + cb.equal(root.get(CustomizableFieldValue.CONTEXT_CLASS), contextClass)); + + cq.where(predicate); + + List values = em.createQuery(cq).getResultList(); + + // Convert to map for easier access by field metadata UUID + Map result = new HashMap<>(); + for (CustomizableFieldValue value : values) { + result.put(value.getCustomizableFieldMetadata().getUuid(), value); + } + return result; + } + + public void saveEntityValues(String entityUuid, CustomizableFieldContext contextClass, Map fieldUuidToValue) { + // Get existing values + Map existing = getValuesForEntity(entityUuid, contextClass); + + // Update or create values + for (Map.Entry entry : fieldUuidToValue.entrySet()) { + String fieldMetadataUuid = entry.getKey(); + CustomizableFieldMetadata metadata = customizableFieldMetadataService.getByUuid(fieldMetadataUuid); + + if (metadata == null) { + throw new IllegalArgumentException("Field metadata not found: " + fieldMetadataUuid); + } + + CustomizableFieldValue value = existing.getOrDefault(fieldMetadataUuid, createNewValue(metadata, entityUuid, contextClass)); + value.setValue(entry.getValue().getValue()); + ensurePersisted(value); + } + } + + public void deleteValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { + Map values = getValuesForEntity(entityUuid, contextClass); + for (CustomizableFieldValue value : values.values()) { + em.remove(value); + } + } + + private CustomizableFieldValue createNewValue(CustomizableFieldMetadata metadata, String entityUuid, CustomizableFieldContext contextClass) { + CustomizableFieldValue value = new CustomizableFieldValue(); + value.setCustomizableFieldMetadata(metadata); + value.setEntityUuid(entityUuid); + value.setContextClass(contextClass); + return value; + } +} diff --git a/sormas-backend/src/main/resources/META-INF/persistence.xml b/sormas-backend/src/main/resources/META-INF/persistence.xml index c403dbcdcf0..c48dbc5ee34 100644 --- a/sormas-backend/src/main/resources/META-INF/persistence.xml +++ b/sormas-backend/src/main/resources/META-INF/persistence.xml @@ -75,6 +75,8 @@ de.symeda.sormas.backend.infrastructure.subcontinent.Subcontinent de.symeda.sormas.backend.sormastosormas.share.incoming.SormasToSormasShareRequest de.symeda.sormas.backend.customizableenum.CustomizableEnumValue + de.symeda.sormas.backend.customizablefield.CustomizableFieldMetadata + de.symeda.sormas.backend.customizablefield.CustomizableFieldValue de.symeda.sormas.backend.immunization.entity.BaseImmunization de.symeda.sormas.backend.immunization.entity.Immunization de.symeda.sormas.backend.immunization.entity.DirectoryImmunization diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 7e3a07b7007..44a893eaa04 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15367,4 +15367,144 @@ ALTER TABLE externalmessage_history ALTER COLUMN casecomments TYPE text; INSERT INTO schema_version (version_number, comment) VALUES (613, '#13879 - Increased length of case comments'); +-- #13828 - Customizable Fields + +CREATE TABLE IF NOT EXISTS customizablefieldmetadata ( + id bigint NOT NULL, + uuid character varying(36) NOT NULL UNIQUE, + changeDate timestamp without time zone NOT NULL DEFAULT NOW(), + creationDate timestamp without time zone NOT NULL DEFAULT NOW(), + deleted boolean NOT NULL DEFAULT false, + deletionreason varchar(255), + otherdeletionreason text, + + -- Core field metadata + name character varying(512) NOT NULL, + description text, + fieldType character varying(50) NOT NULL, -- TEXT, DATE, COMBOBOX, YES_NO_UNKNOWN, CHECKBOX_LIST, RADIO_BUTTON_LIST + defaultValue text, + + -- Constraints + mandatory boolean NOT NULL DEFAULT false, + readOnly boolean NOT NULL DEFAULT false, + active boolean NOT NULL DEFAULT true, + + -- Context and UI placement + contextClass character varying(256) NOT NULL, + uiGroup character varying(256), + uiLinePosition integer, + uiLineWeight float4, + + -- Complex JSON-serialized properties + visibilityRestrictions jsonb, + customProperties jsonb, + translations jsonb, + + change_user_id bigint, + sys_period tstzrange NOT NULL, + + PRIMARY KEY (id), + UNIQUE(name, contextClass) +); + +CREATE INDEX idx_customizablefieldmetadata_uuid + ON customizablefieldmetadata (uuid); +CREATE INDEX idx_customizablefieldmetadata_contextClass + ON customizablefieldmetadata (contextClass); +CREATE INDEX idx_customizablefieldmetadata_uiGroup + ON customizablefieldmetadata (uiGroup); +CREATE INDEX idx_customizablefieldmetadata_active + ON customizablefieldmetadata (active); +CREATE INDEX idx_customizablefieldmetadata_deleted + ON customizablefieldmetadata (deleted); + +ALTER TABLE customizablefieldmetadata OWNER TO sormas_user; +ALTER TABLE customizablefieldmetadata ADD CONSTRAINT fk_change_user_id FOREIGN KEY (change_user_id) REFERENCES users (id); +ALTER INDEX idx_customizablefieldmetadata_uuid OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldmetadata_contextClass OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldmetadata_uiGroup OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldmetadata_active OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldmetadata_deleted OWNER TO sormas_user; + +-- CustomizableFieldMetadata history tables +CREATE TABLE customizablefieldmetadata_history (LIKE customizablefieldmetadata); +CREATE TRIGGER versioning_trigger +BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldmetadata +FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldmetadata_history', true); +ALTER TABLE customizablefieldmetadata_history OWNER TO sormas_user; + +DROP TRIGGER IF EXISTS delete_history_trigger_customizablefieldmetadata ON customizablefieldmetadata; +CREATE TRIGGER delete_history_trigger_customizablefieldmetadata + AFTER DELETE ON customizablefieldmetadata + FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldmetadata_history', 'id'); + +-- Create CustomizableFieldValue table +CREATE TABLE IF NOT EXISTS customizablefieldvalue ( + id bigint NOT NULL, + uuid character varying(36) NOT NULL UNIQUE, + changeDate timestamp(3) NOT NULL DEFAULT NOW(), + creationDate timestamp(3) NOT NULL DEFAULT NOW(), + deleted boolean NOT NULL DEFAULT false, + deletionreason varchar(255), + otherdeletionreason text, + + -- Fkey to metadata + customizablefieldmetadata_id bigint NOT NULL REFERENCES customizablefieldmetadata(id) ON DELETE CASCADE, + + -- Generic entity reference + entityUuid character varying(36) NOT NULL, + contextClass character varying(256) NOT NULL, + + -- Value storage (text for all types, type conversion happens in service) + value text, + + change_user_id bigint, + sys_period tstzrange NOT NULL, + + PRIMARY KEY (id), + UNIQUE(customizablefieldmetadata_id, entityUuid, contextClass) +); + +CREATE INDEX idx_customizablefieldvalue_uuid + ON customizablefieldvalue (uuid); +CREATE INDEX idx_customizablefieldvalue_entityUuid + ON customizablefieldvalue (entityUuid); +CREATE INDEX idx_customizablefieldvalue_contextEntity + ON customizablefieldvalue (contextClass, entityUuid); +CREATE INDEX idx_customizablefieldvalue_fieldMetadata + ON customizablefieldvalue (customizablefieldmetadata_id); +CREATE INDEX idx_customizablefieldvalue_deleted + ON customizablefieldvalue (deleted); + +ALTER TABLE customizablefieldvalue OWNER TO sormas_user; + +ALTER TABLE customizablefieldvalue + ADD CONSTRAINT fk_customizablefieldvalue_metadata + FOREIGN KEY (customizablefieldmetadata_id) + REFERENCES customizablefieldmetadata(id) + ON DELETE CASCADE; + +ALTER TABLE customizablefieldvalue ADD CONSTRAINT fk_change_user_id FOREIGN KEY (change_user_id) REFERENCES users (id); + +ALTER INDEX idx_customizablefieldvalue_uuid OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldvalue_entityUuid OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldvalue_contextEntity OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldvalue_fieldMetadata OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldvalue_deleted OWNER TO sormas_user; + +-- CustomizableFieldValue history tables +CREATE TABLE customizablefieldvalue_history (LIKE customizablefieldvalue); +CREATE TRIGGER versioning_trigger +BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldvalue +FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldvalue_history', true); + +DROP TRIGGER IF EXISTS delete_history_trigger_customizablefieldvalue ON customizablefieldvalue; +CREATE TRIGGER delete_history_trigger_customizablefieldvalue + AFTER DELETE ON customizablefieldvalue + FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldvalue_history', 'id'); + +ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; + +INSERT INTO schema_version (version_number, comment) VALUES (614, '#13828 - Add history tables for customizable fields'); + -- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java new file mode 100644 index 00000000000..b945a4aa810 --- /dev/null +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java @@ -0,0 +1,112 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.caze.CaseClassification; +import de.symeda.sormas.api.caze.CaseDataDto; +import de.symeda.sormas.api.caze.InvestigationStatus; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; +import de.symeda.sormas.api.customizablefield.CustomizableFieldType; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade; +import de.symeda.sormas.api.person.PersonDto; +import de.symeda.sormas.api.person.Sex; +import de.symeda.sormas.api.user.UserDto; +import de.symeda.sormas.backend.AbstractBeanTest; +import de.symeda.sormas.backend.TestDataCreator.RDCF; + +class CustomizableFieldFacadeEjbTest extends AbstractBeanTest { + + private RDCF rdcf; + private UserDto surveillanceSupervisor; + + @Override + public void init() { + super.init(); + rdcf = creator.createRDCF("Region", "District", "Community", "Facility"); + surveillanceSupervisor = creator.createSurveillanceSupervisor(rdcf); + } + + @ParameterizedTest + @EnumSource(CustomizableFieldContext.class) + void testRenderCustomizableFields(CustomizableFieldContext context) { + CustomizableFieldMetadataFacade metadataFacade = getBean(CustomizableFieldMetadataFacadeEjb.CustomizableFieldMetadataFacadeEjbLocal.class); + CustomizableFieldValueFacade valueFacade = getBean(CustomizableFieldValueFacadeEjb.CustomizableFieldValueFacadeEjbLocal.class); + + CustomizableFieldMetadataDto metadata = new CustomizableFieldMetadataDto(); + metadata.setName("customField_" + context.name().toLowerCase()); + metadata.setFieldType(CustomizableFieldType.TEXT); + metadata.setContextClass(context); + metadata.setUiGroup(context.name().toLowerCase()); + metadata.setUiLinePosition(1); + + CustomizableFieldMetadataDto savedMetadata = metadataFacade.save(metadata); + assertThat(savedMetadata.getUuid(), is(notNullValue())); + + PersonDto person = creator.createPerson("Case", "Person", Sex.MALE, 1980, 1, 1); + CaseDataDto caze = creator.createCase( + surveillanceSupervisor.toReference(), + person.toReference(), + Disease.EVD, + CaseClassification.PROBABLE, + InvestigationStatus.PENDING, + new java.util.Date(), + rdcf); + + String entityUuid = getEntityUuidForContext(context, caze); + CustomizableFieldValueDto valueDto = new CustomizableFieldValueDto(); + valueDto.setValue("custom-value"); + Map fieldValues = Map.of(savedMetadata.getUuid(), valueDto); + valueFacade.saveEntityCustomFields(entityUuid, context, fieldValues); + + List activeFields = metadataFacade.getActiveFieldsForContext(context); + Map values = valueFacade.getValuesForEntity(entityUuid, context); + + assertThat(activeFields, hasSize(1)); + assertThat(activeFields.get(0).getUuid(), is(savedMetadata.getUuid())); + assertThat(activeFields.get(0).getName(), is(equalTo("customField_" + context.name().toLowerCase()))); + + assertThat(values, hasKey(savedMetadata.getUuid())); + assertThat(values.get(savedMetadata.getUuid()).getValue(), is(equalTo("custom-value"))); + } + + private String getEntityUuidForContext(CustomizableFieldContext context, CaseDataDto caze) { + switch (context) { + case CASE: + return caze.getUuid(); + case EPIDATA: + return caze.getEpiData().getUuid(); + default: + throw new IllegalArgumentException("Unhandled context: " + context); + } + } +} diff --git a/sormas-backend/src/test/resources/META-INF/persistence.xml b/sormas-backend/src/test/resources/META-INF/persistence.xml index ad44e72739e..99c650ceb7e 100644 --- a/sormas-backend/src/test/resources/META-INF/persistence.xml +++ b/sormas-backend/src/test/resources/META-INF/persistence.xml @@ -76,6 +76,8 @@ de.symeda.sormas.backend.infrastructure.subcontinent.Subcontinent de.symeda.sormas.backend.sormastosormas.share.incoming.SormasToSormasShareRequest de.symeda.sormas.backend.customizableenum.CustomizableEnumValue + de.symeda.sormas.backend.customizablefield.CustomizableFieldMetadata + de.symeda.sormas.backend.customizablefield.CustomizableFieldValue de.symeda.sormas.backend.immunization.entity.BaseImmunization de.symeda.sormas.backend.immunization.entity.Immunization de.symeda.sormas.backend.immunization.entity.DirectoryImmunization diff --git a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java new file mode 100644 index 00000000000..10f8b8a5111 --- /dev/null +++ b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java @@ -0,0 +1,127 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.rest.resources; + +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.common.DeletionDetails; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; + +/** + * REST resource for managing customizable field metadata. + */ +@Path("/customizablefieldmetadata") +@Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") +@Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8") +public class CustomizableFieldMetadataResource { + + private CustomizableFieldMetadataFacade getFacade() { + return FacadeProvider.getCustomizableFieldMetadataFacade(); + } + + @GET + @Path("/all") + public List getAll() { + return getFacade().getAll(); + } + + @GET + @Path("/{uuid}") + public CustomizableFieldMetadataDto getByUuid(@PathParam("uuid") String uuid) { + return getFacade().getByUuid(uuid); + } + + @GET + @Path("/context/{contextClass}") + public List getActiveFieldsForContext(@PathParam("contextClass") CustomizableFieldContext contextClass) { + return getFacade().getActiveFieldsForContext(contextClass); + } + + @GET + @Path("/uigroup/{uiGroup}") + public List getFieldsForUIGroup(@PathParam("uiGroup") String uiGroup) { + return getFacade().getFieldsOrderedByUIPosition(uiGroup); + } + + @GET + @Path("/byname") + public CustomizableFieldMetadataDto getByNameAndContext( + @QueryParam("name") String name, + @QueryParam("contextClass") CustomizableFieldContext contextClass) { + return getFacade().getByNameAndContext(name, contextClass); + } + + @POST + @Path("/save") + public CustomizableFieldMetadataDto save(CustomizableFieldMetadataDto dto) { + return getFacade().save(dto); + } + + @PUT + @Path("/{uuid}") + public CustomizableFieldMetadataDto update(@PathParam("uuid") String uuid, CustomizableFieldMetadataDto dto) { + dto.setUuid(uuid); + return getFacade().save(dto); + } + + @DELETE + @Path("/{uuid}") + public Response delete(@PathParam("uuid") String uuid) { + getFacade().delete(uuid, new DeletionDetails()); + return Response.ok().build(); + } + + @POST + @Path("/{uuid}/clone") + public CustomizableFieldMetadataDto cloneField(@PathParam("uuid") String uuid, @QueryParam("newName") String newName) { + return getFacade().cloneField(uuid, newName); + } + + @POST + @Path("/{uuid}/activate") + public Response activate(@PathParam("uuid") String uuid) { + getFacade().activateField(uuid); + return Response.ok().build(); + } + + @POST + @Path("/{uuid}/deactivate") + public Response deactivate(@PathParam("uuid") String uuid) { + getFacade().deactivateField(uuid); + return Response.ok().build(); + } + + @GET + @Path("/{uuid}/exists") + public boolean exists(@PathParam("uuid") String uuid) { + return getFacade().exists(uuid); + } +} diff --git a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldValueResource.java b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldValueResource.java new file mode 100644 index 00000000000..94fe4dc319a --- /dev/null +++ b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldValueResource.java @@ -0,0 +1,109 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.rest.resources; + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.common.DeletionDetails; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade; + +/** + * REST resource for managing customizable field values. + */ +@Path("/customizablefieldvalue") +@Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") +@Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8") +public class CustomizableFieldValueResource { + + private CustomizableFieldValueFacade getFacade() { + return FacadeProvider.getCustomizableFieldValueFacade(); + } + + @GET + @Path("/all") + public List getAll() { + return getFacade().getAll(); + } + + @GET + @Path("/{uuid}") + public CustomizableFieldValueDto getByUuid(@PathParam("uuid") String uuid) { + return getFacade().getByUuid(uuid); + } + + @GET + @Path("/entity/{entityUuid}") + public Map getValuesForEntity( + @PathParam("entityUuid") String entityUuid, + @QueryParam("contextClass") CustomizableFieldContext contextClass) { + return getFacade().getValuesForEntity(entityUuid, contextClass); + } + + @POST + @Path("/entity/{entityUuid}/save") + public Response saveEntityCustomFields( + @PathParam("entityUuid") String entityUuid, + @QueryParam("contextClass") CustomizableFieldContext contextClass, + Map fieldValues) { + getFacade().saveEntityCustomFields(entityUuid, contextClass, fieldValues); + return Response.ok().build(); + } + + @DELETE + @Path("/entity/{entityUuid}") + public Response deleteValuesForEntity( + @PathParam("entityUuid") String entityUuid, + @QueryParam("contextClass") CustomizableFieldContext contextClass) { + getFacade().deleteValuesForEntity(entityUuid, contextClass); + return Response.ok().build(); + } + + @POST + @Path("/save") + public CustomizableFieldValueDto save(CustomizableFieldValueDto dto) { + return getFacade().save(dto); + } + + @PUT + @Path("/{uuid}") + public CustomizableFieldValueDto update(@PathParam("uuid") String uuid, CustomizableFieldValueDto dto) { + dto.setUuid(uuid); + return getFacade().save(dto); + } + + @DELETE + @Path("/{uuid}") + public Response delete(@PathParam("uuid") String uuid) { + getFacade().delete(uuid, new DeletionDetails()); + return Response.ok().build(); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java index 1d75e388707..55a0eeaff25 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java @@ -21,8 +21,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -46,6 +48,8 @@ import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.InfrastructureDataReferenceDto; import de.symeda.sormas.api.customizableenum.CustomizableEnum; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.i18n.Captions; import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.i18n.Strings; @@ -70,6 +74,9 @@ public abstract class AbstractEditForm extends AbstractForm implements private ComboBox diseaseField; private boolean setServerDiseaseAsDefault; + private List customizableFieldsMetadata; + private Map customizableFieldsValues; + protected AbstractEditForm(Class type, String propertyI18nPrefix) { this(type, propertyI18nPrefix, true, null, null); } @@ -134,6 +141,48 @@ public void setValue(DTO newFieldValue) throws com.vaadin.v7.data.Property.ReadO } } + /** + * Set customizable field metadata for this form. + * This data should be pre-loaded by the controller and passed to the form. + * The form will use this metadata to create and configure customizable field components. + * + * @param metadata + * List of customizable field metadata DTOs pre-loaded by the controller + */ + public void setCustomizableFieldsMetadata(List metadata) { + this.customizableFieldsMetadata = metadata; + } + + /** + * Get customizable field metadata. + * + * @return List of customizable field metadata, or null if not set + */ + protected List getCustomizableFieldsMetadata() { + return customizableFieldsMetadata; + } + + /** + * Set customizable field values for this form. + * This data should be pre-loaded by the controller and passed to the form. + * The form will use this data to populate customizable field components. + * + * @param values + * Map of customizable field values keyed by field metadata UUID, pre-loaded by the controller + */ + public void setCustomizableFieldsValues(Map values) { + this.customizableFieldsValues = values; + } + + /** + * Get customizable field values. + * + * @return Map of customizable field values, or empty map if not set + */ + protected Map getCustomizableFieldsValues() { + return customizableFieldsValues != null ? customizableFieldsValues : new HashMap<>(); + } + @Override public boolean isModified() { if (getFieldGroup().isModified()) { diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java new file mode 100644 index 00000000000..2f7c43f5a82 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java @@ -0,0 +1,295 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.vaadin.data.HasValue; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.VerticalLayout; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityRestrictions; +import de.symeda.sormas.ui.utils.components.customizablefield.CustomizableFieldInput; +import de.symeda.sormas.ui.utils.components.customizablefield.CustomizableFieldInputFactory; + +/** + * Container component for managing multiple customizable field value fields belonging to a specific UI group. + * + * Each group is bound to one {@code uiGroup} value (from {@link CustomizableFieldMetadataDto#UI_GROUP}). + * When rendering, only fields whose metadata {@code uiGroup} matches this group's value are displayed. + * + * Usage: + * + *
+ * CustomizableFieldsGroup group = new CustomizableFieldsGroup("myGroup");
+ * group.setFieldsMetadata(fieldsList);   // full list – group filters by its own uiGroup
+ * group.setFieldsValues(valuesMap);
+ * group.updateFieldsDisplay();
+ * form.addComponent(group, "myGroup");   // use the uiGroup as the layout location ID
+ * 
+ */ +public class CustomizableFieldsGroup extends VerticalLayout { + + private static final long serialVersionUID = 1L; + + private static final String STYLE_NAME_CUSTOMIZABLE_FIELDS_GROUP = "customizable-fields-group"; + + private final String uiGroup; + private final Map> fieldComponents; + private final List> valueChangeListeners = new ArrayList<>(); + private List fieldsMetadata; + private Map fieldsValues; + private CustomizableFieldVisibilityContext visibilityContext; + + /** + * Creates a group scoped to the given UI group. + * Only fields whose metadata {@code uiGroup} equals this value will be rendered. + * + * @param uiGroup + * the UI group identifier, must not be null + */ + public CustomizableFieldsGroup(String uiGroup) { + if (uiGroup == null) { + throw new IllegalArgumentException("uiGroup must not be null"); + } + this.uiGroup = uiGroup; + this.fieldComponents = new HashMap<>(); + this.setSpacing(true); + this.setMargin(false); + this.setWidth(100, Unit.PERCENTAGE); + this.addStyleName(STYLE_NAME_CUSTOMIZABLE_FIELDS_GROUP); + this.addStyleName(uiGroup); + + this.setId(uiGroup); + } + + /** + * Returns the UI group this group is scoped to. + * + * @return the UI group identifier + */ + public String getUiGroup() { + return uiGroup; + } + + /** + * Registers a listener that is called whenever any field in this group changes its value. + * The listener is also applied retroactively to any fields already rendered. + * + * @param listener + * the listener to register + */ + @SuppressWarnings("unchecked") + public void addValueChangeListener(HasValue.ValueChangeListener listener) { + HasValue.ValueChangeListener typed = (HasValue.ValueChangeListener) listener; + valueChangeListeners.add(typed); + for (CustomizableFieldInput field : fieldComponents.values()) { + ((CustomizableFieldInput) field).addValueChangeListener(typed); + } + } + + /** + * Sets the runtime visibility context used to evaluate + * {@link CustomizableFieldVisibilityRestrictions} during {@link #updateFieldsDisplay()}. + * Fields whose restrictions do not match the context are omitted from the rendered output. + * When {@code null}, all restriction checks are skipped (fields are shown regardless). + * + * @param visibilityContext + * the current runtime values (e.g. the active disease) + */ + public void setVisibilityContext(CustomizableFieldVisibilityContext visibilityContext) { + this.visibilityContext = visibilityContext; + } + + /** + * Sets the metadata for customizable fields that can be displayed in this panel. + * After setting metadata, you can call {@link #updateFieldsDisplay()} to render the fields. + * + * @param metadata + * the list of field metadata DTOs + */ + public void setFieldsMetadata(List metadata) { + this.fieldsMetadata = metadata; + } + + /** + * Sets the values for customizable fields to be displayed. + * Key should be the field metadata UUID. + * + * @param values + * map of field values keyed by metadata UUID + */ + public void setFieldsValues(Map values) { + this.fieldsValues = values; + } + + /** + * Updates the display of all fields based on current metadata and values. + *

+ * Active fields whose {@code uiGroup} matches this group are: + *

    + *
  1. sorted by {@link CustomizableFieldMetadataDto#getUiLinePosition()} ascending + * (fields with {@code null} position are placed last, each on its own row);
  2. + *
  3. grouped by {@code uiLinePosition} – fields sharing the same (non-null) position + * are placed side-by-side in a {@link HorizontalLayout};
  4. + *
  5. sized within their row proportionally to + * {@link CustomizableFieldMetadataDto#getUiLineWeight()} (Vaadin expand ratio; + * defaults to {@code 1.0} when the weight is {@code null}).
  6. + *
+ */ + public void updateFieldsDisplay() { + removeAllComponents(); + fieldComponents.clear(); + + if (fieldsMetadata == null || fieldsMetadata.isEmpty()) { + return; + } + + // Collect active fields for this group that match the current visibility context + List groupFields = new ArrayList<>(); + for (CustomizableFieldMetadataDto metadata : fieldsMetadata) { + boolean visibilityContextMatch = visibilityContext == null + || metadata.getVisibilityRestrictions() == null + || metadata.getVisibilityRestrictions().matches(visibilityContext); + if (metadata.isActive() && uiGroup.equals(metadata.getUiGroup()) && visibilityContextMatch) { + groupFields.add(metadata); + } + } + + if (groupFields.isEmpty()) { + return; + } + + // Sort: non-null positions first (ascending), null positions last (stable) + groupFields.sort(Comparator.comparing(CustomizableFieldMetadataDto::getUiLinePosition, Comparator.nullsLast(Comparator.naturalOrder()))); + + // Group by uiLinePosition; null-positioned fields each get their own synthetic key + // using a LinkedHashMap to preserve insertion (sorted) order. + LinkedHashMap> lines = new LinkedHashMap<>(); + int nullKeyCounter = 0; + for (CustomizableFieldMetadataDto metadata : groupFields) { + Object lineKey = metadata.getUiLinePosition() != null ? metadata.getUiLinePosition() : "__null_" + nullKeyCounter++; + lines.computeIfAbsent(lineKey, k -> new ArrayList<>()).add(metadata); + } + + // Render each line as a HorizontalLayout row + for (List lineFields : lines.values()) { + addLineRow(lineFields); + } + } + + /** + * Renders a single horizontal row for the given list of fields. + * Each field's width within the row is controlled by its {@code uiLineWeight} + * (used as a Vaadin expand ratio; defaults to {@code 1.0} when absent). + * + * @param lineFields + * fields to place side-by-side on this row + */ + private void addLineRow(List lineFields) { + HorizontalLayout row = new HorizontalLayout(); + row.setWidth(100, Unit.PERCENTAGE); + row.setSpacing(true); + row.setMargin(false); + + for (CustomizableFieldMetadataDto metadata : lineFields) { + CustomizableFieldInput field = CustomizableFieldInputFactory.create(metadata); + + CustomizableFieldValueDto dto = (fieldsValues != null && fieldsValues.containsKey(metadata.getUuid())) + ? fieldsValues.get(metadata.getUuid()) + : new CustomizableFieldValueDto(); + field.setFieldValue(dto); + + field.setWidth(100, Unit.PERCENTAGE); + row.addComponent(field); + + float expandRatio = metadata.getUiLineWeight() != null ? metadata.getUiLineWeight() : 1.0f; + row.setExpandRatio(field, expandRatio); + + for (@SuppressWarnings("unchecked") + HasValue.ValueChangeListener listener : valueChangeListeners) { + ((CustomizableFieldInput) field).addValueChangeListener(listener); + } + + fieldComponents.put(metadata.getUuid(), field); + } + + addComponent(row); + } + + /** + * Gets a specific field component by metadata UUID. + * + * @param metadataUuid + * the UUID of the field metadata + * @return the field component, or null if not found + */ + public CustomizableFieldInput getFieldByMetadataUuid(String metadataUuid) { + return fieldComponents.get(metadataUuid); + } + + /** + * Gets all field value components. + * + * @return map of field components keyed by metadata UUID + */ + public Map> getAllFields() { + return new HashMap<>(fieldComponents); + } + + /** + * Gets all current field values as a map. + * Key is the metadata UUID, value is the CustomizableFieldValueDto. + * + * @return map of all current field values + */ + public Map getFieldsValues() { + Map result = new HashMap<>(); + for (Map.Entry> entry : fieldComponents.entrySet()) { + CustomizableFieldValueDto value = entry.getValue().getFieldValue(); + if (value != null && value.getValue() != null) { + result.put(entry.getKey(), value); + } + } + return result; + } + + /** + * Clears all field values. + */ + public void clearAllValues() { + for (CustomizableFieldInput field : fieldComponents.values()) { + field.clear(); + } + } + + /** + * Checks if any of the customizable fields have values. + * + * @return true if at least one field has a non-empty value + */ + public boolean hasAnyValues() { + return fieldComponents.values().stream().anyMatch(field -> !field.isEmpty()); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInput.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInput.java new file mode 100644 index 00000000000..c655603d438 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInput.java @@ -0,0 +1,226 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; + +import com.vaadin.data.Binder; +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.Component; +import com.vaadin.ui.CustomField; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; + +/** + * Abstract base for editable customizable field input components (Vaadin v8). + *

+ * Each concrete subclass is responsible for a specific {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType}. + * The base class handles metadata-driven configuration (caption, mandatory indicator, read-only state) + * and owns a {@link Binder} that keeps {@link CustomizableFieldValueDto#getValue() dto.value} in sync + * with the widget in both directions: + *

    + *
  • DTO → widget: {@link #setFieldValue(CustomizableFieldValueDto)} calls {@link Binder#setBean}, + * which reads the DTO and pushes the value into this {@code CustomField} via {@link #doSetValue}.
  • + *
  • widget → DTO: subclasses must call {@link #setValue(Object)} whenever the inner widget + * changes (typically via a value-change listener); the binder immediately writes the new value + * into the bean so {@link #getFieldValue()} never needs a manual flush.
  • + *
+ *

+ * Subclass contract: + *

    + *
  • {@link #buildInputComponent()} – called once by {@link #initContent()}; return the inner editable widget + * and wire a value-change listener that calls {@link #setValue(Object)}.
  • + *
  • {@link #applyValueToWidget(Object)} – propagates the value down to the inner widget; + * called by the final {@link #doSetValue(Object)} after storing the new value internally.
  • + *
  • {@link #configureBinding(Binder.BindingBuilder)} – optional; override to attach + * {@link com.vaadin.data.Validator}s to the binding before it is finalised.
  • + *
+ */ +public abstract class CustomizableFieldInput extends CustomField { + + private static final long serialVersionUID = 1L; + + private final CustomizableFieldMetadataDto fieldMetadata; + private final Binder binder; + /** + * Stores the current logical value of this field. + *

+ * {@link #getValue()} reads from here instead of from the inner widget. + * This ensures that {@link com.vaadin.ui.AbstractField#setValue(Object)} can + * correctly detect value changes: when the inner widget fires a change event + * and the listener calls {@link #setValue(Object)}, the old logical value is + * still present here, so the equality check succeeds and a + * {@link com.vaadin.data.HasValue.ValueChangeEvent} is fired to notify the binder. + * The field is updated inside {@link #doSetValue(Object)}, which is invoked by + * Vaadin only after the equality check has already passed. + */ + private transient T currentValue; + + /** + * Constructs the input and wires a {@link Binder} that keeps the DTO's {@code value} field + * continuously in sync with the widget state. + * + * @param metadata + * field metadata; must not be {@code null} + */ + protected CustomizableFieldInput(CustomizableFieldMetadataDto metadata) { + this.fieldMetadata = Objects.requireNonNull(metadata, "fieldMetadata must not be null"); + + binder = new Binder<>(CustomizableFieldValueDto.class); + Binder.BindingBuilder bindingBuilder = binder.forField(this); + if (metadata.isMandatory()) { + bindingBuilder = bindingBuilder.asRequired(); + } + bindingBuilder = configureBinding(bindingBuilder); + bindingBuilder.bind(getValueGetter(), getValueSetter()); + + applyMetadata(); + } + + /** + * Optional hook called during construction, just before the binder binding is finalised. + * Subclasses can override this to attach additional {@link com.vaadin.data.Validator}s + * (e.g. pattern validators for numeric types). + *

+ * Note: this method is called from the superclass constructor. Overrides must + * only reference compile-time constants or static members, never uninitialized instance fields. + * + * @param builder + * the in-progress binding builder; never {@code null} + * @return the (possibly augmented) builder; must not be {@code null} + */ + protected Binder.BindingBuilder configureBinding(Binder.BindingBuilder builder) { + return builder; + } + + /** + * Build and return the inner editable UI component. + * Called exactly once during {@link #initContent()}. + *

+ * Implementations should wire a value-change listener on the inner widget that + * calls {@link #setValue(Object)} so that the value stored in the field's internal + * state stays in sync. + * + * @return the inner component; never {@code null} + */ + protected abstract Component buildInputComponent(); + + @Override + protected final Component initContent() { + return buildInputComponent(); + } + + /** + * Returns the current logical value of this field. + *

+ * Reads from the internal {@link #currentValue} store rather than from the inner widget, + * so that {@link com.vaadin.ui.AbstractField#setValue(Object)} can detect changes correctly: + * when the inner widget fires a change and the listener calls {@link #setValue(Object)}, + * the old value is still present here, allowing the equality check to pass and a + * {@link com.vaadin.data.HasValue.ValueChangeEvent} to be fired to the binder. + */ + @Override + public T getValue() { + return currentValue; + } + + /** + * Called by Vaadin when a new value is set on this field (via {@link #setValue(Object)}). + * Stores the new value in {@link #currentValue}, then delegates widget propagation to + * {@link #applyValueToWidget(Object)}. + *

+ * Made {@code final} so that subclasses cannot accidentally bypass the value-storage step. + */ + @Override + protected final void doSetValue(T value) { + this.currentValue = value; + applyValueToWidget(value); + } + + /** + * Propagates {@code value} down to the inner UI widget. + * Called by {@link #doSetValue(Object)} after the new value has been stored. + *

+ * If the inner widget has not been created yet (component not yet rendered), + * implementations should stash the value as a pending value and apply it in + * {@link #buildInputComponent()}. + * + * @param value + * the new value to display; may be {@code null} + */ + protected abstract void applyValueToWidget(T value); + + /** + * Returns a {@link ValueProvider} that reads the typed value from a {@link CustomizableFieldValueDto}. + * Called once during construction; implementations must only reference compile-time constants. + */ + protected abstract ValueProvider getValueGetter(); + + /** + * Returns a {@link Setter} that writes the typed value back to a {@link CustomizableFieldValueDto}. + * Called once during construction; implementations must only reference compile-time constants. + */ + protected abstract Setter getValueSetter(); + + /** + * Sets the {@link CustomizableFieldValueDto} as the binder's active bean. + * The binder reads {@code dto.getValue()} and pushes it into the widget immediately. + * From this point on every user edit automatically writes back to the same DTO instance. + * Pass {@code null} to detach the current bean and clear the widget. + * + * @param fieldValue + * the value DTO, or {@code null} to clear + */ + public void setFieldValue(CustomizableFieldValueDto fieldValue) { + binder.setBean(fieldValue); + } + + /** + * Returns the currently bound {@link CustomizableFieldValueDto}. + * Because the binder writes user edits to the bean immediately, the returned DTO + * always reflects the current widget state — no manual flush required. + * + * @return the live bean, or {@code null} if none was set + */ + public CustomizableFieldValueDto getFieldValue() { + return binder.getBean(); + } + + /** + * Returns the metadata that configures this input. + * + * @return field metadata; never {@code null} + */ + public CustomizableFieldMetadataDto getFieldMetadata() { + return fieldMetadata; + } + + /** + * Applies caption, mandatory indicator and read-only state from metadata. + * Called once in the constructor, before the inner component is built. + */ + private void applyMetadata() { + if (StringUtils.isNotBlank(fieldMetadata.getName())) { + setCaption(fieldMetadata.getName()); + } + setRequiredIndicatorVisible(fieldMetadata.isMandatory()); + setReadOnly(fieldMetadata.isReadOnly()); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCheckbox.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCheckbox.java new file mode 100644 index 00000000000..522bc9ddb6c --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCheckbox.java @@ -0,0 +1,95 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.Component; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; + +/** + * Concrete {@link CustomizableFieldInput} for {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#CHECKBOX}. + *

+ * Renders a Vaadin v8 {@link CheckBox}. The checked state is serialised to/from the DTO's + * {@code value} field as {@code "true"} / {@code "false"} via + * {@link CustomizableFieldValueDto#getValueAsBoolean()} and + * {@link CustomizableFieldValueDto#setValueAsBoolean(Boolean)}. + * An absent/unrecognised raw value is treated as {@code false}. + */ +public class CustomizableFieldInputCheckbox extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + private CheckBox checkBox; + /** + * Holds a value that was pushed via {@link #doSetValue(Boolean)} before {@link #buildInputComponent()} + * had a chance to create the {@link CheckBox}. Applied to the check box on first render. + */ + private Boolean pendingValue; + + public CustomizableFieldInputCheckbox(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValueAsBoolean; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValueAsBoolean; + } + + public Class getType() { + return Boolean.class; + } + + @Override + protected Component buildInputComponent() { + checkBox = new CheckBox(); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + checkBox.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + checkBox.addValueChangeListener(e -> setValue(e.getValue())); + + return checkBox; + } + + /** + * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. + * Pushes the new state into the {@link CheckBox}, defaulting {@code null} to {@code false}. + * If the {@link CheckBox} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(Boolean value) { + boolean checked = Boolean.TRUE.equals(value); + if (checkBox != null) { + checkBox.setValue(checked); + } else { + pendingValue = checked; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCheckboxList.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCheckboxList.java new file mode 100644 index 00000000000..f30af1f11fe --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCheckboxList.java @@ -0,0 +1,117 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.CheckBoxGroup; +import com.vaadin.ui.Component; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldCustomProperties; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; + +/** + * Concrete {@link CustomizableFieldInput} for + * {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#CHECKBOX_LIST}. + *

+ * Renders a Vaadin v8 {@link CheckBoxGroup} populated with the string options defined in the + * field's {@link CustomizableFieldMetadataDto#getCustomProperties() customProperties} under + * {@link CustomizableFieldCustomProperties#getOptions()}. + *

+ * The selected set is serialised to/from the DTO's {@code value} field as a JSON array via + * {@link CustomizableFieldValueDto#getValueAsStringSet()} and + * {@link CustomizableFieldValueDto#setValueAsStringSet(Set)}. + */ +public class CustomizableFieldInputCheckboxList extends CustomizableFieldInput> { + + private static final long serialVersionUID = 1L; + + private CheckBoxGroup checkBoxGroup; + /** + * Holds a value that was pushed via {@link #doSetValue(Set)} before + * {@link #buildInputComponent()} had a chance to create the {@link CheckBoxGroup}. + * Applied to the group on first render. + */ + private Set pendingValue; + + public CustomizableFieldInputCheckboxList(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider> getValueGetter() { + return CustomizableFieldValueDto::getValueAsStringSet; + } + + @Override + protected Setter> getValueSetter() { + return CustomizableFieldValueDto::setValueAsStringSet; + } + + @SuppressWarnings("unchecked") + public Class> getType() { + return (Class>) (Class) Set.class; + } + + @Override + protected Component buildInputComponent() { + checkBoxGroup = new CheckBoxGroup<>(); + checkBoxGroup.setItems(resolveOptions()); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + checkBoxGroup.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + checkBoxGroup.addValueChangeListener(e -> setValue(e.getValue())); + + return checkBoxGroup; + } + + /** + * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. + * Pushes the value into the {@link CheckBoxGroup}; {@code null} clears the selection. + * If the {@link CheckBoxGroup} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(Set value) { + if (checkBoxGroup != null) { + checkBoxGroup.setValue(value != null ? value : Collections.emptySet()); + } else { + pendingValue = value; + } + } + + /** + * Reads the options list from {@link CustomizableFieldMetadataDto#getCustomProperties()}. + * Returns an empty list when no custom properties are set or the options are absent. + */ + private List resolveOptions() { + CustomizableFieldCustomProperties props = getFieldMetadata().getCustomProperties(); + if (props == null || props.getOptions() == null) { + return Collections.emptyList(); + } + return props.getOptions(); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCombobox.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCombobox.java new file mode 100644 index 00000000000..ad123b13a6e --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputCombobox.java @@ -0,0 +1,113 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import java.util.Collections; +import java.util.List; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Component; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldCustomProperties; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; + +/** + * Concrete {@link CustomizableFieldInput} for {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#COMBOBOX}. + *

+ * Renders a Vaadin v8 {@link ComboBox} populated with the string options defined in the field's + * {@link CustomizableFieldMetadataDto#getCustomProperties() customProperties} map under the key + * {@code "options"} (expected to be a {@link List} of {@link String}s). The selected value is + * stored directly as a {@link String} in {@link CustomizableFieldValueDto#getValue()}. + */ +public class CustomizableFieldInputCombobox extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + private ComboBox comboBox; + /** + * Holds a value that was pushed via {@link #doSetValue(String)} before + * {@link #buildInputComponent()} had a chance to create the {@link ComboBox}. + * Applied to the combo box on first render. + */ + private String pendingValue; + + public CustomizableFieldInputCombobox(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValue; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValue; + } + + public Class getType() { + return String.class; + } + + @Override + protected Component buildInputComponent() { + comboBox = new ComboBox<>(); + comboBox.setWidth(100, Unit.PERCENTAGE); + comboBox.setItems(resolveOptions()); + comboBox.setEmptySelectionAllowed(true); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + comboBox.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + comboBox.addValueChangeListener(e -> setValue(e.getValue())); + + return comboBox; + } + + /** + * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. + * Pushes the value into the {@link ComboBox}; {@code null} clears the selection. + * If the {@link ComboBox} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(String value) { + if (comboBox != null) { + comboBox.setValue(value); + } else { + pendingValue = value; + } + } + + /** + * Reads the options list from {@link CustomizableFieldMetadataDto#getCustomProperties()}. + * Returns an empty list when no custom properties are set or the options are absent. + */ + private List resolveOptions() { + CustomizableFieldCustomProperties props = getFieldMetadata().getCustomProperties(); + if (props == null || props.getOptions() == null) { + return Collections.emptyList(); + } + return props.getOptions(); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDate.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDate.java new file mode 100644 index 00000000000..013d74e4237 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDate.java @@ -0,0 +1,104 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import java.time.LocalDate; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.Component; +import com.vaadin.ui.DateField; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.utils.DateFormatHelper; + +/** + * Concrete {@link CustomizableFieldInput} for {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#DATE}. + *

+ * Renders a Vaadin 8 {@link DateField} (date-only picker, value type {@link LocalDate}). + * The value is stored internally as an ISO date string ({@code yyyy-MM-dd}) consistent with + * the {@link String} contract of the base class. The display format follows the locale + * configured in SORMAS via {@link DateFormatHelper#getDateFormatPattern()}. + *

+ * Value round-trip: + *

    + *
  • widget → string: {@link LocalDate#toString()} ({@code yyyy-MM-dd})
  • + *
  • string → widget: {@link LocalDate#parse(CharSequence)}
  • + *
+ */ +public class CustomizableFieldInputDate extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + private DateField dateField; + /** + * Holds a value that was pushed via {@link #doSetValue(LocalDate)} before + * {@link #buildInputComponent()} had a chance to create the {@link DateField}. + * Applied to the date field on first render. + */ + private LocalDate pendingValue; + + public CustomizableFieldInputDate(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValueAsDate; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValueAsDate; + } + + public Class getType() { + return LocalDate.class; + } + + @Override + protected Component buildInputComponent() { + dateField = new DateField(); + dateField.setWidth(100, Unit.PERCENTAGE); + dateField.setDateFormat(DateFormatHelper.getDateFormatPattern()); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + dateField.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + dateField.addValueChangeListener(e -> setValue(e.getValue())); + + return dateField; + } + + /** + * Propagates {@code value} into the {@link DateField}; {@code null} clears it. + * If the {@link DateField} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(LocalDate value) { + if (dateField != null) { + dateField.setValue(value); + } else { + pendingValue = value; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDateTime.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDateTime.java new file mode 100644 index 00000000000..674581df0ed --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDateTime.java @@ -0,0 +1,106 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import java.time.LocalDateTime; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.Component; +import com.vaadin.ui.DateTimeField; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.utils.DateHelper; + +/** + * Concrete {@link CustomizableFieldInput} for {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#DATE_TIME}. + *

+ * Renders a Vaadin 8 {@link DateTimeField} (date + time picker, value type {@link LocalDateTime}). + * The value is stored internally as an ISO-8601 date-time string ({@code yyyy-MM-ddTHH:mm}) + * consistent with the {@link String} contract of the base class. The display format follows + * the locale configured in SORMAS via {@link DateHelper#getLocalDateTimeFormat}. + *

+ * Value round-trip: + *

    + *
  • widget → string: {@link LocalDateTime} truncated to minutes, formatted as + * {@code yyyy-MM-ddTHH:mm} via {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME}
  • + *
  • string → widget: {@link LocalDateTime#parse(CharSequence)}
  • + *
+ */ +public class CustomizableFieldInputDateTime extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + private DateTimeField dateTimeField; + /** + * Holds a value that was pushed via {@link #doSetValue(LocalDateTime)} before + * {@link #buildInputComponent()} had a chance to create the {@link DateTimeField}. + * Applied to the date-time field on first render. + */ + private LocalDateTime pendingValue; + + public CustomizableFieldInputDateTime(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValueAsDateTime; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValueAsDateTime; + } + + public Class getType() { + return LocalDateTime.class; + } + + @Override + protected Component buildInputComponent() { + dateTimeField = new DateTimeField(); + dateTimeField.setWidth(100, Unit.PERCENTAGE); + dateTimeField.setDateFormat(DateHelper.getLocalDateTimeFormat(I18nProperties.getUserLanguage()).toPattern()); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + dateTimeField.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + dateTimeField.addValueChangeListener(e -> setValue(e.getValue())); + + return dateTimeField; + } + + /** + * Propagates {@code value} into the {@link DateTimeField}; {@code null} clears it. + * If the {@link DateTimeField} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(LocalDateTime value) { + if (dateTimeField != null) { + dateTimeField.setValue(value); + } else { + pendingValue = value; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDecimal.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDecimal.java new file mode 100644 index 00000000000..994edb7290a --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputDecimal.java @@ -0,0 +1,110 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import com.vaadin.data.Binder; +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.Component; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Validations; + +/** + * Concrete {@link CustomizableFieldInput} for {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#DECIMAL}. + *

+ * Renders a Vaadin v8 {@link TextField} restricted to decimal-number input via a + * binder validator. The value is stored as a {@link String} consistent with the + * base class contract. User edits are propagated via a value-change listener; + * programmatic changes pushed through {@link #setValue(Object)} reach the widget via + * {@link #doSetValue(String)}. + */ +public class CustomizableFieldInputDecimal extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + /** Matches an optional leading minus, one or more digits, and an optional decimal part. */ + private static final String DECIMAL_PATTERN = "-?\\d*([.,]\\d*)?"; + + private TextField textField; + /** + * Holds a value that was pushed via {@link #doSetValue(String)} before {@link #buildInputComponent()} + * had a chance to create the {@link TextField}. Applied to the text field on first render. + */ + private String pendingValue; + + public CustomizableFieldInputDecimal(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValue; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValue; + } + + public Class getType() { + return String.class; + } + + @Override + protected Binder.BindingBuilder configureBinding( + Binder.BindingBuilder builder) { + return builder.withValidator( + value -> value == null || value.isEmpty() || value.matches(DECIMAL_PATTERN), + I18nProperties.getValidationError(Validations.onlyDecimalNumbersAllowed, getFieldMetadata().getName())); + } + + @Override + protected Component buildInputComponent() { + textField = new TextField(); + textField.setWidth(100, Unit.PERCENTAGE); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + textField.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + textField.addValueChangeListener(e -> setValue(e.getValue())); + + return textField; + } + + /** + * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. + * Pushes the new value into the {@link TextField}, converting {@code null} to an + * empty string because {@code TextField.setValue(null)} throws {@link NullPointerException}. + * If the {@link TextField} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(String value) { + if (textField != null) { + textField.setValue(value != null ? value : ""); + } else { + pendingValue = value; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputFactory.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputFactory.java new file mode 100644 index 00000000000..a00a47ea78c --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputFactory.java @@ -0,0 +1,84 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldType; + +/** + * Factory for creating {@link CustomizableFieldInput} instances based on + * {@link CustomizableFieldMetadataDto#getFieldType()}. + *

+ * To add support for a new type, add the corresponding {@link CustomizableFieldType} case + * and return the appropriate {@link CustomizableFieldInput} subclass. + */ +public final class CustomizableFieldInputFactory { + + private CustomizableFieldInputFactory() { + // utility class + } + + /** + * Creates the appropriate {@link CustomizableFieldInput} for the given metadata. + * + * @param metadata + * field metadata; must not be {@code null} + * @return a new input component wired to the given metadata + * @throws UnsupportedOperationException + * when the {@link CustomizableFieldType} has no implementation yet + */ + public static CustomizableFieldInput create(CustomizableFieldMetadataDto metadata) { + CustomizableFieldType type = metadata.getFieldType(); + + switch (type) { + case TEXT: + return new CustomizableFieldInputText(metadata); + + case TEXTAREA: + return new CustomizableFieldInputTextArea(metadata); + + case NUMBER: + return new CustomizableFieldInputNumber(metadata); + + case DECIMAL: + return new CustomizableFieldInputDecimal(metadata); + + case DATE: + return new CustomizableFieldInputDate(metadata); + + case DATE_TIME: + return new CustomizableFieldInputDateTime(metadata); + + case COMBOBOX: + return new CustomizableFieldInputCombobox(metadata); + + case CHECKBOX: + return new CustomizableFieldInputCheckbox(metadata); + + case YES_NO_UNKNOWN: + return new CustomizableFieldInputYesNoUnknown(metadata); + + case CHECKBOX_LIST: + return new CustomizableFieldInputCheckboxList(metadata); + + case RADIO_BUTTON_LIST: + return new CustomizableFieldInputRadioButtonList(metadata); + + default: + throw new UnsupportedOperationException("No CustomizableFieldInput implementation for field type: " + type); + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputNumber.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputNumber.java new file mode 100644 index 00000000000..b4b20a310ac --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputNumber.java @@ -0,0 +1,110 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import com.vaadin.data.Binder; +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.Component; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Validations; + +/** + * Concrete {@link CustomizableFieldInput} for {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#NUMBER}. + *

+ * Renders a Vaadin v8 {@link TextField} restricted to whole-number input via a + * binder validator. The value is stored as a {@link String} consistent with the + * base class contract. User edits are propagated via a value-change listener; + * programmatic changes pushed through {@link #setValue(Object)} reach the widget via + * {@link #doSetValue(String)}. + */ +public class CustomizableFieldInputNumber extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + /** Matches an optional leading minus followed by one or more digits (or empty string). */ + private static final String INTEGER_PATTERN = "-?\\d*"; + + private TextField textField; + /** + * Holds a value that was pushed via {@link #doSetValue(String)} before {@link #buildInputComponent()} + * had a chance to create the {@link TextField}. Applied to the text field on first render. + */ + private String pendingValue; + + public CustomizableFieldInputNumber(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValue; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValue; + } + + public Class getType() { + return String.class; + } + + @Override + protected Binder.BindingBuilder configureBinding( + Binder.BindingBuilder builder) { + return builder.withValidator( + value -> value == null || value.isEmpty() || value.matches(INTEGER_PATTERN), + I18nProperties.getValidationError(Validations.onlyIntegerNumbersAllowed, getFieldMetadata().getName())); + } + + @Override + protected Component buildInputComponent() { + textField = new TextField(); + textField.setWidth(100, Unit.PERCENTAGE); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + textField.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + textField.addValueChangeListener(e -> setValue(e.getValue())); + + return textField; + } + + /** + * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. + * Pushes the new value into the {@link TextField}, converting {@code null} to an + * empty string because {@code TextField.setValue(null)} throws {@link NullPointerException}. + * If the {@link TextField} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(String value) { + if (textField != null) { + textField.setValue(value != null ? value : ""); + } else { + pendingValue = value; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputRadioButtonList.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputRadioButtonList.java new file mode 100644 index 00000000000..9c58fff06ee --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputRadioButtonList.java @@ -0,0 +1,114 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import java.util.Collections; +import java.util.List; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.Component; +import com.vaadin.ui.RadioButtonGroup; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldCustomProperties; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; + +/** + * Concrete {@link CustomizableFieldInput} for + * {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#RADIO_BUTTON_LIST}. + *

+ * Renders a Vaadin v8 {@link RadioButtonGroup} populated with the string options defined in the + * field's {@link CustomizableFieldMetadataDto#getCustomProperties() customProperties} under + * {@link CustomizableFieldCustomProperties#getOptions()}. + *

+ * The selected value is stored directly as a {@link String} in + * {@link CustomizableFieldValueDto#getValue()}. + */ +public class CustomizableFieldInputRadioButtonList extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + private RadioButtonGroup radioButtonGroup; + /** + * Holds a value that was pushed via {@link #doSetValue(String)} before + * {@link #buildInputComponent()} had a chance to create the {@link RadioButtonGroup}. + * Applied to the group on first render. + */ + private String pendingValue; + + public CustomizableFieldInputRadioButtonList(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValue; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValue; + } + + public Class getType() { + return String.class; + } + + @Override + protected Component buildInputComponent() { + radioButtonGroup = new RadioButtonGroup<>(); + radioButtonGroup.setItems(resolveOptions()); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + radioButtonGroup.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + radioButtonGroup.addValueChangeListener(e -> setValue(e.getValue())); + + return radioButtonGroup; + } + + /** + * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. + * Pushes the value into the {@link RadioButtonGroup}; {@code null} clears the selection. + * If the {@link RadioButtonGroup} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(String value) { + if (radioButtonGroup != null) { + radioButtonGroup.setValue(value); + } else { + pendingValue = value; + } + } + + /** + * Reads the options list from {@link CustomizableFieldMetadataDto#getCustomProperties()}. + * Returns an empty list when no custom properties are set or the options are absent. + */ + private List resolveOptions() { + CustomizableFieldCustomProperties props = getFieldMetadata().getCustomProperties(); + if (props == null || props.getOptions() == null) { + return Collections.emptyList(); + } + return props.getOptions(); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputText.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputText.java new file mode 100644 index 00000000000..a86dbbf7b58 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputText.java @@ -0,0 +1,95 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.Component; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; + +/** + * Concrete {@link CustomizableFieldInput} for {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#TEXT} + * (and, by extension, any single-line free-text field type). + *

+ * Renders a Vaadin v8 {@link TextField}. User edits are propagated to the parent field's + * internal state via a value-change listener; programmatic changes pushed through + * {@link #setValue(Object)} are reflected in the {@link TextField} via {@link #doSetValue(String)}. + */ +public class CustomizableFieldInputText extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + private TextField textField; + /** + * Holds a value that was pushed via {@link #doSetValue(String)} before {@link #buildInputComponent()} + * had a chance to create the {@link TextField}. Applied to the text field on first render. + */ + private String pendingValue; + + public CustomizableFieldInputText(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValue; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValue; + } + + public Class getType() { + return String.class; + } + + @Override + protected Component buildInputComponent() { + textField = new TextField(); + textField.setWidth(100, Unit.PERCENTAGE); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + textField.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + textField.addValueChangeListener(e -> setValue(e.getValue())); + + return textField; + } + + /** + * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. + * Pushes the new value into the {@link TextField}, converting {@code null} to an + * empty string because {@code TextField.setValue(null)} throws {@link NullPointerException}. + * If the {@link TextField} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(String value) { + if (textField != null) { + textField.setValue(value != null ? value : ""); + } else { + pendingValue = value; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputTextArea.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputTextArea.java new file mode 100644 index 00000000000..dcc3dc0259e --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputTextArea.java @@ -0,0 +1,94 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.Component; +import com.vaadin.ui.TextArea; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; + +/** + * Concrete {@link CustomizableFieldInput} for {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#TEXTAREA}. + *

+ * Renders a Vaadin v8 {@link TextArea}. User edits are propagated to the parent field's + * internal state via a value-change listener; programmatic changes pushed through + * {@link #setValue(Object)} are reflected in the {@link TextArea} via {@link #doSetValue(String)}. + */ +public class CustomizableFieldInputTextArea extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + private TextArea textArea; + /** + * Holds a value that was pushed via {@link #doSetValue(String)} before {@link #buildInputComponent()} + * had a chance to create the {@link TextArea}. Applied to the text area on first render. + */ + private String pendingValue; + + public CustomizableFieldInputTextArea(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValue; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValue; + } + + public Class getType() { + return String.class; + } + + @Override + protected Component buildInputComponent() { + textArea = new TextArea(); + textArea.setWidth(100, Unit.PERCENTAGE); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + textArea.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + textArea.addValueChangeListener(e -> setValue(e.getValue())); + + return textArea; + } + + /** + * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. + * Pushes the new value into the {@link TextArea}, converting {@code null} to an + * empty string because {@code TextArea.setValue(null)} throws {@link NullPointerException}. + * If the {@link TextArea} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(String value) { + if (textArea != null) { + textArea.setValue(value != null ? value : ""); + } else { + pendingValue = value; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputYesNoUnknown.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputYesNoUnknown.java new file mode 100644 index 00000000000..1156f9f6220 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputYesNoUnknown.java @@ -0,0 +1,103 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.utils.components.customizablefield; + +import java.util.Arrays; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Component; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.utils.YesNoUnknown; + +/** + * Concrete {@link CustomizableFieldInput} for + * {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#YES_NO_UNKNOWN}. + *

+ * Renders a Vaadin v8 {@link ComboBox} populated with all {@link YesNoUnknown} constants. + * The selected value is serialised to/from the DTO's {@code value} field as the enum name + * ({@code "YES"}, {@code "NO"}, {@code "UNKNOWN"}) via + * {@link CustomizableFieldValueDto#getValueAsYesNoUnknown()} and + * {@link CustomizableFieldValueDto#setValueAsYesNoUnknown(YesNoUnknown)}. + */ +public class CustomizableFieldInputYesNoUnknown extends CustomizableFieldInput { + + private static final long serialVersionUID = 1L; + + private ComboBox comboBox; + /** + * Holds a value that was pushed via {@link #doSetValue(YesNoUnknown)} before + * {@link #buildInputComponent()} had a chance to create the {@link ComboBox}. + * Applied to the combo box on first render. + */ + private YesNoUnknown pendingValue; + + public CustomizableFieldInputYesNoUnknown(CustomizableFieldMetadataDto metadata) { + super(metadata); + } + + @Override + protected ValueProvider getValueGetter() { + return CustomizableFieldValueDto::getValueAsYesNoUnknown; + } + + @Override + protected Setter getValueSetter() { + return CustomizableFieldValueDto::setValueAsYesNoUnknown; + } + + public Class getType() { + return YesNoUnknown.class; + } + + @Override + protected Component buildInputComponent() { + comboBox = new ComboBox<>(); + comboBox.setWidth(100, Unit.PERCENTAGE); + comboBox.setItems(Arrays.asList(YesNoUnknown.values())); + comboBox.setItemCaptionGenerator(YesNoUnknown::toString); + comboBox.setEmptySelectionAllowed(true); + + // Apply any value that arrived before the component was first rendered. + if (pendingValue != null) { + comboBox.setValue(pendingValue); + pendingValue = null; + } + + // Propagate user edits back to the field's internal value state. + comboBox.addValueChangeListener(e -> setValue(e.getValue())); + + return comboBox; + } + + /** + * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. + * Pushes the value into the {@link ComboBox}; {@code null} clears the selection. + * If the {@link ComboBox} has not been created yet (component not yet rendered), + * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + */ + @Override + protected void applyValueToWidget(YesNoUnknown value) { + if (comboBox != null) { + comboBox.setValue(value); + } else { + pendingValue = value; + } + } +} diff --git a/sormas-ui/src/test/resources/META-INF/persistence.xml b/sormas-ui/src/test/resources/META-INF/persistence.xml index 448d702fd4e..3a4842187be 100644 --- a/sormas-ui/src/test/resources/META-INF/persistence.xml +++ b/sormas-ui/src/test/resources/META-INF/persistence.xml @@ -74,6 +74,8 @@ de.symeda.sormas.backend.infrastructure.subcontinent.Subcontinent de.symeda.sormas.backend.sormastosormas.share.incoming.SormasToSormasShareRequest de.symeda.sormas.backend.customizableenum.CustomizableEnumValue + de.symeda.sormas.backend.customizablefield.CustomizableFieldMetadata + de.symeda.sormas.backend.customizablefield.CustomizableFieldValue de.symeda.sormas.backend.immunization.entity.BaseImmunization de.symeda.sormas.backend.immunization.entity.Immunization de.symeda.sormas.backend.immunization.entity.DirectoryImmunization From f993c23081f9617d4ae3b670d8b1fb2b608520d0 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 02/55] 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 | 102 +++++++++- 14 files changed, 1133 insertions(+), 1 deletion(-) 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 44a893eaa04..4affe0d26ee 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15367,6 +15367,106 @@ 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'); + -- #13828 - Customizable Fields CREATE TABLE IF NOT EXISTS customizablefieldmetadata ( @@ -15505,6 +15605,6 @@ CREATE TRIGGER delete_history_trigger_customizablefieldvalue ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; -INSERT INTO schema_version (version_number, comment) VALUES (614, '#13828 - Add history tables for customizable fields'); +INSERT INTO schema_version (version_number, comment) VALUES (615, '#13828 - Add history tables for customizable fields'); -- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** From 7d13524f23d7c2eaf2a0762d8cac9bae04a11784 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 10:53:20 +0200 Subject: [PATCH 03/55] Updated action versions --- .github/workflows/ci.yml | 16 ++++++++-------- .github/workflows/dependency-review.yml | 4 ++-- .github/workflows/github_pages.yml | 2 +- .github/workflows/gradle_wrapper_validation.yml | 4 ++-- .github/workflows/linter.yml | 4 ++-- .github/workflows/openapi_canary.yml | 4 ++-- .github/workflows/sormas_app_ci.yml | 16 ++++++++-------- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4047c4170e..2fdd4654196 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: # The token is only needed for privileged actions from within the repo, so no need # to make it available on 3rd party PRs if: ${{ fromJSON(env.PRIVILEGED_RUN) }} - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: token: ${{ secrets.SORMAS_VITAGROUP_CI_TOKEN }} @@ -40,7 +40,7 @@ jobs: # The token is only needed for privileged actions from within the # repo, so no need to make it available on 3rd party PRs if: ${{ !fromJSON(env.PRIVILEGED_RUN) }} - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Initialize CodeQL uses: github/codeql-action/init@v2 @@ -48,7 +48,7 @@ jobs: languages: ${{ env.CODEQL_LANGUAGES }} - name: Set up JDK ${{ env.JAVA }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: ${{ env.JAVA }} distribution: 'zulu' @@ -57,7 +57,7 @@ jobs: # Check if PR results from the repository: if yes, it is safe to cache dependencies. # This is to keep us safe from cache poisoning through 3rd party PRs. if: ${{ fromJSON(env.PRIVILEGED_RUN) }} - uses: actions/cache@v3 + uses: actions/cache@v5 with: path: ~/.m2 key: ${{ runner.os }}-java-${{ env.JAVA }}-m2-${{ hashFiles('**/pom.xml') }} @@ -67,7 +67,7 @@ jobs: # Check if PR results from the repository: if yes, it is safe to cache dependencies. # This is to keep us safe from cache poisoning through 3rd party PRs. if: ${{ fromJSON(env.PRIVILEGED_RUN) }} - uses: actions/cache@v3 + uses: actions/cache@v5 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar @@ -94,10 +94,10 @@ jobs: }) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v4 - name: Run Trivy vulnerability scanner in repo mode - uses: aquasecurity/trivy-action@0.11.2 + uses: aquasecurity/trivy-action@0.35.0 with: scan-type: 'fs' ignore-unfixed: true @@ -106,7 +106,7 @@ jobs: scanners: 'vuln,secret,config' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: 'trivy-results.sarif' # needed as codeQL also performs an upload, and they clash otherwise diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index da99d0c5488..ae5ae59895a 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -9,6 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: 'Dependency Review' - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index 35dad6b8bc9..fd1b23bdb32 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout development - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Copy files run: | cp README.md docs/index.md diff --git a/.github/workflows/gradle_wrapper_validation.yml b/.github/workflows/gradle_wrapper_validation.yml index ded86c79999..e567b8ef956 100644 --- a/.github/workflows/gradle_wrapper_validation.yml +++ b/.github/workflows/gradle_wrapper_validation.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository (without token) - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/actions/wrapper-validation@v3 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 1a7e140796f..f753a124669 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -37,7 +37,7 @@ jobs: # Checkout the code base # ########################## - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: # Full git history is needed to get a proper list of changed files within `super-linter` fetch-depth: 0 @@ -46,7 +46,7 @@ jobs: # Run Linter against code base # ################################ - name: Lint Code Base - uses: github/super-linter/slim@v5 + uses: github/super-linter/slim@v6 env: DEFAULT_BRANCH: development GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/openapi_canary.yml b/.github/workflows/openapi_canary.yml index 854ba86ecac..6989bdc4b2e 100644 --- a/.github/workflows/openapi_canary.yml +++ b/.github/workflows/openapi_canary.yml @@ -14,12 +14,12 @@ jobs: steps: - name: Checkout development branch - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: path: head - name: Checkout master branch - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: ref: master path: base diff --git a/.github/workflows/sormas_app_ci.yml b/.github/workflows/sormas_app_ci.yml index 27f7294cb0e..1026de9c77d 100644 --- a/.github/workflows/sormas_app_ci.yml +++ b/.github/workflows/sormas_app_ci.yml @@ -41,7 +41,7 @@ jobs: # The token is only needed for privileged actions from within the repo, so no need # to make it available on 3rd party PRs if: ${{ fromJSON(env.PRIVILEGED_RUN) }} - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: token: ${{ secrets.SORMAS_VITAGROUP_CI_TOKEN }} @@ -50,10 +50,10 @@ jobs: # The token is only needed for privileged actions from within the repo, so no need # to make it available on 3rd party PRs if: ${{ !fromJSON(env.PRIVILEGED_RUN) }} - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Set up JDK ${{ env.JAVA }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: ${{ env.JAVA }} distribution: 'zulu' @@ -62,7 +62,7 @@ jobs: # Check if PR results from the repository: if yes, it is safe to cache dependencies. # This is to keep us safe from cache poisoning through 3rd party PRs. if: ${{ fromJSON(env.PRIVILEGED_RUN) }} - uses: actions/cache@v3 + uses: actions/cache@v5 with: path: ~/.m2 key: ${{ runner.os }}-java-${{ env.JAVA }}-m2-${{ hashFiles('**/pom.xml') }} @@ -73,7 +73,7 @@ jobs: run: mvn install -pl :sormas-api -am -DskipTests=true - name: Cache Gradle packages - uses: actions/cache@v3 + uses: actions/cache@v5 # Check if PR results from the repository: if yes, it is safe to cache dependencies. # This is to keep us safe from cache poisoning through 3rd party PRs. if: ${{ fromJSON(env.PRIVILEGED_RUN) }} @@ -93,7 +93,7 @@ jobs: # Check if PR results from the repository: if yes, it is safe to cache dependencies. # This is to keep us safe from cache poisoning through 3rd party PRs. if: ${{ fromJSON(env.PRIVILEGED_RUN) }} - uses: actions/cache@v3 + uses: actions/cache@v5 id: avd-cache with: path: | @@ -122,12 +122,12 @@ jobs: script: ./gradlew connectedAndroidTest - name: mobsfscan - uses: MobSF/mobsfscan@0.2.0 + uses: MobSF/mobsfscan@main with: args: '. --sarif --output mobsf-results.sarif || true' - name: Upload mobsfscan report - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: 'mobsf-results.sarif' # needed as codeQL also performs an upload, and they clash otherwise From 104484366b2089bfbf3aea30b323155446cc4979 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 10:57:25 +0200 Subject: [PATCH 04/55] Added manual trigger --- .github/workflows/openapi_canary.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/openapi_canary.yml b/.github/workflows/openapi_canary.yml index 6989bdc4b2e..7eda999879a 100644 --- a/.github/workflows/openapi_canary.yml +++ b/.github/workflows/openapi_canary.yml @@ -2,6 +2,12 @@ name: OpenAPI Canary on: + workflow_dispatch: + inputs: + reason: + description: 'Reason for manual run' + required: false + default: 'Testing on specific branch' schedule: # 2.30 UTC - cron: '30 2 * * *' From da91663b99d6d89f7532fc6206e744e0b5d738d4 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 11:00:37 +0200 Subject: [PATCH 05/55] Added manual trigger --- .github/workflows/sormas_app_ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/sormas_app_ci.yml b/.github/workflows/sormas_app_ci.yml index 1026de9c77d..535acb7b55c 100644 --- a/.github/workflows/sormas_app_ci.yml +++ b/.github/workflows/sormas_app_ci.yml @@ -12,6 +12,12 @@ env: || github.event.pull_request.head.repo.full_name == github.repository }} on: + workflow_dispatch: + inputs: + reason: + description: 'Reason for manual run' + required: false + default: 'Testing on specific branch' push: branches: [ development, master, hotfix* ] paths: From 5a776bbea76413a3be9603381afaac16d184de7d Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 11:09:13 +0200 Subject: [PATCH 06/55] Fixed sormas app barcodescanner version --- sormas-app/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sormas-app/app/build.gradle b/sormas-app/app/build.gradle index 1a8455112be..c79e9ab11b5 100644 --- a/sormas-app/app/build.gradle +++ b/sormas-app/app/build.gradle @@ -134,7 +134,7 @@ dependencies { implementation 'androidx.paging:paging-runtime:3.1.1' implementation 'androidx.work:work-runtime-ktx:2.8.1' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' - implementation 'me.dm7.barcodescanner:zxing:1.9.13' + implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'io.crowdcode.sormas.lbds:lbds-android-messaging:1.4.8' implementation 'org.slf4j:slf4j-api:2.0.7' // Align versions of all Kotlin components From 33b16398f4a445db7a2480690e210579e8ce9b94 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 11:18:26 +0200 Subject: [PATCH 07/55] Removed manual trigger, corrected gradle validate version --- .github/workflows/gradle_wrapper_validation.yml | 2 +- .github/workflows/sormas_app_ci.yml | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/gradle_wrapper_validation.yml b/.github/workflows/gradle_wrapper_validation.yml index e567b8ef956..a2854a6d914 100644 --- a/.github/workflows/gradle_wrapper_validation.yml +++ b/.github/workflows/gradle_wrapper_validation.yml @@ -14,4 +14,4 @@ jobs: uses: actions/checkout@v6 - name: Validate gradle wrapper - uses: gradle/actions/wrapper-validation@v3 + uses: gradle/actions/wrapper-validation@v5 diff --git a/.github/workflows/sormas_app_ci.yml b/.github/workflows/sormas_app_ci.yml index 535acb7b55c..1026de9c77d 100644 --- a/.github/workflows/sormas_app_ci.yml +++ b/.github/workflows/sormas_app_ci.yml @@ -12,12 +12,6 @@ env: || github.event.pull_request.head.repo.full_name == github.repository }} on: - workflow_dispatch: - inputs: - reason: - description: 'Reason for manual run' - required: false - default: 'Testing on specific branch' push: branches: [ development, master, hotfix* ] paths: From 03ef5093a0c1283f0435d2f2072fd9f4e074d362 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 12:37:58 +0200 Subject: [PATCH 08/55] Disabled linter, fixed maven ci --- .github/workflows/ci.yml | 38 ++++-------------------------------- .github/workflows/linter.yml | 5 +++-- 2 files changed, 7 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fdd4654196..a0b014d5403 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: if: ${{ fromJSON(env.PRIVILEGED_RUN) }} uses: actions/checkout@v6 with: - token: ${{ secrets.SORMAS_VITAGROUP_CI_TOKEN }} + token: ${{ secrets.MAVEN_ACTIONS_TOKEN }} - name: Checkout repository (without token) # Check if PR results from a fork: if yes, we cannot access the token. @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@v6 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v4 with: languages: ${{ env.CODEQL_LANGUAGES }} @@ -63,36 +63,6 @@ jobs: key: ${{ runner.os }}-java-${{ env.JAVA }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-java-${{ env.JAVA }}-m2 - - name: Cache SonarCloud packages - # Check if PR results from the repository: if yes, it is safe to cache dependencies. - # This is to keep us safe from cache poisoning through 3rd party PRs. - if: ${{ fromJSON(env.PRIVILEGED_RUN) }} - uses: actions/cache@v5 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Run mvn verify and sonar analysis - # FIXME(@JonasCir) see https://github.com/sormas-foundation/SORMAS-Project/issues/3730#issuecomment-745165678 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - working-directory: ./sormas-base - run: mvn -B -ntp verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=SORMAS-Project - - - name: Comment with SonarCloud analysis - uses: actions/github-script@v6 - if: github.event_name == 'pull_request' - with: - github-token: ${{ secrets.SORMAS_VITAGROUP_CI_TOKEN }} - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `SonarCloud analysis: https://sonarcloud.io/dashboard?id=SORMAS-Project&pullRequest=${{ github.event.pull_request.number }}` - }) - - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 @@ -119,8 +89,8 @@ jobs: && hashFiles('sormas-rest/target/swagger.yaml') != hashFiles('sormas-rest/swagger.yaml') # https://stackoverflow.com/questions/59604922/authorize-bash-to-access-github-protected-branch run: | - git config --global user.name "sormas-vitagroup" - git config --global user.email "support.sormas@helpdesk.symeda.de" + git config --global user.name "sormas-robot" + git config --global user.email "accounts@sormas.org" mkdir /tmp/openapi cp sormas-rest/target/swagger.* /tmp/openapi diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index f753a124669..5844655b7bf 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -16,8 +16,9 @@ name: Lint Code Base ############################# on: workflow_dispatch: - pull_request: - branches: [development, hotfix*] +# Disabled auto for now until linting errors are fixed +# pull_request: +# branches: [development, hotfix*] ############### # Set the Job # From 58ac29bbdec43f16bbf75abf3ec1aec92f589d2c Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 12:43:25 +0200 Subject: [PATCH 09/55] Disabled codeql --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0b014d5403..25254b0d15d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,8 +63,8 @@ jobs: key: ${{ runner.os }}-java-${{ env.JAVA }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-java-${{ env.JAVA }}-m2 - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 +# - name: Perform CodeQL Analysis +# uses: github/codeql-action/analyze@v4 - name: Run Trivy vulnerability scanner in repo mode uses: aquasecurity/trivy-action@0.35.0 From c34ef2173749a616966345e071f922cf58021f15 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 12:55:57 +0200 Subject: [PATCH 10/55] Disabled codeql --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25254b0d15d..244971b3d49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,10 +42,10 @@ jobs: if: ${{ !fromJSON(env.PRIVILEGED_RUN) }} uses: actions/checkout@v6 - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ env.CODEQL_LANGUAGES }} +# - name: Initialize CodeQL +# uses: github/codeql-action/init@v4 +# with: +# languages: ${{ env.CODEQL_LANGUAGES }} - name: Set up JDK ${{ env.JAVA }} uses: actions/setup-java@v5 From c5c71e352df384f0dace960d502fe8bb9ccc862b Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 12:59:07 +0200 Subject: [PATCH 11/55] Fixing codeql --- .github/workflows/ci.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 244971b3d49..83ddd648d3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,9 +7,8 @@ name: Java CI with Maven env: JAVA: 17 - PRIVILEGED_RUN: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/development') - || github.event.pull_request.head.repo.full_name == github.repository }} - CODEQL_LANGUAGES: 'java' # FIXME(@JonasCir) add 'javascript' + PRIVILEGED_RUN: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/development') || github.event.pull_request.head.repo.full_name == github.repository }} + CODEQL_LANGUAGES: 'java' on: push: branches: [ development, master, hotfix* ] @@ -42,10 +41,10 @@ jobs: if: ${{ !fromJSON(env.PRIVILEGED_RUN) }} uses: actions/checkout@v6 -# - name: Initialize CodeQL -# uses: github/codeql-action/init@v4 -# with: -# languages: ${{ env.CODEQL_LANGUAGES }} + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ env.CODEQL_LANGUAGES }} - name: Set up JDK ${{ env.JAVA }} uses: actions/setup-java@v5 @@ -63,8 +62,8 @@ jobs: key: ${{ runner.os }}-java-${{ env.JAVA }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-java-${{ env.JAVA }}-m2 -# - name: Perform CodeQL Analysis -# uses: github/codeql-action/analyze@v4 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 - name: Run Trivy vulnerability scanner in repo mode uses: aquasecurity/trivy-action@0.35.0 From b96fc533041db7c96b3bbd09041454d911e53a55 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 13:08:01 +0200 Subject: [PATCH 12/55] Re-added build step --- .github/workflows/ci.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83ddd648d3b..ab953e21d4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,12 @@ on: pull_request: branches: [ development, hotfix* ] workflow_dispatch: # run it manually from the GH Actions web console + inputs: + skip_tests: + description: 'Skip Maven tests during build' + required: false + default: true + type: boolean schedule: - cron: '35 1 * * 0' jobs: @@ -62,6 +68,15 @@ jobs: key: ${{ runner.os }}-java-${{ env.JAVA }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-java-${{ env.JAVA }}-m2 + - name: Build with Maven + working-directory: ./sormas-base + run: | + MAVEN_OPTS="-B -ntp clean install" + if [ "${{ inputs.skip_tests }}" = "true" ]; then + MAVEN_OPTS="$MAVEN_OPTS -DskipTests" + fi + mvn $MAVEN_OPTS + - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 @@ -84,8 +99,7 @@ jobs: - name: Commit openAPI spec to development # Privileged action needing a secret token. Since this only runs on development in our own repo # the token will be available through a privileged checkout. - if: github.event_name == 'push' && github.ref == 'refs/heads/development' - && hashFiles('sormas-rest/target/swagger.yaml') != hashFiles('sormas-rest/swagger.yaml') + if: github.event_name == 'push' && github.ref == 'refs/heads/development' && hashFiles('sormas-rest/target/swagger.yaml') != hashFiles('sormas-rest/swagger.yaml') # https://stackoverflow.com/questions/59604922/authorize-bash-to-access-github-protected-branch run: | git config --global user.name "sormas-robot" From 1a2d5e45a0dcb29d04b2625d42c0c03385fe83aa Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 13:19:07 +0200 Subject: [PATCH 13/55] Enabled maven tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab953e21d4a..f8299f611e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ on: skip_tests: description: 'Skip Maven tests during build' required: false - default: true + default: false type: boolean schedule: - cron: '35 1 * * 0' From 2e7923d3d2437fe34f532b746dca6f6332c0d0fc Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 13:20:58 +0200 Subject: [PATCH 14/55] Temporarily disabled open canary --- .github/workflows/openapi_canary.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/openapi_canary.yml b/.github/workflows/openapi_canary.yml index 7eda999879a..11406966c44 100644 --- a/.github/workflows/openapi_canary.yml +++ b/.github/workflows/openapi_canary.yml @@ -8,9 +8,10 @@ on: description: 'Reason for manual run' required: false default: 'Testing on specific branch' - schedule: +# Disabled until fixing the issue with the diff tool +# schedule: # 2.30 UTC - - cron: '30 2 * * *' +# - cron: '30 2 * * *' jobs: canary: From c7ab067d66153b06dc2d227fa533024a5a401b36 Mon Sep 17 00:00:00 2001 From: raulbob Date: Wed, 1 Apr 2026 13:36:33 +0200 Subject: [PATCH 15/55] Merge pull request #13893 from SORMAS-Foundation/task-update_github_actions Updated app ci token --- .github/workflows/sormas_app_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sormas_app_ci.yml b/.github/workflows/sormas_app_ci.yml index 1026de9c77d..22c5bbd70bd 100644 --- a/.github/workflows/sormas_app_ci.yml +++ b/.github/workflows/sormas_app_ci.yml @@ -43,7 +43,7 @@ jobs: if: ${{ fromJSON(env.PRIVILEGED_RUN) }} uses: actions/checkout@v6 with: - token: ${{ secrets.SORMAS_VITAGROUP_CI_TOKEN }} + token: ${{ secrets.MAVEN_ACTIONS_TOKEN }} - name: Checkout repository (without token) # Check if PR results from a fork: if yes, we cannot access the token. From 5db41c6e2e74de2ae10a5e304498dafd1deba51e Mon Sep 17 00:00:00 2001 From: Karnaiah Pesula Date: Wed, 25 Mar 2026 16:14:11 +0100 Subject: [PATCH 16/55] Summary: Malaria and Dengue Sample and Pathogentestform changes. Changes: https://github.com/SORMAS-Foundation/SORMAS-Project/issues/13801 https://github.com/SORMAS-Foundation/SORMAS-Project/issues/13814 --- .../clinicalcourse/HealthConditionsDto.java | 26 +++ .../disease/DiseaseConfigurationFacade.java | 2 + .../api/epidata/DiseaseTransmission.java | 36 ++++ .../symeda/sormas/api/epidata/EpiDataDto.java | 30 +++ .../EpipulseDiseaseExportEntryDto.java | 2 +- .../epipulse/EpipulseLaboratoryMapper.java | 2 +- .../labmessage/TestReportDto.java | 12 +- .../processing/ExternalMessageMapper.java | 13 +- .../de/symeda/sormas/api/i18n/Captions.java | 13 +- .../de/symeda/sormas/api/i18n/Strings.java | 2 + .../{GenoTypeResult.java => GenoType.java} | 2 +- .../sormas/api/sample/PathogenSpecie.java | 154 ++++++++++++- .../sormas/api/sample/PathogenTestDto.java | 96 +++++++-- .../sormas/api/sample/PathogenTestType.java | 92 ++++++-- .../sormas/api/sample/SampleListEntryDto.java | 28 +++ .../sormas/api/sample/SampleMaterial.java | 3 +- .../de/symeda/sormas/api/sample/Serotype.java | 63 ++++++ .../sormas/api/symptoms/SymptomsDto.java | 23 +- .../src/main/resources/captions.properties | 15 +- sormas-api/src/main/resources/enum.properties | 99 ++++++--- .../src/main/resources/strings.properties | 4 +- .../clinicalcourse/HealthConditions.java | 18 ++ .../HealthConditionsMapper.java | 5 +- .../DiseaseConfigurationFacadeEjb.java | 4 + .../strategy/MeaslesExportStrategy.java | 4 +- .../labmessage/TestReport.java | 12 +- .../labmessage/TestReportFacadeEjb.java | 4 +- .../sormas/backend/sample/PathogenTest.java | 89 ++++++-- .../backend/sample/PathogenTestFacadeEjb.java | 31 ++- .../src/main/resources/sql/sormas_schema.sql | 168 +++++++++++++++ .../symeda/sormas/ui/caze/CaseController.java | 33 ++- .../symeda/sormas/ui/caze/CaseDataForm.java | 5 +- .../ui/caze/CaseSymptomSideViewComponent.java | 54 +++-- .../clinicalcourse/HealthConditionsForm.java | 31 ++- .../symeda/sormas/ui/epidata/EpiDataForm.java | 10 +- .../sormas/ui/samples/PathogenTestForm.java | 203 ++++++++++-------- .../PathogenTestListEntry.java | 35 ++- .../samples/sampleLink/SampleListEntry.java | 22 ++ .../sormas/ui/symptoms/SymptomsForm.java | 5 +- 39 files changed, 1209 insertions(+), 241 deletions(-) create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/epidata/DiseaseTransmission.java rename sormas-api/src/main/java/de/symeda/sormas/api/sample/{GenoTypeResult.java => GenoType.java} (98%) create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/sample/Serotype.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/clinicalcourse/HealthConditionsDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/clinicalcourse/HealthConditionsDto.java index d36f68f53fb..d3f333acf1d 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/clinicalcourse/HealthConditionsDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/clinicalcourse/HealthConditionsDto.java @@ -58,6 +58,8 @@ public class HealthConditionsDto extends PseudonymizableDto { public static final String EXPOSED_TO_MOSQUITO_BORNE_VIRUSES = "exposedToMosquitoBorneViruses"; public static final String EXPOSED_TO_MOSQUITO_BORNE_VIRUSES_TEXT = "exposedToMosquitoBorneVirusesText"; public static final String VACCINATED_AGAINST_MOSQUITO_BORNE_VIRUSES = "vaccinatedAgainstMosquitoBorneViruses"; + public static final String MALARIA = "malaria"; + public static final String MALARIA_INFECTED_YEAR = "malariaInfectedYear"; @HideForCountries(countries = { CountryHelper.COUNTRY_CODE_GERMANY, @@ -170,6 +172,14 @@ public class HealthConditionsDto extends PseudonymizableDto { Disease.DENGUE }) private YesNoUnknown vaccinatedAgainstMosquitoBorneViruses; + @Diseases(value = { + Disease.MALARIA }) + private YesNoUnknown malaria; + + @Diseases(value = { + Disease.MALARIA }) + private Integer malariaInfectedYear; + public static HealthConditionsDto build() { HealthConditionsDto healthConditions = new HealthConditionsDto(); healthConditions.setUuid(DataHelper.createUuid()); @@ -447,4 +457,20 @@ public YesNoUnknown getVaccinatedAgainstMosquitoBorneViruses() { public void setVaccinatedAgainstMosquitoBorneViruses(YesNoUnknown vaccinatedAgainstMosquitoBorneViruses) { this.vaccinatedAgainstMosquitoBorneViruses = vaccinatedAgainstMosquitoBorneViruses; } + + public YesNoUnknown getMalaria() { + return malaria; + } + + public void setMalaria(YesNoUnknown malaria) { + this.malaria = malaria; + } + + public Integer getMalariaInfectedYear() { + return malariaInfectedYear; + } + + public void setMalariaInfectedYear(Integer malariaInfectedYear) { + this.malariaInfectedYear = malariaInfectedYear; + } } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationFacade.java index 6419c30519e..f0d6e92a4aa 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationFacade.java @@ -54,6 +54,8 @@ public interface DiseaseConfigurationFacade { int getMinIncubationPeriod(Disease disease); + DiseaseConfigurationDto getDiseaseConfiguration(Disease disease); + String getCaseDefinitionText(Disease disease); Integer getAutomaticSampleAssignmentThreshold(Disease disease); diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/epidata/DiseaseTransmission.java b/sormas-api/src/main/java/de/symeda/sormas/api/epidata/DiseaseTransmission.java new file mode 100644 index 00000000000..61f573df864 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/epidata/DiseaseTransmission.java @@ -0,0 +1,36 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.epidata; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum DiseaseTransmission { + + TRANSMITTED_WITH_MOSQUITOES_FROM_ENDEMIC_COUNTRY, + TRANSMITTED_WITH_MOSQUITOES_BY_AIR, + TRANSMITTED_THROUGH_MEDICAL_CARE, + TRANSMITTED_WITH_STRONG_EPI_EVIDENCE, + TRANSMITTED_WITHOUT_EVIDENCE, + TRANSMITTED_FROM_MOTHER_TO_CHILD, + TRANSMITTED_BY_LAB, + TRANSFUSION_TRANSPLANT_RECIPIENT, + OTHER, + UNKNOWN; + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} 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 bad504a8f2a..63bf927cab9 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 @@ -18,6 +18,7 @@ package de.symeda.sormas.api.epidata; import java.util.ArrayList; +import java.util.Date; import java.util.List; import javax.validation.Valid; @@ -68,6 +69,8 @@ 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 EXPOSURE_START_DATE = "exposureStartDate"; + public static final String EXPOSURE_END_DATE = "exposureEndDate"; public static final String OTHER_DETAILS = "otherDetails"; private YesNoUnknown exposureDetailsKnown; @@ -131,6 +134,17 @@ public class EpiDataDto extends PseudonymizableDto { Disease.CRYPTOSPORIDIOSIS }) private String infectionSourceText; + // this values only for display the data from UI. + + @Diseases({ + Disease.DENGUE, + Disease.MALARIA }) + private Date exposureStartDate; + @Diseases({ + Disease.DENGUE, + Disease.MALARIA }) + private Date exposureEndDate; + @Diseases({ Disease.GIARDIASIS }) private CountryReferenceDto country; @@ -288,6 +302,22 @@ public void setImportedCase(YesNoUnknown importedCase) { this.importedCase = importedCase; } + public Date getExposureStartDate() { + return exposureStartDate; + } + + public void setExposureStartDate(Date exposureStartDate) { + this.exposureStartDate = exposureStartDate; + } + + public Date getExposureEndDate() { + return exposureEndDate; + } + + public void setExposureEndDate(Date exposureEndDate) { + this.exposureEndDate = exposureEndDate; + } + public CountryReferenceDto getCountry() { return country; } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseDiseaseExportEntryDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseDiseaseExportEntryDto.java index 8df1fe4e7d6..d29e6017d14 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseDiseaseExportEntryDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseDiseaseExportEntryDto.java @@ -87,7 +87,7 @@ public class EpipulseDiseaseExportEntryDto { private Date dateOfLaboratoryResult; private List typeOfSpecimenCollected; // SampleMaterial mapped to EpiPulse codes (repeatable) private String resultOfVirusDetection; // PathogenTestResultType mapped to POS/NEG/EQUI/NOTEST - private String genotype; // PathogenTest typingId/genoTypeResult + private String genotype; // PathogenTest typingId/genoType private List typeOfSpecimenSerology; // SampleMaterial for serology tests (repeatable) private String resultIgG; // IgG test result private String resultIgM; // IgM test result diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapper.java b/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapper.java index e6de9520f71..fd49a740801 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapper.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapper.java @@ -116,7 +116,7 @@ public static String mapTestResultToEpipulseCode(PathogenTestResultType testResu * MEASV_C1, MEASV_C2, MEASV_D1-D11, MEASV_E, MEASV_F, MEASV_G1-G3, MEASV_H1-H2, etc. * * @param genotypeText - * SORMAS genotype string (from typingId or genoTypeResult) + * SORMAS genotype string (from typingId or genoType) * @return Normalized EpiPulse genotype code, or null if not a valid measles genotype */ public static String normalizeGenotypeForEpipulse(String genotypeText) { diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/labmessage/TestReportDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/labmessage/TestReportDto.java index 8f82468ca69..edb0a3099aa 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/labmessage/TestReportDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/labmessage/TestReportDto.java @@ -11,7 +11,7 @@ import de.symeda.sormas.api.feature.FeatureType; import de.symeda.sormas.api.i18n.Validations; import de.symeda.sormas.api.infrastructure.country.CountryReferenceDto; -import de.symeda.sormas.api.sample.GenoTypeResult; +import de.symeda.sormas.api.sample.GenoType; import de.symeda.sormas.api.sample.PCRTestSpecification; import de.symeda.sormas.api.sample.PathogenSpecie; import de.symeda.sormas.api.sample.PathogenStrainCallStatus; @@ -168,7 +168,7 @@ public class TestReportDto extends EntityDto { @HideForCountriesExcept(countries = CountryHelper.COUNTRY_CODE_LUXEMBOURG) private CountryReferenceDto prescriberCountry; @HideForCountriesExcept(countries = CountryHelper.COUNTRY_CODE_LUXEMBOURG) - private GenoTypeResult genoTypeResult; + private GenoType genoType; @HideForCountriesExcept(countries = CountryHelper.COUNTRY_CODE_LUXEMBOURG) private RsvSubtype rsvSubtype; @@ -572,12 +572,12 @@ public void setPrescriberCountry(CountryReferenceDto prescriberCountry) { this.prescriberCountry = prescriberCountry; } - public GenoTypeResult getGenoTypeResult() { - return genoTypeResult; + public GenoType getGenoType() { + return genoType; } - public void setGenoTypeResult(GenoTypeResult genoTypeResult) { - this.genoTypeResult = genoTypeResult; + public void setGenoType(GenoType genoType) { + this.genoType = genoType; } public RsvSubtype getRsvSubtype() { diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/ExternalMessageMapper.java b/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/ExternalMessageMapper.java index 40b4df4f236..2e8ede5222e 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/ExternalMessageMapper.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/ExternalMessageMapper.java @@ -55,6 +55,7 @@ import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.SampleDto; +import de.symeda.sormas.api.sample.Serotype; import de.symeda.sormas.api.therapy.DrugSusceptibilityDto; import de.symeda.sormas.api.utils.DataHelper; import de.symeda.sormas.api.utils.DateHelper; @@ -705,11 +706,7 @@ public List mapToPathogenTest(TestReportDto sourceTestReport, Pathogen pathogenTest.getPrescriberCountry(), sourceTestReport.getPrescriberCountry(), PathogenTestDto.PRESCRIBER_COUNTRY), - Mapping.of( - pathogenTest::setGenoTypeResult, - pathogenTest.getGenoTypeResult(), - sourceTestReport.getGenoTypeResult(), - PathogenTestDto.GENOTYPE_RESULT), + Mapping.of(pathogenTest::setGenoType, pathogenTest.getGenoType(), sourceTestReport.getGenoType(), PathogenTestDto.GENOTYPE), Mapping.of( pathogenTest::setSeroGroupSpecification, pathogenTest.getSeroGroupSpecification(), @@ -720,7 +717,11 @@ public List mapToPathogenTest(TestReportDto sourceTestReport, Pathogen pathogenTest.getSeroGroupSpecificationText(), sourceTestReport.getSeroGroupSpecificationText(), PathogenTestDto.SERO_GROUP_SPECIFICATION), - Mapping.of(pathogenTest::setSerotype, pathogenTest.getSerotype(), sourceTestReport.getSerotype(), PathogenTestDto.SEROTYPE), + Mapping.of( + pathogenTest::setSerotype, + pathogenTest.getSerotype(), + Serotype.fromString(sourceTestReport.getSerotype()), + PathogenTestDto.SEROTYPE), Mapping.of( pathogenTest::setSeroTypingMethod, pathogenTest.getSeroTypingMethod(), diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java index d679b54ee62..d2543b9e84d 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java @@ -2032,6 +2032,8 @@ public interface Captions { String HealthConditions_immunodeficiencyOtherThanHiv = "HealthConditions.immunodeficiencyOtherThanHiv"; String HealthConditions_immunodeficiencyOtherThanHivText = "HealthConditions.immunodeficiencyOtherThanHivText"; String HealthConditions_immunodepression = "HealthConditions.immunodepression"; + String HealthConditions_malaria = "HealthConditions.malaria"; + String HealthConditions_malariaInfectedYear = "HealthConditions.malariaInfectedYear"; String HealthConditions_malignancyChemotherapy = "HealthConditions.malignancyChemotherapy"; String HealthConditions_obesity = "HealthConditions.obesity"; String HealthConditions_otherConditions = "HealthConditions.otherConditions"; @@ -2271,6 +2273,7 @@ public interface Captions { String outbreakOutbreak = "outbreakOutbreak"; String passportNumber = "passportNumber"; String PathogenTest = "PathogenTest"; + String PathogenTest_antibodyTitre = "PathogenTest.antibodyTitre"; String PathogenTest_cqValue = "PathogenTest.cqValue"; String PathogenTest_ctValueE = "PathogenTest.ctValueE"; String PathogenTest_ctValueN = "PathogenTest.ctValueN"; @@ -2283,14 +2286,16 @@ public interface Captions { String PathogenTest_externalId = "PathogenTest.externalId"; String PathogenTest_externalOrderId = "PathogenTest.externalOrderId"; String PathogenTest_fourFoldIncreaseAntibodyTiter = "PathogenTest.fourFoldIncreaseAntibodyTiter"; - String PathogenTest_genoTypeResult = "PathogenTest.genoTypeResult"; - String PathogenTest_genoTypeResultText = "PathogenTest.genoTypeResultText"; + String PathogenTest_fourFoldIncreaseAntibodyTiter_DENGUE = "PathogenTest.fourFoldIncreaseAntibodyTiter.DENGUE"; + String PathogenTest_genoType = "PathogenTest.genoType"; + String PathogenTest_genoTypeText = "PathogenTest.genoTypeText"; String PathogenTest_isoniazidResistant = "PathogenTest.isoniazidResistant"; String PathogenTest_lab = "PathogenTest.lab"; String PathogenTest_labDetails = "PathogenTest.labDetails"; String PathogenTest_otherDeletionReason = "PathogenTest.otherDeletionReason"; String PathogenTest_patternProfile = "PathogenTest.patternProfile"; String PathogenTest_pcrTestSpecification = "PathogenTest.pcrTestSpecification"; + String PathogenTest_performedByReferenceLaboratory = "PathogenTest.performedByReferenceLaboratory"; String PathogenTest_preliminary = "PathogenTest.preliminary"; String PathogenTest_prescriber = "PathogenTest.prescriber"; String PathogenTest_prescriberAddress = "PathogenTest.prescriberAddress"; @@ -2302,15 +2307,19 @@ public interface Captions { String PathogenTest_prescriberPhysicianCode = "PathogenTest.prescriberPhysicianCode"; String PathogenTest_prescriberPostalCode = "PathogenTest.prescriberPostalCode"; String PathogenTest_reportDate = "PathogenTest.reportDate"; + String PathogenTest_resultDetails = "PathogenTest.resultDetails"; + String PathogenTest_retestRequested = "PathogenTest.retestRequested"; String PathogenTest_rifampicinResistant = "PathogenTest.rifampicinResistant"; String PathogenTest_rsv_testedDiseaseVariant = "PathogenTest.rsv.testedDiseaseVariant"; String PathogenTest_rsv_testedDiseaseVariantDetails = "PathogenTest.rsv.testedDiseaseVariantDetails"; String PathogenTest_seroGroupSpecification = "PathogenTest.seroGroupSpecification"; String PathogenTest_seroGroupSpecificationText = "PathogenTest.seroGroupSpecificationText"; String PathogenTest_serotype = "PathogenTest.serotype"; + String PathogenTest_serotypeText = "PathogenTest.serotypeText"; String PathogenTest_seroTypingMethod = "PathogenTest.seroTypingMethod"; String PathogenTest_seroTypingMethodText = "PathogenTest.seroTypingMethodText"; String PathogenTest_specie = "PathogenTest.specie"; + String PathogenTest_specieText = "PathogenTest.specieText"; String PathogenTest_strainCallStatus = "PathogenTest.strainCallStatus"; String PathogenTest_testDateTime = "PathogenTest.testDateTime"; String PathogenTest_testedDisease = "PathogenTest.testedDisease"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java index 3f4cd18307c..089ed27a5f3 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java @@ -402,6 +402,8 @@ public interface Strings { String errorViewNotFound = "errorViewNotFound"; String errorWasReported = "errorWasReported"; String errorWritingTemplate = "errorWritingTemplate"; + String exposureEndDate = "exposureEndDate"; + String exposureStartDate = "exposureStartDate"; String externalMessageMultipleSampleReports = "externalMessageMultipleSampleReports"; String ExternalSurveillanceToolGateway_confirmDeleteCase = "ExternalSurveillanceToolGateway.confirmDeleteCase"; String ExternalSurveillanceToolGateway_confirmDeleteEvent = "ExternalSurveillanceToolGateway.confirmDeleteEvent"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/GenoTypeResult.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/GenoType.java similarity index 98% rename from sormas-api/src/main/java/de/symeda/sormas/api/sample/GenoTypeResult.java rename to sormas-api/src/main/java/de/symeda/sormas/api/sample/GenoType.java index 1018a82050d..7b35e012410 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/GenoTypeResult.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/GenoType.java @@ -21,7 +21,7 @@ import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.utils.Diseases; -public enum GenoTypeResult { +public enum GenoType { @Diseases({ Disease.MEASLES }) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java index 662c5849ff4..738989ba968 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java @@ -20,8 +20,10 @@ import java.util.List; import java.util.stream.Collectors; +import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.utils.ApplicableToPathogenTests; +import de.symeda.sormas.api.utils.Diseases; public enum PathogenSpecie { @@ -37,8 +39,158 @@ public enum PathogenSpecie { @ApplicableToPathogenTests(value = { PathogenTestType.SPOLIGOTYPING }) OTHER_MTBC_MEMBER, + @Diseases({ + Disease.MALARIA }) @ApplicableToPathogenTests(value = { - PathogenTestType.SPOLIGOTYPING }) + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + SPP, + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + FALCIPARUM, + @Diseases({ + Disease.MALARIA }) + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + VIVAX, + @Diseases({ + Disease.MALARIA }) + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + MALARIAE, + @Diseases({ + Disease.MALARIA }) + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + OVALE, + @Diseases({ + Disease.MALARIA }) + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + KNOWLESI, + @Diseases({ + Disease.MALARIA }) + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + CYNOMOLGI, + @Diseases({ + Disease.MALARIA }) + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + SPECIES, + @Diseases({ + Disease.MALARIA }) + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + NOT_SPECIFIED, + @Diseases({ + Disease.MALARIA }) + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + COINFECTION, + @Diseases({ + Disease.MALARIA }) + @ApplicableToPathogenTests(value = { + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) + OTHER, + @ApplicableToPathogenTests(value = { + PathogenTestType.SPOLIGOTYPING, + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST }) UNKNOWN, @ApplicableToPathogenTests(value = { PathogenTestType.SPOLIGOTYPING }) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestDto.java index df46ffdb50f..01078b2d266 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestDto.java @@ -77,6 +77,7 @@ public class PathogenTestDto extends PseudonymizableDto { public static final String TEST_RESULT_VERIFIED = "testResultVerified"; public static final String FOUR_FOLD_INCREASE_ANTIBODY_TITER = "fourFoldIncreaseAntibodyTiter"; public static final String SEROTYPE = "serotype"; + public static final String SEROTYPE_TEXT = "serotypeText"; public static final String CQ_VALUE = "cqValue"; public static final String CT_VALUE_E = "ctValueE"; public static final String CT_VALUE_N = "ctValueN"; @@ -103,6 +104,7 @@ public class PathogenTestDto extends PseudonymizableDto { public static final String RIFAMPICIN_RESISTANT = "rifampicinResistant"; public static final String ISONIAZID_RESISTANT = "isoniazidResistant"; public static final String SPECIE = "specie"; + public static final String SPECIE_TEXT = "specieText"; public static final String PATTERN_PROFILE = "patternProfile"; public static final String STRAIN_CALL_STATUS = "strainCallStatus"; public static final String TEST_SCALE = "testScale"; @@ -111,8 +113,8 @@ public class PathogenTestDto extends PseudonymizableDto { public static final String SERO_TYPING_METHOD_TEXT = "seroTypingMethodText"; public static final String SERO_GROUP_SPECIFICATION = "seroGroupSpecification"; public static final String SERO_GROUP_SPECIFICATION_TEXT = "seroGroupSpecificationText"; - public static final String GENOTYPE_RESULT = "genoTypeResult"; - public static final String GENOTYPE_RESULT_TEXT = "genoTypeResultText"; + public static final String GENOTYPE = "genoType"; + public static final String GENOTYPE_TEXT = "genoTypeText"; public static final String RSV_SUBTYPE = "rsvSubtype"; public static final String TUBE_NIL = "tubeNil"; public static final String TUBE_NIL_GT10 = "tubeNilGT10"; @@ -122,6 +124,10 @@ public class PathogenTestDto extends PseudonymizableDto { public static final String TUBE_AG_TB2_GT10 = "tubeAgTb2GT10"; public static final String TUBE_MITOGENE = "tubeMitogene"; public static final String TUBE_MITOGENE_GT10 = "tubeMitogeneGT10"; + public static final String ANTIBODY_TITRE = "antibodyTitre"; + public static final String PERFORMED_BY_REFERENCE_LABORATORY = "performedByReferenceLaboratory"; + public static final String RETEST_REQUESTED = "retestRequested"; + public static final String RESULT_DETAILS = "resultDetails"; private SampleReferenceDto sample; private EnvironmentSampleReferenceDto environmentSample; @@ -158,8 +164,9 @@ public class PathogenTestDto extends PseudonymizableDto { private Boolean testResultVerified; private boolean fourFoldIncreaseAntibodyTiter; @SensitiveData + private Serotype serotype; @Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong) - private String serotype; + private String serotypeText; private Float cqValue; @HideForCountriesExcept(countries = CountryHelper.COUNTRY_CODE_LUXEMBOURG) private Float ctValueE; @@ -222,6 +229,7 @@ public class PathogenTestDto extends PseudonymizableDto { private YesNoUnknown rifampicinResistant; private YesNoUnknown isoniazidResistant; private PathogenSpecie specie; + private String specieText; private String patternProfile; private PathogenStrainCallStatus strainCallStatus; private PathogenTestScale testScale; @@ -242,13 +250,13 @@ public class PathogenTestDto extends PseudonymizableDto { @Diseases(value = { Disease.MEASLES, Disease.CRYPTOSPORIDIOSIS }) - private GenoTypeResult genoTypeResult; + private GenoType genoType; @SensitiveData @HideForCountriesExcept(countries = CountryHelper.COUNTRY_CODE_LUXEMBOURG) @Diseases(value = { Disease.MEASLES }) - private String genoTypeResultText; + private String genoTypeText; @SensitiveData @HideForCountriesExcept(countries = CountryHelper.COUNTRY_CODE_LUXEMBOURG) @@ -311,6 +319,16 @@ public class PathogenTestDto extends PseudonymizableDto { Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS }) private Boolean tubeMitogeneGT10; + // Dengue and Malaria changes + @Diseases(value = { + Disease.DENGUE }) + private String antibodyTitre; + private Boolean performedByReferenceLaboratory; + // defaulting this test to false. + private Boolean retestRequested = false; + @Diseases({ + Disease.MALARIA }) + private String resultDetails; public static PathogenTestDto build(SampleDto sample, UserDto currentUser) { @@ -529,11 +547,11 @@ public PathogenTestReferenceDto toReference() { return new PathogenTestReferenceDto(getUuid()); } - public String getSerotype() { + public Serotype getSerotype() { return serotype; } - public void setSerotype(String serotype) { + public void setSerotype(Serotype serotype) { this.serotype = serotype; } @@ -795,20 +813,20 @@ public void setSeroTypingMethod(SerotypingMethod seroTypingMethod) { this.seroTypingMethod = seroTypingMethod; } - public GenoTypeResult getGenoTypeResult() { - return genoTypeResult; + public GenoType getGenoType() { + return genoType; } - public void setGenoTypeResult(GenoTypeResult genoTypeResult) { - this.genoTypeResult = genoTypeResult; + public void setGenoType(GenoType genoType) { + this.genoType = genoType; } - public String getGenoTypeResultText() { - return genoTypeResultText; + public String getGenoTypeText() { + return genoTypeText; } - public void setGenoTypeResultText(String genoTypeResultText) { - this.genoTypeResultText = genoTypeResultText; + public void setGenoTypeText(String genoTypeText) { + this.genoTypeText = genoTypeText; } public String getSeroTypingMethodText() { @@ -907,6 +925,54 @@ public void setTubeMitogeneGT10(Boolean tubeMitogeneGT10) { this.tubeMitogeneGT10 = tubeMitogeneGT10; } + public String getAntibodyTitre() { + return antibodyTitre; + } + + public void setAntibodyTitre(String antibodyTitre) { + this.antibodyTitre = antibodyTitre; + } + + public Boolean getPerformedByReferenceLaboratory() { + return performedByReferenceLaboratory; + } + + public void setPerformedByReferenceLaboratory(Boolean performedByReferenceLaboratory) { + this.performedByReferenceLaboratory = performedByReferenceLaboratory; + } + + public Boolean getRetestRequested() { + return retestRequested; + } + + public void setRetestRequested(Boolean retestRequested) { + this.retestRequested = retestRequested; + } + + public String getResultDetails() { + return resultDetails; + } + + public void setResultDetails(String resultDetails) { + this.resultDetails = resultDetails; + } + + public String getSerotypeText() { + return serotypeText; + } + + public void setSerotypeText(String serotypeText) { + this.serotypeText = serotypeText; + } + + public String getSpecieText() { + return specieText; + } + + public void setSpecieText(String specieText) { + this.specieText = specieText; + } + @Override public PathogenTestDto clone() throws CloneNotSupportedException { return (PathogenTestDto) super.clone(); diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestType.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestType.java index 004a0459a56..cba443d521e 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestType.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestType.java @@ -28,7 +28,9 @@ public enum PathogenTestType { Disease.RESPIRATORY_SYNCYTIAL_VIRUS, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) ANTIBODY_DETECTION, ANTIGEN_DETECTION, @@ -40,7 +42,9 @@ public enum PathogenTestType { RAPID_TEST, @Diseases(value = { - Disease.GIARDIASIS }, hide = true) + Disease.GIARDIASIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) CULTURE, @Diseases(value = { @@ -49,13 +53,16 @@ public enum PathogenTestType { Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) HISTOPATHOLOGY, @Diseases(value = { Disease.RESPIRATORY_SYNCYTIAL_VIRUS, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.MALARIA }, hide = true) ISOLATION, @Diseases(value = { @@ -63,7 +70,8 @@ public enum PathogenTestType { Disease.INVASIVE_MENINGOCOCCAL_INFECTION, Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.MALARIA }, hide = true) IGM_SERUM_ANTIBODY, @Diseases(value = { @@ -71,7 +79,8 @@ public enum PathogenTestType { Disease.INVASIVE_MENINGOCOCCAL_INFECTION, Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.MALARIA }, hide = true) IGG_SERUM_ANTIBODY, @Diseases(value = { @@ -80,7 +89,9 @@ public enum PathogenTestType { Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) IGA_SERUM_ANTIBODY, @Diseases(value = { @@ -90,7 +101,9 @@ public enum PathogenTestType { Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) INCUBATION_TIME, @Diseases(value = { @@ -112,7 +125,9 @@ public enum PathogenTestType { @Diseases(value = { Disease.CORONAVIRUS, Disease.RESPIRATORY_SYNCYTIAL_VIRUS, - Disease.MEASLES }, hide = true) + Disease.MEASLES, + Disease.DENGUE, + Disease.MALARIA }, hide = true) MICROSCOPY, @Diseases(value = { @@ -120,11 +135,13 @@ public enum PathogenTestType { Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.MALARIA }, hide = true) NEUTRALIZING_ANTIBODIES, @Diseases(value = { - Disease.RESPIRATORY_SYNCYTIAL_VIRUS }) + Disease.RESPIRATORY_SYNCYTIAL_VIRUS, + Disease.MALARIA }) ENZYME_LINKED_IMMUNOSORBENT_ASSAY, PCR_RT_PCR, @@ -134,7 +151,9 @@ public enum PathogenTestType { Disease.RESPIRATORY_SYNCYTIAL_VIRUS, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) GRAM_STAIN, @Diseases(value = { @@ -144,7 +163,9 @@ public enum PathogenTestType { Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) LATEX_AGGLUTINATION, @Diseases(value = { @@ -153,7 +174,9 @@ public enum PathogenTestType { Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) CQ_VALUE_DETECTION, @Diseases(value = { @@ -169,7 +192,9 @@ public enum PathogenTestType { Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) DNA_MICROARRAY, @Diseases(value = { @@ -178,7 +203,9 @@ public enum PathogenTestType { Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Disease.MEASLES, Disease.GIARDIASIS, - Disease.CRYPTOSPORIDIOSIS }, hide = true) + Disease.CRYPTOSPORIDIOSIS, + Disease.DENGUE, + Disease.MALARIA }, hide = true) TMA, @Diseases(value = { @@ -239,6 +266,39 @@ public enum PathogenTestType { Disease.CRYPTOSPORIDIOSIS }) GENOTYPING, + @Diseases(value = { + Disease.DENGUE }) + NAAT, + @Diseases({ + Disease.MALARIA }) + THICK_BLOOD_SMEAR, + @Diseases({ + Disease.MALARIA }) + THIN_BLOOD_SMEAR, + @Diseases({ + Disease.MALARIA }) + Q_PCR, + @Diseases({ + Disease.MALARIA }) + LAMP, + + // @Herold need to refactor this as part of the test categories. + // Antigen detection test is a test category. To create tests for the below categories, decided to use as OTHER_<> + @Diseases({ + Disease.MALARIA }) + OTHER_ANTIGEN_DETECTION_TEST, + // Test for category + @Diseases({ + Disease.MALARIA }) + OTHER_MOLECULAR_ASSAY, + // Test for category + @Diseases({ + Disease.MALARIA }) + OTHER_SEROLOGICAL_TEST, + // Indirect Fluorescent Antibody Test + @Diseases({ + Disease.MALARIA }) + IFAT, OTHER; @Override diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/SampleListEntryDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/SampleListEntryDto.java index ab9ed1a38bd..43b5d5119ea 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/SampleListEntryDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/SampleListEntryDto.java @@ -29,6 +29,10 @@ public class SampleListEntryDto extends PseudonymizableIndexDto implements Seria private String samplingReasonDetails; private AdditionalTestingStatus additionalTestingStatus; private long pathogenTestCount; + // Need to display these values in side view + private Serotype serotype; + private GenoType genoType; + private PathogenSpecie specie; public SampleListEntryDto( String uuid, @@ -186,6 +190,30 @@ public void setPathogenTestCount(long pathogenTestCount) { this.pathogenTestCount = pathogenTestCount; } + public Serotype getSerotype() { + return serotype; + } + + public void setSerotype(Serotype serotype) { + this.serotype = serotype; + } + + public GenoType getGenoType() { + return genoType; + } + + public void setGenoType(GenoType genoType) { + this.genoType = genoType; + } + + public PathogenSpecie getSpecie() { + return specie; + } + + public void setSpecie(PathogenSpecie specie) { + this.specie = specie; + } + public SampleReferenceDto toReference() { return new SampleReferenceDto(getUuid(), getSampleMaterial(), null, null, null); } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/SampleMaterial.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/SampleMaterial.java index d88633653b6..6045ef4dff2 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/SampleMaterial.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/SampleMaterial.java @@ -112,8 +112,7 @@ public enum SampleMaterial { Disease.MEASLES, Disease.GIARDIASIS, Disease.CRYPTOSPORIDIOSIS, - Disease.MALARIA, - Disease.DENGUE }, hide = true) + Disease.MALARIA }, hide = true) CEREBROSPINAL_FLUID, @Diseases(value = { diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/Serotype.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/Serotype.java new file mode 100644 index 00000000000..e522adaa985 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/Serotype.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * + * 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.sample; + +import java.util.Arrays; + +import org.apache.commons.lang3.StringUtils; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.utils.Diseases; + +/** + * Serotypes of disease. + */ + +public enum Serotype { + + @Diseases({ + Disease.DENGUE }) + DENV_1, + @Diseases({ + Disease.DENGUE }) + DENV_2, + @Diseases({ + Disease.DENGUE }) + DENV_3, + @Diseases({ + Disease.DENGUE }) + DENV_4, + OTHER; + + // if a serotype is not in the list (existing serotypes) -> will be displayed with OTHER along with its value in the serotypetext. + public static Serotype fromString(String serotype) { + if (StringUtils.isBlank(serotype)) { + return null; + } + String serotypeEnumVal = serotype.toUpperCase().replaceAll("[-_]", "_"); + + return Arrays.asList(Serotype.values()).stream().noneMatch(s -> s.name().equals(serotypeEnumVal)) ? OTHER : Serotype.valueOf(serotypeEnumVal); + } + + @Override + public String toString() { + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/symptoms/SymptomsDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/symptoms/SymptomsDto.java index d14699730fb..e87468a18cb 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/symptoms/SymptomsDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/symptoms/SymptomsDto.java @@ -697,6 +697,8 @@ public static SymptomsDto build() { MALARIA, UNDEFINED, OTHER }) + @Complication({ + MEASLES }) @Outbreaks @SymptomGrouping(SymptomGroup.GASTROINTESTINAL) private SymptomState diarrhea; @@ -1595,8 +1597,10 @@ public static SymptomsDto build() { POLIO, UNDEFINED, OTHER }) + @Complication({ + MEASLES }) @HideForCountries - @SymptomGrouping(SymptomGroup.OTHER) + @SymptomGrouping(SymptomGroup.GENERAL) private SymptomState otitisMedia; @Diseases({ @@ -2272,6 +2276,8 @@ public static SymptomsDto build() { MEASLES, UNDEFINED, OTHER }) + @Complication({ + MEASLES }) @HideForCountries(countries = CountryHelper.COUNTRY_CODE_SWITZERLAND) @SymptomGrouping(SymptomGroup.RESPIRATORY) private SymptomState pneumoniaClinicalOrRadiologic; @@ -2541,6 +2547,8 @@ public static SymptomsDto build() { PLAGUE, ANTHRAX, CORONAVIRUS, + CRYPTOSPORIDIOSIS, + GIARDIASIS, UNDEFINED, OTHER }) @DependantOn(OTHER_COMPLICATIONS) @@ -2550,7 +2558,9 @@ public static SymptomsDto build() { @Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong) @Complication({ DENGUE, - MALARIA }) + MALARIA, + CRYPTOSPORIDIOSIS, + GIARDIASIS }) private String otherComplicationsText; @Diseases({ @@ -2780,6 +2790,8 @@ public static SymptomsDto build() { MEASLES, DENGUE, MALARIA }) + @Complication({ + MEASLES }) @SymptomGrouping(SymptomGroup.OTHER) private SymptomState unknownSymptom; @@ -2807,14 +2819,15 @@ public static SymptomsDto build() { GIARDIASIS, CRYPTOSPORIDIOSIS }) @DependantOn("parentTimeOffWork") + @SymptomGrouping(SymptomGroup.OTHER) private Float timeOffWorkDays; @HideForCountriesExcept(countries = CountryHelper.COUNTRY_CODE_LUXEMBOURG) @Diseases({ MEASLES }) - @SymptomGrouping(SymptomGroup.NERVOUS_SYSTEM) @Complication({ MEASLES }) + @SymptomGrouping(SymptomGroup.NERVOUS_SYSTEM) private SymptomState acuteEncephalitis; @Diseases({ GIARDIASIS }) @@ -2851,10 +2864,14 @@ public static SymptomsDto build() { @Diseases({ GIARDIASIS, CRYPTOSPORIDIOSIS }) + @Complication() + @SymptomGrouping(SymptomGroup.OTHER) private SymptomState reoccurrence; @Diseases({ GIARDIASIS, CRYPTOSPORIDIOSIS }) + @Complication() + @SymptomGrouping(SymptomGroup.OTHER) private SymptomState overnightStayRequired; @Diseases({ GIARDIASIS }) diff --git a/sormas-api/src/main/resources/captions.properties b/sormas-api/src/main/resources/captions.properties index 291312fb63c..5c48be795db 100644 --- a/sormas-api/src/main/resources/captions.properties +++ b/sormas-api/src/main/resources/captions.properties @@ -1661,7 +1661,9 @@ HealthConditions.complianceWithTreatment=Compliance With Treatment HealthConditions.exposedToMosquitoBorneViruses=Exposed to mosquito-borne viruses HealthConditions.exposedToMosquitoBorneVirusesText=Specify exposure to mosquito-borne HealthConditions.vaccinatedAgainstMosquitoBorneViruses=Vaccinated against mosquito-borne viruses -HealthConditions.immunodeficiencyOtherThanHivText=Specify immunodeficiency other than HIV +HealthConditions.immunodeficiencyOtherThanHivText=Specify immunodeficiency other than HIV +HealthConditions.malaria=Malaria +HealthConditions.malariaInfectedYear=Malaria infected year # Import importDetailed=Detailed Import importDownloadCaseImportTemplate=Download Case Import Template @@ -1868,6 +1870,7 @@ pathogenTestSelect=Select pathogen test PathogenTest=Pathogen test PathogenTests=Pathogen tests PathogenTest.fourFoldIncreaseAntibodyTiter=4 fold increase of antibody titer +PathogenTest.fourFoldIncreaseAntibodyTiter.DENGUE=Seroconversion/ 4-fold increase PathogenTest.lab=Laboratory PathogenTest.labDetails=Laboratory name & description PathogenTest.testDateTime=Date and time of result @@ -1885,6 +1888,7 @@ PathogenTest.testedPathogen=Tested pathogen PathogenTest.testedPathogenDetails=Tested pathogen details PathogenTest.typingId=Typing ID PathogenTest.serotype=Serotype +PathogenTest.serotypeText=Specify serotype text PathogenTest.cqValue=CQ/CT Value PathogenTest.deletionReason=Reason for deletion PathogenTest.otherDeletionReason=Reason for deletion details @@ -1921,8 +1925,8 @@ PathogenTest.seroTypingMethod=Sero Typing Method PathogenTest.seroTypingMethodText=Specify Sero Typing Method PathogenTest.seroGroupSpecification=Serogroup Specification PathogenTest.seroGroupSpecificationText=Specify Serogroup Specification Method -PathogenTest.genoTypeResult=Genotype result -PathogenTest.genoTypeResultText=Specify Genotype result text +PathogenTest.genoType=Genotype result +PathogenTest.genoTypeText=Specify genotype text PathogenTest.tubeNil=Tube Nil PathogenTest.tubeNilGT10=Tube Nil >10 PathogenTest.tubeAgTb1=Tube Ag TB1 @@ -1931,6 +1935,11 @@ PathogenTest.tubeAgTb2=Tube Ag TB2 PathogenTest.tubeAgTb2GT10=Tube Ag TB2 >10 PathogenTest.tubeMitogene=Tube Mitogen PathogenTest.tubeMitogeneGT10=Tube Mitogen >10 +PathogenTest.antibodyTitre=Antibody titre +PathogenTest.performedByReferenceLaboratory=Performed by reference laboratory +PathogenTest.retestRequested=Retest requested +PathogenTest.resultDetails=Result details +PathogenTest.specieText=Specify specie text # Person personPersonsList=Person list personCreateNew=Create a new person diff --git a/sormas-api/src/main/resources/enum.properties b/sormas-api/src/main/resources/enum.properties index e3fd277ef55..01ac659d163 100644 --- a/sormas-api/src/main/resources/enum.properties +++ b/sormas-api/src/main/resources/enum.properties @@ -647,6 +647,18 @@ DiseaseTransmissionMode.FOOD = Primarily via food DiseaseTransmissionMode.VECTOR_BORNE = Primarily vector-borne DiseaseTransmissionMode.UNKNOWN = Unknown +# DiseaseTransmission +DiseaseTransmission.TRANSMITTED_WITH_MOSQUITOES_FROM_ENDEMIC_COUNTRY=Contracted from infected mosquitoes in an endemic country +DiseaseTransmission.TRANSMITTED_WITH_MOSQUITOES_BY_AIR=Contracted from infected mosquitoes transported by air from an endemic country to a non-endemic country +DiseaseTransmission.TRANSMITTED_THROUGH_MEDICAL_CARE=Contracted while receiving medical care +DiseaseTransmission.TRANSMITTED_WITH_STRONG_EPI_EVIDENCE=Acquired locally through mosquito transmission with strong epidemiological evidence directly linking it to a known imported case +DiseaseTransmission.TRANSMITTED_WITHOUT_EVIDENCE=Acquired locally through mosquito transmission without evidence of importation and without a direct link to transmission from an imported case +DiseaseTransmission.TRANSMITTED_FROM_MOTHER_TO_CHILD=Mother-to-child transmission +DiseaseTransmission.TRANSMITTED_BY_LAB=Laboratory transmission due to occupational exposure +DiseaseTransmission.TRANSFUSION_TRANSPLANT_RECIPIENT=Transfusion/transplant recipient +DiseaseTransmission.OTHER = Other +DiseaseTransmission.UNKNOWN = Unknown + # DocumentRelatedEntityType DocumentRelatedEntityType.ACTION = Action DocumentRelatedEntityType.CASE = Case @@ -910,36 +922,36 @@ GestationalAgeCategory.AT_TERM=At term (between 38 and 42 weeks of pregnancy) GestationalAgeCategory.PREMATURE_BEFORE_32=Prematurely before 32 weeks of pregnancy GestationalAgeCategory.PREMATURE_32_TO_38=Prematurely between 32 and 38 weeks of pregnancy -#GenoTypeResult -GenoTypeResult.GENOTYPE_A = Genotype A -GenoTypeResult.GENOTYPE_B = Genotype B -GenoTypeResult.GENOTYPE_B2 = Genotype B2 -GenoTypeResult.GENOTYPE_B3 = Genotype B3 -GenoTypeResult.GENOTYPE_C1 = Genotype C1 -GenoTypeResult.GENOTYPE_C2 = Genotype C2 -GenoTypeResult.GENOTYPE_D1 = Genotype D1 -GenoTypeResult.GENOTYPE_D10 = Genotype D10 -GenoTypeResult.GENOTYPE_D11 = Genotype D11 -GenoTypeResult.GENOTYPE_D2 = Genotype D2 -GenoTypeResult.GENOTYPE_D3 = Genotype D3 -GenoTypeResult.GENOTYPE_D4 = Genotype D4 -GenoTypeResult.GENOTYPE_D5 = Genotype D5 -GenoTypeResult.GENOTYPE_D6 = Genotype D6 -GenoTypeResult.GENOTYPE_D7 = Genotype D7 -GenoTypeResult.GENOTYPE_D8 = Genotype D8 -GenoTypeResult.GENOTYPE_D9 = Genotype D9 -GenoTypeResult.GENOTYPE_E = Genotype E -GenoTypeResult.GENOTYPE_F = Genotype F -GenoTypeResult.GENOTYPE_G1 = Genotype G1 -GenoTypeResult.GENOTYPE_G2 = Genotype G2 -GenoTypeResult.GENOTYPE_G3 = Genotype G3 -GenoTypeResult.GENOTYPE_H1 = Genotype H1 -GenoTypeResult.GENOTYPE_H2 = Genotype H2 -GenoTypeResult.CRYPTOSPORIDIUM_HOMINIS = Cryptosporidium hominis -GenoTypeResult.CRYPTOSPORIDIUM_PARVUM = Cryptosporidium parvum -GenoTypeResult.CRYPTOSPORIDIUM_SPECIES = Cryptosporidium species -GenoTypeResult.OTHER = Other -GenoTypeResult.UNKNOWN = Unknown +#GenoType +GenoType.GENOTYPE_A = Genotype A +GenoType.GENOTYPE_B = Genotype B +GenoType.GENOTYPE_B2 = Genotype B2 +GenoType.GENOTYPE_B3 = Genotype B3 +GenoType.GENOTYPE_C1 = Genotype C1 +GenoType.GENOTYPE_C2 = Genotype C2 +GenoType.GENOTYPE_D1 = Genotype D1 +GenoType.GENOTYPE_D10 = Genotype D10 +GenoType.GENOTYPE_D11 = Genotype D11 +GenoType.GENOTYPE_D2 = Genotype D2 +GenoType.GENOTYPE_D3 = Genotype D3 +GenoType.GENOTYPE_D4 = Genotype D4 +GenoType.GENOTYPE_D5 = Genotype D5 +GenoType.GENOTYPE_D6 = Genotype D6 +GenoType.GENOTYPE_D7 = Genotype D7 +GenoType.GENOTYPE_D8 = Genotype D8 +GenoType.GENOTYPE_D9 = Genotype D9 +GenoType.GENOTYPE_E = Genotype E +GenoType.GENOTYPE_F = Genotype F +GenoType.GENOTYPE_G1 = Genotype G1 +GenoType.GENOTYPE_G2 = Genotype G2 +GenoType.GENOTYPE_G3 = Genotype G3 +GenoType.GENOTYPE_H1 = Genotype H1 +GenoType.GENOTYPE_H2 = Genotype H2 +GenoType.CRYPTOSPORIDIUM_HOMINIS = Cryptosporidium hominis +GenoType.CRYPTOSPORIDIUM_PARVUM = Cryptosporidium parvum +GenoType.CRYPTOSPORIDIUM_SPECIES = Cryptosporidium species +GenoType.OTHER = Other +GenoType.UNKNOWN = Unknown HabitationType.MEDICAL=Stay in a Medical Institution HabitationType.OTHER=Other @@ -1184,6 +1196,15 @@ PathogenTestType.SEROGROUPING = Serogrouping PathogenTestType.GENOTYPING = Genotyping PathogenTestType.RAPID_ANTIGEN_DETECTION = Rapid antigen detection test PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY = Enzyme-linked immunosorbent assay (ELISA) +PathogenTestType.NAAT = Nucleic acid amplification test (NAAT) +PathogenTestType.THICK_BLOOD_SMEAR = Thick blood smear +PathogenTestType.THIN_BLOOD_SMEAR = Thin blood smear +PathogenTestType.Q_PCR = qPCR +PathogenTestType.LAMP = Lamp +PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST = Other antigen detection test +PathogenTestType.OTHER_MOLECULAR_ASSAY = Other molecular assay +PathogenTestType.OTHER_SEROLOGICAL_TEST = Other serological test +PathogenTestType.IFAT = Indirect Fluorescent Antibody Test PCRTestSpecification.VARIANT_SPECIFIC = Variant specific PCRTestSpecification.N501Y_MUTATION_DETECTION = N501Y mutation detection @@ -1195,6 +1216,17 @@ PathogenSpecie.MYCOBATERIUM_TUBERCULOSIS = Mycobaterium tuberculosis PathogenSpecie.OTHER_MTBC_MEMBER = Other member of the MTBC PathogenSpecie.UNKNOWN = Unknown PathogenSpecie.NOT_APPLICABLE = Not Applicable +PathogenSpecie.SPP = Spp +PathogenSpecie.FALCIPARUM = Falciparum +PathogenSpecie.VIVAX = Vivax +PathogenSpecie.MALARIAE = Malariae +PathogenSpecie.OVALE = Ovale +PathogenSpecie.KNOWLESI = Knowlesi +PathogenSpecie.CYNOMOLGI = Cynomolgi +PathogenSpecie.SPECIES = Species +PathogenSpecie.NOT_SPECIFIED = Not specified +PathogenSpecie.COINFECTION = Coinfection +PathogenSpecie.OTHER = Other # PathogenTestScale PathogenTestScale.ONE_PLUS = + @@ -1370,6 +1402,13 @@ SeroGroupSpecification.NOT_UNDER_SURVEILLANCE = Not Under Surveillance SeroGroupSpecification.OTHER = Other SeroGroupSpecification.UNKNOWN = Unknown +# Serotype +Serotype.DENV_1=DENV-1 +Serotype.DENV_2=DENV-2 +Serotype.DENV_3=DENV-3 +Serotype.DENV_4=DENV-4 +Serotype.OTHER=Other + # SerotypingMethod SerotypingMethod.MULTIPLEX_PCR = Multiplex PCR SerotypingMethod.QUELLUNG_REACTION = Quellung Reaction diff --git a/sormas-api/src/main/resources/strings.properties b/sormas-api/src/main/resources/strings.properties index e376f18475e..3de2dff1759 100644 --- a/sormas-api/src/main/resources/strings.properties +++ b/sormas-api/src/main/resources/strings.properties @@ -2044,4 +2044,6 @@ Vaccine.uniiCode.notAvailable=N/A(Not yet assigned) Vaccine.vaccinationType.PCV15=PCV15 Vaccine.vaccinationType.PCV13=PCV13 -Vaccine.vaccinationType.PCV20=PCV20 \ No newline at end of file +Vaccine.vaccinationType.PCV20=PCV20 +exposureStartDate = Exposure start date +exposureEndDate = Exposure end date \ No newline at end of file diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/clinicalcourse/HealthConditions.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/clinicalcourse/HealthConditions.java index 0c64615a298..d9bd60f058b 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/clinicalcourse/HealthConditions.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/clinicalcourse/HealthConditions.java @@ -45,6 +45,8 @@ public class HealthConditions extends AbstractDomainObject { private YesNoUnknown exposedToMosquitoBorneViruses; private String exposedToMosquitoBorneVirusesText; private YesNoUnknown vaccinatedAgainstMosquitoBorneViruses; + private YesNoUnknown malaria; + private Integer malariaInfectedYear; private Integer tuberculosisInfectionYear; private YesNoUnknown previousTuberculosisTreatment; @@ -327,4 +329,20 @@ public YesNoUnknown getVaccinatedAgainstMosquitoBorneViruses() { public void setVaccinatedAgainstMosquitoBorneViruses(YesNoUnknown vaccinatedAgainstMosquitoBorneViruses) { this.vaccinatedAgainstMosquitoBorneViruses = vaccinatedAgainstMosquitoBorneViruses; } + + public Integer getMalariaInfectedYear() { + return malariaInfectedYear; + } + + public void setMalariaInfectedYear(Integer malariaInfectedYear) { + this.malariaInfectedYear = malariaInfectedYear; + } + + public YesNoUnknown getMalaria() { + return malaria; + } + + public void setMalaria(YesNoUnknown malaria) { + this.malaria = malaria; + } } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/clinicalcourse/HealthConditionsMapper.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/clinicalcourse/HealthConditionsMapper.java index 41e754b73f3..8546f3f89d5 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/clinicalcourse/HealthConditionsMapper.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/clinicalcourse/HealthConditionsMapper.java @@ -66,6 +66,8 @@ public static HealthConditionsDto toDto(HealthConditions source) { target.setExposedToMosquitoBorneViruses(source.getExposedToMosquitoBorneViruses()); target.setVaccinatedAgainstMosquitoBorneViruses(source.getVaccinatedAgainstMosquitoBorneViruses()); target.setImmunodeficiencyOtherThanHivText(source.getImmunodeficiencyOtherThanHivText()); + target.setMalaria(source.getMalaria()); + target.setMalariaInfectedYear(source.getMalariaInfectedYear()); return target; } @@ -107,7 +109,8 @@ public HealthConditions fillOrBuildEntity(@NotNull HealthConditionsDto source, H target.setExposedToMosquitoBorneViruses(source.getExposedToMosquitoBorneViruses()); target.setVaccinatedAgainstMosquitoBorneViruses(source.getVaccinatedAgainstMosquitoBorneViruses()); target.setImmunodeficiencyOtherThanHivText(source.getImmunodeficiencyOtherThanHivText()); - + target.setMalaria(source.getMalaria()); + target.setMalariaInfectedYear(source.getMalariaInfectedYear()); return target; } } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfigurationFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfigurationFacadeEjb.java index 4affe5fff14..22995b13650 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfigurationFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfigurationFacadeEjb.java @@ -179,6 +179,10 @@ public List getIndexList( return getResultList(service.getEntityManager(), cq, first, max, this::toIndexDto); } + public DiseaseConfigurationDto getDiseaseConfiguration(Disease disease) { + return toDto(service.getDiseaseConfiguration(disease)); + } + private DiseaseConfigurationIndexDto toIndexDto(DiseaseConfiguration entity) { if (entity == null) { diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeaslesExportStrategy.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeaslesExportStrategy.java index b1a846524e2..7ed30caab8e 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeaslesExportStrategy.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeaslesExportStrategy.java @@ -253,12 +253,12 @@ private String buildVirusDetectionDataCte() { " AND pt2.testtype IN ('PCR_RT_PCR', 'CULTURE', 'ISOLATION', 'DIRECT_FLUORESCENT_ANTIBODY', 'INDIRECT_FLUORESCENT_ANTIBODY', 'SEQUENCING', 'GENOTYPING') " + " ORDER BY pt2.testresultverified DESC, COALESCE(pt2.testdatetime, pt2.reportdate, pt2.creationdate) ASC " + " LIMIT 1) as virus_detection_result," + - " (SELECT COALESCE(pt3.typingid, pt3.genotyperesult) " + + " (SELECT COALESCE(pt3.typingid, pt3.genotype) " + " FROM samples s3 " + " JOIN pathogentest pt3 ON pt3.sample_id = s3.id " + " WHERE s3.associatedcase_id = c.id " + " AND s3.deleted = false " + - " AND (pt3.typingid IS NOT NULL OR pt3.genotyperesult IS NOT NULL) " + + " AND (pt3.typingid IS NOT NULL OR pt3.genotype IS NOT NULL) " + " ORDER BY COALESCE(pt3.testdatetime, pt3.reportdate, pt3.creationdate) ASC " + " LIMIT 1) as genotype_raw " + " FROM filtered_cases c " + diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalmessage/labmessage/TestReport.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/externalmessage/labmessage/TestReport.java index 9b230b2f889..e1394bbf0eb 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalmessage/labmessage/TestReport.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/externalmessage/labmessage/TestReport.java @@ -36,7 +36,7 @@ import com.vladmihalcea.hibernate.type.array.ListArrayType; -import de.symeda.sormas.api.sample.GenoTypeResult; +import de.symeda.sormas.api.sample.GenoType; import de.symeda.sormas.api.sample.PCRTestSpecification; import de.symeda.sormas.api.sample.PathogenSpecie; import de.symeda.sormas.api.sample.PathogenStrainCallStatus; @@ -124,7 +124,7 @@ public class TestReport extends AbstractDomainObject { private String prescriberPostalCode; private String prescriberCity; private Country prescriberCountry; - private GenoTypeResult genoTypeResult; + private GenoType genoType; private RsvSubtype rsvSubtype; private PathogenSpecie specie; @@ -486,12 +486,12 @@ public void setPrescriberCountry(Country prescriberCountry) { } @Enumerated(EnumType.STRING) - public GenoTypeResult getGenoTypeResult() { - return genoTypeResult; + public GenoType getGenoType() { + return genoType; } - public void setGenoTypeResult(GenoTypeResult genoTypeResult) { - this.genoTypeResult = genoTypeResult; + public void setGenoType(GenoType genoType) { + this.genoType = genoType; } @Enumerated(EnumType.STRING) diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalmessage/labmessage/TestReportFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/externalmessage/labmessage/TestReportFacadeEjb.java index 6a70c2014fc..350e837c1fa 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/externalmessage/labmessage/TestReportFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/externalmessage/labmessage/TestReportFacadeEjb.java @@ -104,7 +104,7 @@ public static TestReportDto toDto(TestReport source) { target.setPrescriberPostalCode(source.getPrescriberPostalCode()); target.setPrescriberCity(source.getPrescriberCity()); target.setPrescriberCountry(CountryFacadeEjb.toReferenceDto(source.getPrescriberCountry())); - target.setGenoTypeResult(source.getGenoTypeResult()); + target.setGenoType(source.getGenoType()); target.setRsvSubtype(source.getRsvSubtype()); target.setSpecie(source.getSpecie()); target.setTubeNil(source.getTubeNil()); @@ -199,7 +199,7 @@ public TestReport fillOrBuildEntity(@NotNull TestReportDto source, @NotNull Samp target.setPrescriberPostalCode(source.getPrescriberPostalCode()); target.setPrescriberCity(source.getPrescriberCity()); target.setPrescriberCountry(countryService.getByReferenceDto(source.getPrescriberCountry())); - target.setGenoTypeResult(source.getGenoTypeResult()); + target.setGenoType(source.getGenoType()); target.setRsvSubtype(source.getRsvSubtype()); target.setSpecie(source.getSpecie()); target.setTubeNil(source.getTubeNil()); diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/sample/PathogenTest.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/sample/PathogenTest.java index c6184eab1e5..2ff2a21f897 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/sample/PathogenTest.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/sample/PathogenTest.java @@ -40,7 +40,7 @@ import de.symeda.sormas.api.disease.DiseaseVariantConverter; import de.symeda.sormas.api.disease.PathogenConverter; import de.symeda.sormas.api.environment.environmentsample.Pathogen; -import de.symeda.sormas.api.sample.GenoTypeResult; +import de.symeda.sormas.api.sample.GenoType; import de.symeda.sormas.api.sample.PCRTestSpecification; import de.symeda.sormas.api.sample.PathogenSpecie; import de.symeda.sormas.api.sample.PathogenStrainCallStatus; @@ -50,6 +50,7 @@ import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.api.sample.RsvSubtype; import de.symeda.sormas.api.sample.SeroGroupSpecification; +import de.symeda.sormas.api.sample.Serotype; import de.symeda.sormas.api.sample.SerotypingMethod; import de.symeda.sormas.api.utils.YesNoUnknown; import de.symeda.sormas.backend.common.DeletableAdo; @@ -133,7 +134,7 @@ public class PathogenTest extends DeletableAdo { private String testResultText; private Boolean testResultVerified; private boolean fourFoldIncreaseAntibodyTiter; - private String serotype; + private Float cqValue; private Float ctValueE; private Float ctValueN; @@ -157,15 +158,19 @@ public class PathogenTest extends DeletableAdo { private YesNoUnknown rifampicinResistant; private YesNoUnknown isoniazidResistant; private PathogenSpecie specie; + private String specieText; private String patternProfile; private PathogenStrainCallStatus strainCallStatus; private PathogenTestScale testScale; private DrugSusceptibility drugSusceptibility; private String miruPatternProfile; + private Serotype serotype; + // serotypeText to capture the custom values and to display the existing string values. + private String serotypeText; private SerotypingMethod seroTypingMethod; private String seroTypingMethodText; - private GenoTypeResult genoTypeResult; - private String genoTypeResultText; + private GenoType genoType; + private String genoTypeText; private SeroGroupSpecification seroGroupSpecification; private String seroGroupSpecificationText; private RsvSubtype rsvSubtype; @@ -177,6 +182,11 @@ public class PathogenTest extends DeletableAdo { private Boolean tubeAgTb2GT10; private Float tubeMitogene; private Boolean tubeMitogeneGT10; + // Dengue and Malaria changes + private String antibodyTitre; + private Boolean performedByReferenceLaboratory; + private Boolean retestRequested; + private String resultDetails; @ManyToOne(fetch = FetchType.LAZY) public Sample getSample() { @@ -384,15 +394,24 @@ public void setFourFoldIncreaseAntibodyTiter(boolean fourFoldIncreaseAntibodyTit this.fourFoldIncreaseAntibodyTiter = fourFoldIncreaseAntibodyTiter; } - @Column(length = CHARACTER_LIMIT_DEFAULT) - public String getSerotype() { + @Enumerated(EnumType.STRING) + public Serotype getSerotype() { return serotype; } - public void setSerotype(String serotype) { + public void setSerotype(Serotype serotype) { this.serotype = serotype; } + @Column + public String getSerotypeText() { + return serotypeText; + } + + public void setSerotypeText(String serotypeText) { + this.serotypeText = serotypeText; + } + @Column public Float getCqValue() { return cqValue; @@ -649,20 +668,20 @@ public void setSeroTypingMethod(SerotypingMethod seroTypingMethod) { } @Enumerated(EnumType.STRING) - public GenoTypeResult getGenoTypeResult() { - return genoTypeResult; + public GenoType getGenoType() { + return genoType; } - public void setGenoTypeResult(GenoTypeResult genoTypeResult) { - this.genoTypeResult = genoTypeResult; + public void setGenoType(GenoType genoType) { + this.genoType = genoType; } - public String getGenoTypeResultText() { - return genoTypeResultText; + public String getGenoTypeText() { + return genoTypeText; } - public void setGenoTypeResultText(String genoTypeResultText) { - this.genoTypeResultText = genoTypeResultText; + public void setGenoTypeText(String genoTypeText) { + this.genoTypeText = genoTypeText; } public String getSeroTypingMethodText() { @@ -763,4 +782,44 @@ public void setTubeMitogeneGT10(Boolean tubeMitogeneGT10) { this.tubeMitogeneGT10 = tubeMitogeneGT10; } + public String getAntibodyTitre() { + return antibodyTitre; + } + + public void setAntibodyTitre(String antibodyTitre) { + this.antibodyTitre = antibodyTitre; + } + + public Boolean getPerformedByReferenceLaboratory() { + return performedByReferenceLaboratory; + } + + public void setPerformedByReferenceLaboratory(Boolean performedByReferenceLaboratory) { + this.performedByReferenceLaboratory = performedByReferenceLaboratory; + } + + public Boolean getRetestRequested() { + return retestRequested; + } + + public void setRetestRequested(Boolean retestRequested) { + this.retestRequested = retestRequested; + } + + public String getResultDetails() { + return resultDetails; + } + + public void setResultDetails(String resultDetails) { + this.resultDetails = resultDetails; + } + + public String getSpecieText() { + return specieText; + } + + public void setSpecieText(String specieText) { + this.specieText = specieText; + } + } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/sample/PathogenTestFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/sample/PathogenTestFacadeEjb.java index a3a8257ae30..25eee24200c 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/sample/PathogenTestFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/sample/PathogenTestFacadeEjb.java @@ -37,6 +37,7 @@ import javax.validation.Valid; import javax.validation.constraints.NotNull; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,6 +53,7 @@ import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.api.sample.SampleReferenceDto; +import de.symeda.sormas.api.sample.Serotype; import de.symeda.sormas.api.therapy.DrugSusceptibilityType; import de.symeda.sormas.api.user.NotificationType; import de.symeda.sormas.api.user.UserRight; @@ -289,7 +291,13 @@ public static PathogenTestDto toDto(PathogenTest source) { target.setTestResultText(source.getTestResultText()); target.setTestResultVerified(source.getTestResultVerified()); target.setFourFoldIncreaseAntibodyTiter(source.isFourFoldIncreaseAntibodyTiter()); - target.setSerotype(source.getSerotype()); + // If serotypetext is not null, then serotype is Other, otherwise it'd be normal serotype + if (StringUtils.isNotBlank(source.getSerotypeText())) { + target.setSerotype(Serotype.fromString(source.getSerotypeText() == null ? null : source.getSerotypeText().toString())); + } else { + target.setSerotype(source.getSerotype()); + } + target.setSerotypeText(source.getSerotypeText()); target.setCqValue(source.getCqValue()); target.setCtValueE(source.getCtValueE()); target.setCtValueN(source.getCtValueN()); @@ -319,6 +327,7 @@ public static PathogenTestDto toDto(PathogenTest source) { target.setRifampicinResistant(source.getRifampicinResistant()); target.setIsoniazidResistant(source.getIsoniazidResistant()); target.setSpecie(source.getSpecie()); + target.setSpecieText(source.getSpecieText()); target.setPatternProfile(source.getPatternProfile()); target.setStrainCallStatus(source.getStrainCallStatus()); target.setTestScale(source.getTestScale()); @@ -328,9 +337,13 @@ public static PathogenTestDto toDto(PathogenTest source) { target.setSeroTypingMethodText(source.getSeroTypingMethodText()); target.setSeroGroupSpecification(source.getSeroGroupSpecification()); target.setSeroGroupSpecificationText(source.getSeroGroupSpecificationText()); - target.setGenoTypeResult(source.getGenoTypeResult()); - target.setGenoTypeResultText(source.getGenoTypeResultText()); + target.setGenoType(source.getGenoType()); + target.setGenoTypeText(source.getGenoTypeText()); target.setRsvSubtype(source.getRsvSubtype()); + target.setAntibodyTitre(source.getAntibodyTitre()); + target.setPerformedByReferenceLaboratory(source.getPerformedByReferenceLaboratory()); + target.setRetestRequested(source.getRetestRequested()); + target.setResultDetails(source.getResultDetails()); // IGRA tube values target.setTubeNil(source.getTubeNil()); @@ -598,7 +611,8 @@ public PathogenTest fillOrBuildEntity(@NotNull PathogenTestDto source, PathogenT target.setTestResultText(source.getTestResultText()); target.setTestResultVerified(source.getTestResultVerified()); target.setFourFoldIncreaseAntibodyTiter(source.isFourFoldIncreaseAntibodyTiter()); - target.setSerotype(source.getSerotype()); + target.setSerotype(Serotype.fromString(source.getSerotype() == null ? null : source.getSerotype().toString())); + target.setSerotypeText(source.getSerotypeText()); target.setCqValue(source.getCqValue()); target.setCtValueE(source.getCtValueE()); target.setCtValueN(source.getCtValueN()); @@ -628,6 +642,7 @@ public PathogenTest fillOrBuildEntity(@NotNull PathogenTestDto source, PathogenT target.setRifampicinResistant(source.getRifampicinResistant()); target.setIsoniazidResistant(source.getIsoniazidResistant()); target.setSpecie(source.getSpecie()); + target.setSpecieText(source.getSpecieText()); target.setPatternProfile(source.getPatternProfile()); target.setStrainCallStatus(source.getStrainCallStatus()); target.setTestScale(source.getTestScale()); @@ -640,9 +655,13 @@ public PathogenTest fillOrBuildEntity(@NotNull PathogenTestDto source, PathogenT target.setSeroTypingMethodText(source.getSeroTypingMethodText()); target.setSeroGroupSpecification(source.getSeroGroupSpecification()); target.setSeroGroupSpecificationText(source.getSeroGroupSpecificationText()); - target.setGenoTypeResult(source.getGenoTypeResult()); - target.setGenoTypeResultText(source.getGenoTypeResultText()); + target.setGenoType(source.getGenoType()); + target.setGenoTypeText(source.getGenoTypeText()); target.setRsvSubtype(source.getRsvSubtype()); + target.setAntibodyTitre(source.getAntibodyTitre()); + target.setPerformedByReferenceLaboratory(source.getPerformedByReferenceLaboratory()); + target.setRetestRequested(source.getRetestRequested()); + target.setResultDetails(source.getResultDetails()); // IGRA tube values if (target.getTestType() != PathogenTestType.IGRA) { diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 4affe0d26ee..b106a76e3ca 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15607,4 +15607,172 @@ ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; INSERT INTO schema_version (version_number, comment) VALUES (615, '#13828 - Add history tables for customizable fields'); +-- 23-03-2026 new fields related to Malaria and Dengue samples and pathogenform. +ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS serotypetext varchar(255); +UPDATE pathogentest SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; +ALTER TABLE pathogentest rename column genotyperesult to genotype; +ALTER TABLE pathogentest rename column genotyperesulttext to genotypetext; +ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS antibodyTitre varchar(255); +ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS performedByReferenceLaboratory boolean; +ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS retestRequested boolean default false; +ALTER TABLE pathogentest add column IF NOT EXISTS resultdetails varchar(255); +ALTER TABLE pathogentest add column IF NOT EXISTS specietext varchar(255); +ALTER TABLE healthconditions ADD COLUMN IF NOT EXISTS malaria varchar(255); +ALTER TABLE healthconditions ADD COLUMN IF NOT EXISTS malariainfectedyear integer ; + + +ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS serotypetext varchar(255); +UPDATE pathogentest_history SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; +ALTER TABLE pathogentest_history rename column genotyperesult to genotype; +ALTER TABLE pathogentest_history rename column genotyperesulttext to genotypetext; +ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS antibodyTitre varchar(255); +ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS performedByReferenceLaboratory boolean; +ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS retestRequested boolean default false; +ALTER TABLE pathogentest_history add column IF NOT EXISTS resultdetails varchar(255); +ALTER TABLE pathogentest_history add column IF NOT EXISTS specietext varchar(255); +ALTER TABLE healthconditions_history ADD COLUMN IF NOT EXISTS malaria varchar(255); +ALTER TABLE healthconditions_history ADD COLUMN IF NOT EXISTS malariainfectedyear integer ; + +INSERT INTO schema_version (version_number, comment) VALUES (615, '#13801, #13814 - Malaria and Dengue sampel changes'); + +-- #13828 - Customizable Fields + +CREATE TABLE IF NOT EXISTS customizablefieldmetadata ( + id bigint NOT NULL, + uuid character varying(36) NOT NULL UNIQUE, + changeDate timestamp without time zone NOT NULL DEFAULT NOW(), + creationDate timestamp without time zone NOT NULL DEFAULT NOW(), + deleted boolean NOT NULL DEFAULT false, + deletionreason varchar(255), + otherdeletionreason text, + + -- Core field metadata + name character varying(512) NOT NULL, + description text, + fieldType character varying(50) NOT NULL, -- TEXT, DATE, COMBOBOX, YES_NO_UNKNOWN, CHECKBOX_LIST, RADIO_BUTTON_LIST + defaultValue text, + + -- Constraints + mandatory boolean NOT NULL DEFAULT false, + readOnly boolean NOT NULL DEFAULT false, + active boolean NOT NULL DEFAULT true, + + -- Context and UI placement + contextClass character varying(256) NOT NULL, + uiGroup character varying(256), + uiLinePosition integer, + uiLineWeight float4, + + -- Complex JSON-serialized properties + visibilityRestrictions jsonb, + customProperties jsonb, + translations jsonb, + + change_user_id bigint, + sys_period tstzrange NOT NULL, + + PRIMARY KEY (id), + UNIQUE(name, contextClass) +); + +CREATE INDEX idx_customizablefieldmetadata_uuid + ON customizablefieldmetadata (uuid); +CREATE INDEX idx_customizablefieldmetadata_contextClass + ON customizablefieldmetadata (contextClass); +CREATE INDEX idx_customizablefieldmetadata_uiGroup + ON customizablefieldmetadata (uiGroup); +CREATE INDEX idx_customizablefieldmetadata_active + ON customizablefieldmetadata (active); +CREATE INDEX idx_customizablefieldmetadata_deleted + ON customizablefieldmetadata (deleted); + +ALTER TABLE customizablefieldmetadata OWNER TO sormas_user; +ALTER TABLE customizablefieldmetadata ADD CONSTRAINT fk_change_user_id FOREIGN KEY (change_user_id) REFERENCES users (id); +ALTER INDEX idx_customizablefieldmetadata_uuid OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldmetadata_contextClass OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldmetadata_uiGroup OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldmetadata_active OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldmetadata_deleted OWNER TO sormas_user; + +-- CustomizableFieldMetadata history tables +CREATE TABLE customizablefieldmetadata_history (LIKE customizablefieldmetadata); +CREATE TRIGGER versioning_trigger +BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldmetadata +FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldmetadata_history', true); +ALTER TABLE customizablefieldmetadata_history OWNER TO sormas_user; + +DROP TRIGGER IF EXISTS delete_history_trigger_customizablefieldmetadata ON customizablefieldmetadata; +CREATE TRIGGER delete_history_trigger_customizablefieldmetadata + AFTER DELETE ON customizablefieldmetadata + FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldmetadata_history', 'id'); + +-- Create CustomizableFieldValue table +CREATE TABLE IF NOT EXISTS customizablefieldvalue ( + id bigint NOT NULL, + uuid character varying(36) NOT NULL UNIQUE, + changeDate timestamp(3) NOT NULL DEFAULT NOW(), + creationDate timestamp(3) NOT NULL DEFAULT NOW(), + deleted boolean NOT NULL DEFAULT false, + deletionreason varchar(255), + otherdeletionreason text, + + -- Fkey to metadata + customizablefieldmetadata_id bigint NOT NULL REFERENCES customizablefieldmetadata(id) ON DELETE CASCADE, + + -- Generic entity reference + entityUuid character varying(36) NOT NULL, + contextClass character varying(256) NOT NULL, + + -- Value storage (text for all types, type conversion happens in service) + value text, + + change_user_id bigint, + sys_period tstzrange NOT NULL, + + PRIMARY KEY (id), + UNIQUE(customizablefieldmetadata_id, entityUuid, contextClass) +); + +CREATE INDEX idx_customizablefieldvalue_uuid + ON customizablefieldvalue (uuid); +CREATE INDEX idx_customizablefieldvalue_entityUuid + ON customizablefieldvalue (entityUuid); +CREATE INDEX idx_customizablefieldvalue_contextEntity + ON customizablefieldvalue (contextClass, entityUuid); +CREATE INDEX idx_customizablefieldvalue_fieldMetadata + ON customizablefieldvalue (customizablefieldmetadata_id); +CREATE INDEX idx_customizablefieldvalue_deleted + ON customizablefieldvalue (deleted); + +ALTER TABLE customizablefieldvalue OWNER TO sormas_user; + +ALTER TABLE customizablefieldvalue + ADD CONSTRAINT fk_customizablefieldvalue_metadata + FOREIGN KEY (customizablefieldmetadata_id) + REFERENCES customizablefieldmetadata(id) + ON DELETE CASCADE; + +ALTER TABLE customizablefieldvalue ADD CONSTRAINT fk_change_user_id FOREIGN KEY (change_user_id) REFERENCES users (id); + +ALTER INDEX idx_customizablefieldvalue_uuid OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldvalue_entityUuid OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldvalue_contextEntity OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldvalue_fieldMetadata OWNER TO sormas_user; +ALTER INDEX idx_customizablefieldvalue_deleted OWNER TO sormas_user; + +-- CustomizableFieldValue history tables +CREATE TABLE customizablefieldvalue_history (LIKE customizablefieldvalue); +CREATE TRIGGER versioning_trigger +BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldvalue +FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldvalue_history', true); + +DROP TRIGGER IF EXISTS delete_history_trigger_customizablefieldvalue ON customizablefieldvalue; +CREATE TRIGGER delete_history_trigger_customizablefieldvalue + AFTER DELETE ON customizablefieldvalue + FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldvalue_history', 'id'); + +ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; + +INSERT INTO schema_version (version_number, comment) VALUES (616, '#13828 - Add history tables for customizable fields'); + -- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java index eebd39197c5..9cc96af01d1 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java @@ -78,9 +78,11 @@ import de.symeda.sormas.api.contact.ContactStatus; import de.symeda.sormas.api.contact.SimilarContactDto; import de.symeda.sormas.api.deletionconfiguration.DeletionInfoDto; +import de.symeda.sormas.api.disease.DiseaseConfigurationDto; import de.symeda.sormas.api.docgeneneration.DocumentWorkflow; import de.symeda.sormas.api.docgeneneration.RootEntityType; import de.symeda.sormas.api.document.DocumentRelatedEntityType; +import de.symeda.sormas.api.epidata.EpiDataDto; import de.symeda.sormas.api.event.EventDto; import de.symeda.sormas.api.event.EventParticipantCriteria; import de.symeda.sormas.api.event.EventParticipantDto; @@ -1392,6 +1394,11 @@ public CommitDiscardWrapperComponent getEpiDataComponent( boolean isEditAllowed) { CaseDataDto caze = findCase(caseUuid); + EpiDataDto epiDataDto = caze.getEpiData(); + // Exposure start date and end date should be calculated based on symptom onsetDate and incubation start periods + Date symptomOnsetDate = caze.getSymptoms().getOnsetDate(); + + includeExposureDates(symptomOnsetDate, epiDataDto, caze.getDisease()); EpiDataForm epiDataForm = new EpiDataForm( caze.getDisease(), CaseDataDto.class, @@ -1399,7 +1406,7 @@ public CommitDiscardWrapperComponent getEpiDataComponent( caze.isInJurisdiction(), sourceContactsToggleCallback, isEditAllowed); - epiDataForm.setValue(caze.getEpiData()); + epiDataForm.setValue(epiDataDto); final CommitDiscardWrapperComponent editView = new CommitDiscardWrapperComponent(epiDataForm, epiDataForm.getFieldGroup()); @@ -1420,6 +1427,30 @@ public CommitDiscardWrapperComponent getEpiDataComponent( return editView; } + // include the exposure dates. + private void includeExposureDates(Date symptomOnsetDate, EpiDataDto epiDataDto, Disease disease) { + // if symptomOnsetDate is null, return; + if (symptomOnsetDate == null) { + return; + } + DiseaseConfigurationDto diseaseConfigurationDto = FacadeProvider.getDiseaseConfigurationFacade().getDiseaseConfiguration(disease); + if (diseaseConfigurationDto == null) { + return; + } + if (!diseaseConfigurationDto.getIncubationPeriodEnabled()) { + return; + } + if (diseaseConfigurationDto.getMaxIncubationPeriod() == 0 || diseaseConfigurationDto.getMaxIncubationPeriod() == null) { + return; + } + if (diseaseConfigurationDto.getMinIncubationPeriod() == null) { + return; + } + + epiDataDto.setExposureStartDate(DateHelper.subtractDays(symptomOnsetDate, diseaseConfigurationDto.getMaxIncubationPeriod())); + epiDataDto.setExposureEndDate(DateHelper.subtractDays(symptomOnsetDate, diseaseConfigurationDto.getMinIncubationPeriod())); + } + public CommitDiscardWrapperComponent getTherapyEditComponent(final String caseUuid, boolean isEditAllowed) { CaseDataDto caseDataDto = findCase(caseUuid); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java index a82901000e7..953fd9b9c9f 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java @@ -1113,8 +1113,7 @@ protected void addFields() { FieldVisibilityCheckers.withDisease(disease) .add(new CountryFieldVisibilityChecker(FacadeProvider.getConfigFacade().getCountryLocale())), UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale()), - new PersonReferenceDto(person.getUuid()))) - .setCaption(null); + new PersonReferenceDto(person.getUuid()))).setCaption(null); //diagnosis criteria if ((FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) && disease == Disease.TUBERCULOSIS) { @@ -1545,7 +1544,7 @@ public String getFormattedHtmlMessage() { private void getManualCaseDefinition() { // If a disease has caseDefinitionText, it should display; otherwise criteria will display as it is. String caseDefinitionText = FacadeProvider.getDiseaseConfigurationFacade().getCaseDefinitionText(disease); - if (caseDefinitionText == null) { + if (StringUtils.isBlank(caseDefinitionText)) { return; } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseSymptomSideViewComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseSymptomSideViewComponent.java index 7fee9c0fed9..44f28b3670e 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseSymptomSideViewComponent.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseSymptomSideViewComponent.java @@ -19,17 +19,22 @@ import java.util.Map; import java.util.TreeMap; +import org.apache.commons.lang3.StringUtils; + import com.vaadin.ui.Component; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; +import com.vaadin.v7.ui.Field; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.i18n.Captions; import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.symptoms.SymptomState; import de.symeda.sormas.api.symptoms.SymptomsDto; import de.symeda.sormas.api.utils.AnnotationFieldHelper; import de.symeda.sormas.ui.utils.CssStyles; +import de.symeda.sormas.ui.utils.FieldHelper; import de.symeda.sormas.ui.utils.components.sidecomponent.SideComponent; /** @@ -59,28 +64,45 @@ public CaseSymptomSideViewComponent(Disease disease) { /** * Toggle the symptom based on the flag * - * @param symptomName - * @param validComplication + * @param sourceField */ - public void toggleComplicationSymptom(String symptomName, boolean validComplication) { - if (symptomName == null) { + public void toggleComplicationSymptom(Field sourceField) { + + if (sourceField == null) { return; } - // if the selected symptom is NO or UNKNOWN and componentMap has that symptom, remove it from the layout and map - if (!validComplication && componentMap.containsKey(symptomName)) { - layout.removeComponent(componentMap.get(symptomName)); - componentMap.entrySet().removeIf(entry -> entry.getKey().equals(symptomName)); + + Object sourceFieldObj = FieldHelper.getNullableSourceFieldValue(sourceField); + + // if the source field value is NO, UNKNOWN or deselect Yes and the value will be empty (in case of other complications symptoms data), + // and a map has it, remove it from the layout and map + if ((sourceFieldObj == null + || sourceFieldObj == SymptomState.NO + || sourceFieldObj == SymptomState.UNKNOWN + || StringUtils.isBlank(sourceFieldObj.toString())) && componentMap.containsKey(sourceField.getId())) { + layout.removeComponent(componentMap.get(sourceField.getId())); + componentMap.entrySet().removeIf(entry -> entry.getKey().equals(sourceField.getId())); + return; } - // if the selected symptom is NO or UNKNOWN - if (!validComplication) { + // This is to set the symptom name as the caption. + + // if the sourceField value YES and map does not have it, and it's a complicated symptom, + // add it to the layout and map + if ((sourceFieldObj == SymptomState.YES) + && !componentMap.containsKey(sourceField.getId()) + && complicatedSymptoms.contains(sourceField.getId())) { + Label label = new Label(I18nProperties.getCaption(Captions.Symptoms + "." + sourceField.getId())); + componentMap.put(sourceField.getId(), label); return; } - // find the symptom in the complicated symptoms list, if it exists, update the map - if (validComplication) { - complicatedSymptoms.stream().filter(symptom -> symptom.equals(symptomName)).findFirst().ifPresent(symptom -> { - Label label = new Label(I18nProperties.getCaption(Captions.Symptoms + "." + symptomName)); - componentMap.put(symptomName, label); - }); + // if the source field value is String type, and it's value is not empty, and a map does not have that symptom, and its a complicated symptom, add it to the layout and map + // This is to set the entered value as a caption. + if (sourceFieldObj instanceof String + && StringUtils.isNotBlank((String) sourceFieldObj) + && !componentMap.containsKey(sourceField.getId()) + && complicatedSymptoms.contains(sourceField.getId())) { + Label label = new Label(sourceField.getValue().toString()); + componentMap.put(sourceField.getId(), label); } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/clinicalcourse/HealthConditionsForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/clinicalcourse/HealthConditionsForm.java index 3f6f01c9a51..23d97141914 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/clinicalcourse/HealthConditionsForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/clinicalcourse/HealthConditionsForm.java @@ -21,8 +21,10 @@ import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.HIV_ART; import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.I18N_PREFIX; import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.IMMUNODEFICIENCY_INCLUDING_HIV; -import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.IMMUNODEFICIENCY_OTHER_THAN_HIV_TEXT; import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.IMMUNODEFICIENCY_OTHER_THAN_HIV; +import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.IMMUNODEFICIENCY_OTHER_THAN_HIV_TEXT; +import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.MALARIA; +import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.MALARIA_INFECTED_YEAR; import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.MALIGNANCY_CHEMOTHERAPY; import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.OBESITY; import static de.symeda.sormas.api.clinicalcourse.HealthConditionsDto.OTHER_CONDITIONS; @@ -87,6 +89,7 @@ public class HealthConditionsForm extends AbstractEditForm //@formatter:off public static final String TB_INFECTION_YEAR_LAYOUT = fluidRowLocs(6, "LBL_TUBERCULOSIS_INFECTION_YEAR", 6, TUBERCULOSIS_INFECTION_YEAR); + public static final String MALARIA_YEAR_LAYOUT = fluidRowLocs(7, "LBL_MALARIA_INFECTED_YEAR", 5, MALARIA_INFECTED_YEAR); public static final String TBA_LAYOUT = fluidRowLocs(6, "LBL_COMPLIANCE_WITH_TREATMENT", 6, COMPLIANCE_WITH_TREATMENT); public static final String IMMUNODEFICIENCY_LAYOUT = fluidRowLocs(7, "LBL_IMMUNODEFICIENCY", 5, IMMUNODEFICIENCY_OTHER_THAN_HIV_TEXT); public static final String EXPOSED_TO_MOSQUITO_BORNE_VIRUSES_LAYOUT = fluidRowLocs(7, "LBL_EXPOSED_TO_BORNE_VIRUSES", 5, EXPOSED_TO_MOSQUITO_BORNE_VIRUSES_TEXT); @@ -96,7 +99,7 @@ public class HealthConditionsForm extends AbstractEditForm fluidColumn(6, 0, locs( TUBERCULOSIS, PREVIOUS_TUBERCULOSIS_TREATMENT, ASPLENIA, HEPATITIS, DIABETES, IMMUNODEFICIENCY_OTHER_THAN_HIV,"IMMUNODEFICIENCY_INCLUDING_HIV_LAYOUT", HIV, HIV_ART, CONGENITAL_SYPHILIS, DOWN_SYNDROME, - CHRONIC_LIVER_DISEASE, MALIGNANCY_CHEMOTHERAPY, RECURRENT_BRONCHIOLITIS)), + CHRONIC_LIVER_DISEASE, MALIGNANCY_CHEMOTHERAPY, RECURRENT_BRONCHIOLITIS, MALARIA, "MALARIA_INFECTED_YEAR_LAYOUT")), fluidColumn(6, 0, locs( "TUBERCULOSIS_INFECTION_YEAR_LAYOUT","COMPLIANCE_WITH_TREATMENT_LAYOUT",CHRONIC_HEART_FAILURE, CHRONIC_PULMONARY_DISEASE, CHRONIC_KIDNEY_DISEASE, CHRONIC_NEUROLOGIC_CONDITION, CARDIOVASCULAR_DISEASE_INCLUDING_HYPERTENSION, @@ -130,7 +133,8 @@ public class HealthConditionsForm extends AbstractEditForm IMMUNODEFICIENCY_INCLUDING_HIV, EXPOSED_TO_MOSQUITO_BORNE_VIRUSES, VACCINATED_AGAINST_MOSQUITO_BORNE_VIRUSES, - RECURRENT_BRONCHIOLITIS); + RECURRENT_BRONCHIOLITIS, + MALARIA); private boolean vaccinationListener = false; @@ -303,6 +307,27 @@ protected void addFields() { initializeVisibilitiesAndAllowedVisibilities(); initializeAccessAndAllowedAccesses(); + if (isVisibleAllowed(MALARIA)) { + // Malaria infected year visibility. + CustomLayout malariaInfectedYearLayout = new CustomLayout(); + malariaInfectedYearLayout.setTemplateContents(MALARIA_YEAR_LAYOUT); + + // infected year label + Label lblInfectedYear = new Label(I18nProperties.getCaption(Captions.HealthConditions_malariaInfectedYear)); + malariaInfectedYearLayout.addComponent(lblInfectedYear, "LBL_MALARIA_INFECTED_YEAR"); + getContent().addComponent(malariaInfectedYearLayout, "MALARIA_INFECTED_YEAR_LAYOUT"); + + // infection year combobox + ComboBox malInfectedYearCB = addField(malariaInfectedYearLayout, MALARIA_INFECTED_YEAR, ComboBox.class); + malInfectedYearCB.setInputPrompt(I18nProperties.getString(Strings.year)); + malInfectedYearCB.setNullSelectionAllowed(true); + malInfectedYearCB.setCaption(null); + malInfectedYearCB.addItems(DateHelper.getYearsToNow()); + malInfectedYearCB.setItemCaptionMode(AbstractSelect.ItemCaptionMode.ID_TOSTRING); + malariaInfectedYearLayout.addComponent(malInfectedYearCB, MALARIA_INFECTED_YEAR); + + FieldHelper.setVisibleWhen(getFieldGroup(), Arrays.asList(MALARIA_INFECTED_YEAR), MALARIA, Arrays.asList(YesNoUnknown.YES), true); + } if (UiUtil.permitted(UserRight.SEE_SENSITIVE_DATA_IN_JURISDICTION, UserRight.SEE_SENSITIVE_DATA_OUTSIDE_JURISDICTION)) { Field other = getField(OTHER_CONDITIONS); if (other != null) { diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java index ba7fdcc0360..957ccd20c98 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java @@ -36,6 +36,7 @@ import com.vaadin.shared.ui.ContentMode; import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.DateField; import com.vaadin.v7.ui.Field; import com.vaadin.v7.ui.TextField; @@ -80,7 +81,8 @@ public class EpiDataForm extends AbstractEditForm { //@formatter:off private static final String MAIN_HTML_LAYOUT = loc(LOC_EXPOSURE_INVESTIGATION_HEADING) + - fluidRowLocs(6,EpiDataDto.CASE_IMPORTED_STATUS,6,"") + + fluidRowLocs(6, EpiDataDto.EXPOSURE_START_DATE, 6, EpiDataDto.EXPOSURE_END_DATE)+ + fluidRowLocs(6,EpiDataDto.CASE_IMPORTED_STATUS,6,"") + loc(LOC_EXP_PERIOD_HEADING) + loc(EpiDataDto.EXPOSURE_DETAILS_KNOWN) + loc(EpiDataDto.EXPOSURES) + @@ -176,6 +178,12 @@ protected void addFields() { addField(EpiDataDto.MODE_OF_TRANSMISSION_TYPE); addField(EpiDataDto.INFECTION_SOURCE); addField(EpiDataDto.INFECTION_SOURCE_TEXT); + DateField exposureStartDate = addField(EpiDataDto.EXPOSURE_START_DATE); + exposureStartDate.setCaption(I18nProperties.getString(Strings.exposureStartDate)); + exposureStartDate.setEnabled(false); + DateField exposureEndDate = addField(EpiDataDto.EXPOSURE_END_DATE); + exposureEndDate.setCaption(I18nProperties.getString(Strings.exposureEndDate)); + exposureEndDate.setEnabled(false); addField(EpiDataDto.IMPORTED_CASE, NullableOptionGroup.class); List countries = FacadeProvider.getCountryFacade().getAllActiveAsReference(); ComboBox country = addInfrastructureField(EpiDataDto.COUNTRY); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index 7ac59a0bdb4..4d49782fff5 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -33,6 +33,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.Consumer; import org.apache.commons.collections4.CollectionUtils; @@ -62,7 +63,8 @@ import de.symeda.sormas.api.i18n.Validations; import de.symeda.sormas.api.infrastructure.facility.FacilityDto; import de.symeda.sormas.api.infrastructure.facility.FacilityReferenceDto; -import de.symeda.sormas.api.sample.GenoTypeResult; +import de.symeda.sormas.api.sample.GenoType; +import de.symeda.sormas.api.sample.PathogenSpecie; import de.symeda.sormas.api.sample.PathogenStrainCallStatus; import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestResultType; @@ -70,6 +72,7 @@ import de.symeda.sormas.api.sample.SampleDto; import de.symeda.sormas.api.sample.SamplePurpose; import de.symeda.sormas.api.sample.SeroGroupSpecification; +import de.symeda.sormas.api.sample.Serotype; import de.symeda.sormas.api.sample.SerotypingMethod; import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; @@ -106,18 +109,21 @@ public class PathogenTestForm extends AbstractEditForm { fluidRowLocs(PathogenTestDto.TESTED_PATHOGEN, PathogenTestDto.TESTED_PATHOGEN_DETAILS) + fluidRowLocs(PathogenTestDto.TYPING_ID, "") + fluidRowLocs(PathogenTestDto.TEST_DATE_TIME, PathogenTestDto.LAB) + - fluidRowLocs("", PathogenTestDto.LAB_DETAILS) + + fluidRowLocs(6, "",6, PathogenTestDto.LAB_DETAILS) + fluidRowLocs(6,PathogenTestDto.TEST_RESULT, 4, PathogenTestDto.TEST_RESULT_VERIFIED, 2,PathogenTestDto.PRELIMINARY) + + fluidRowLocs(6, PathogenTestDto.RESULT_DETAILS,3,PathogenTestDto.PERFORMED_BY_REFERENCE_LABORATORY,3, PathogenTestDto.RETEST_REQUESTED) + fluidRowLocs(PathogenTestDto.TESTED_DISEASE_VARIANT, PathogenTestDto.TESTED_DISEASE_VARIANT_DETAILS) + fluidRowLocs(PathogenTestDto.RIFAMPICIN_RESISTANT, PathogenTestDto.ISONIAZID_RESISTANT, "", "") + fluidRowLocs(PathogenTestDto.TEST_SCALE, "") + fluidRowLocs(PathogenTestDto.STRAIN_CALL_STATUS, "") + - fluidRowLocs(PathogenTestDto.SPECIE, "") + + fluidRowLocs(PathogenTestDto.SPECIE, PathogenTestDto.SPECIE_TEXT) + fluidRowLocs(PathogenTestDto.PATTERN_PROFILE, "") + fluidRowLocs(PathogenTestDto.DRUG_SUSCEPTIBILITY) + - fluidRowLocs(4,PathogenTestDto.SEROTYPE, 4,PathogenTestDto.SEROTYPING_METHOD, 4,PathogenTestDto.SERO_TYPING_METHOD_TEXT) + + fluidRowLocs(6,PathogenTestDto.SEROTYPE, 6,PathogenTestDto.SEROTYPE_TEXT) + + fluidRowLocs(6,PathogenTestDto.SEROTYPING_METHOD, 6,PathogenTestDto.SERO_TYPING_METHOD_TEXT) + fluidRowLocs(6,PathogenTestDto.SERO_GROUP_SPECIFICATION , 6, PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT) + - fluidRowLocs(4,PathogenTestDto.GENOTYPE_RESULT,6, PathogenTestDto.GENOTYPE_RESULT_TEXT) + + fluidRowLocs(6,PathogenTestDto.GENOTYPE,6, PathogenTestDto.GENOTYPE_TEXT) + + fluidRowLocs(6,PathogenTestDto.ANTIBODY_TITRE) + fluidRowLocs(PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER, "") + fluidRowLocs(PathogenTestDto.CQ_VALUE, "") + fluidRowLocs(PathogenTestDto.CT_VALUE_E, PathogenTestDto.CT_VALUE_N) + @@ -138,37 +144,36 @@ public class PathogenTestForm extends AbstractEditForm { fluidRowLocs(PathogenTestDto.OTHER_DELETION_REASON); //@formatter:on + //@formatter:off // map to decide the result type field value and enable/disable state public static final Map> RESULT_FIELD_DECISION_MAP = Collections.unmodifiableMap(new HashMap<>() { - { - put( - Disease.INVASIVE_MENINGOCOCCAL_INFECTION, - new ArrayList<>( - List.of( - PathogenTestType.SEROGROUPING, - PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, - PathogenTestType.SLIDE_AGGLUTINATION, - PathogenTestType.WHOLE_GENOME_SEQUENCING, - PathogenTestType.SEQUENCING, - PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY))); - put( - Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, - new ArrayList<>( - List.of( - PathogenTestType.SEROGROUPING, - PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, - PathogenTestType.SLIDE_AGGLUTINATION, - PathogenTestType.WHOLE_GENOME_SEQUENCING, - PathogenTestType.SEQUENCING, - PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY))); + put(Disease.INVASIVE_MENINGOCOCCAL_INFECTION, new ArrayList<>(List.of(PathogenTestType.SEROGROUPING, + PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, PathogenTestType.SLIDE_AGGLUTINATION, PathogenTestType.WHOLE_GENOME_SEQUENCING, + PathogenTestType.SEQUENCING, PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY))); + put(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, new ArrayList<>(List.of(PathogenTestType.SEROGROUPING, PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, + PathogenTestType.SLIDE_AGGLUTINATION, PathogenTestType.WHOLE_GENOME_SEQUENCING, PathogenTestType.SEQUENCING, PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY))); put(Disease.MEASLES, new ArrayList<>(List.of(PathogenTestType.GENOTYPING))); put(Disease.RESPIRATORY_SYNCYTIAL_VIRUS, new ArrayList<>(List.of(PathogenTestType.SEQUENCING, PathogenTestType.WHOLE_GENOME_SEQUENCING))); put(Disease.INFLUENZA, new ArrayList<>(List.of(PathogenTestType.ISOLATION))); put(Disease.CRYPTOSPORIDIOSIS, new ArrayList<>(List.of(PathogenTestType.GENOTYPING))); + put(Disease.DENGUE, new ArrayList<>(List.of(PathogenTestType.NAAT, PathogenTestType.NEUTRALIZING_ANTIBODIES, PathogenTestType.PCR_RT_PCR))); + put(Disease.MALARIA, new ArrayList<>(List.of( PathogenTestType.ANTIGEN_DETECTION, PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.RAPID_TEST, + PathogenTestType.PCR_RT_PCR, PathogenTestType.Q_PCR, PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, PathogenTestType.LAMP, + PathogenTestType.IFAT, PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, PathogenTestType.OTHER_SEROLOGICAL_TEST, PathogenTestType.OTHER_MOLECULAR_ASSAY))); } }); + // map to decide the serotype field value and enable/disable state + // Serotype should display, with @Herold code refactor, it should be removed from here. + public static final Map> SEROTYPE_VISIBILITY_MAP = Collections.unmodifiableMap(new HashMap<>() { + { + put(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Collections.unmodifiableList(Arrays.asList(PathogenTestType.WHOLE_GENOME_SEQUENCING, + PathogenTestType.SLIDE_AGGLUTINATION, PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, PathogenTestType.SEROGROUPING))); + put(Disease.DENGUE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.NAAT, PathogenTestType.PCR_RT_PCR, PathogenTestType.NEUTRALIZING_ANTIBODIES))); + } + }); + //@formatter:off public static final Map> RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { { @@ -193,7 +198,21 @@ public class PathogenTestForm extends AbstractEditForm { put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.BEIJINGGENOTYPING))); } }); + //@formatter:off + // this map is to decide the species field value and enable/disable state. + // this suppose to refactored with @Harold changes + public static final Map> SPECIE_VISIBILITY_MAP = Collections.unmodifiableMap(new HashMap<>() { + { + put(Disease.LATENT_TUBERCULOSIS, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SPOLIGOTYPING))); + put(Disease.TUBERCULOSIS, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SPOLIGOTYPING))); + put(Disease.MALARIA, Collections.unmodifiableList(Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.ANTIGEN_DETECTION, + PathogenTestType.RAPID_TEST, PathogenTestType.PCR_RT_PCR, PathogenTestType.Q_PCR, PathogenTestType.LAMP, PathogenTestType.IFAT, + PathogenTestType.OTHER_MOLECULAR_ASSAY, PathogenTestType.OTHER_SEROLOGICAL_TEST, PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY))); + } + }); + //@formatter:on public static final Map> SPECIE_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { { @@ -241,6 +260,7 @@ public class PathogenTestForm extends AbstractEditForm { private ComboBox seroGrpSepcCB; private TextField seroGrpSpecTxt; + private ComboBox seroTypeField; public PathogenTestForm( AbstractSampleForm sampleForm, @@ -302,6 +322,9 @@ private static void setCqValueVisibility( if (((testType == PathogenTestType.PCR_RT_PCR && testResultType == PathogenTestResultType.POSITIVE)) || testType == PathogenTestType.CQ_VALUE_DETECTION) { cqValueField.setVisible(true); + } else if (Disease.MALARIA == (Disease) diseaseField.getValue() && testType == PathogenTestType.Q_PCR) { + // CT value should be visible for Malaria, QPCR test. + cqValueField.setVisible(true); } else { cqValueField.setVisible(false); cqValueField.clear(); @@ -400,12 +423,12 @@ public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Co typingIdField.setValue(newFieldValue.getTypingId()); specieField.setValue(newFieldValue.getSpecie()); if (!genoTypingCB.isReadOnly()) { - genoTypingCB.setValue(newFieldValue.getGenoTypeResult()); + genoTypingCB.setValue(newFieldValue.getGenoType()); } if (!genoTypingResultTextTF.isReadOnly()) { - genoTypingResultTextTF.setValue(newFieldValue.getGenoTypeResultText()); + genoTypingResultTextTF.setValue(newFieldValue.getGenoTypeText()); } if (!seroGrpSepcCB.isReadOnly()) { @@ -494,9 +517,9 @@ protected void addFields() { diseaseVariantField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariant)); diseaseVariantDetailsField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariantDetails)); } - genoTypingCB = addField(PathogenTestDto.GENOTYPE_RESULT, ComboBox.class); + genoTypingCB = addField(PathogenTestDto.GENOTYPE, ComboBox.class); genoTypingCB.setVisible(true); - genoTypingResultTextTF = addField(PathogenTestDto.GENOTYPE_RESULT_TEXT, TextField.class); + genoTypingResultTextTF = addField(PathogenTestDto.GENOTYPE_TEXT, TextField.class); genoTypingResultTextTF.setVisible(true); ComboBox testedPathogenField = addCustomizableEnumField(PathogenTestDto.TESTED_PATHOGEN); @@ -534,7 +557,8 @@ protected void addFields() { if (!FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { testResultField.removeItem(PathogenTestResultType.NOT_APPLICABLE); } - TextField seroTypeTF = addField(PathogenTestDto.SEROTYPE, TextField.class); + seroTypeField = addField(PathogenTestDto.SEROTYPE, ComboBox.class); + addField(PathogenTestDto.SEROTYPE_TEXT, TextField.class); NullableOptionGroup rifampicinResistantField = addField(PathogenTestDto.RIFAMPICIN_RESISTANT, NullableOptionGroup.class); rifampicinResistantField.setVisible(false); @@ -552,6 +576,8 @@ protected void addFields() { specieField = addField(PathogenTestDto.SPECIE, ComboBox.class); specieField.setVisible(false); + addField(PathogenTestDto.SPECIE_TEXT, TextField.class); + TextField patternProfileField = addField(PathogenTestDto.PATTERN_PROFILE, TextField.class); patternProfileField.setVisible(false); @@ -564,6 +590,13 @@ protected void addFields() { //drugSusceptibilityField.setVisible(false); addToVisibleAllowedFields(drugSusceptibilityField); + // Malaria and Dengue fields + addField(PathogenTestDto.ANTIBODY_TITRE, TextField.class); + addField(PathogenTestDto.PERFORMED_BY_REFERENCE_LABORATORY, NullableOptionGroup.class); + addField(PathogenTestDto.RETEST_REQUESTED, NullableOptionGroup.class); + Field resultDetailsField = addField(PathogenTestDto.RESULT_DETAILS); + resultDetailsField.setVisible(false); + if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { //tuberculosis-pcr test specification FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.RIFAMPICIN_RESISTANT, RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, true); @@ -575,7 +608,7 @@ protected void addFields() { FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.STRAIN_CALL_STATUS, STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, true); //tuberculosis-spoligotyping test specification - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SPECIE, SPECIE_VISIBILITY_CONDITIONS, true); + // FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SPECIE, SPECIE_VISIBILITY_CONDITIONS, true); //tuberculosis-miru-code test specification Map> tuberculosisMiruCodeDependencies = new HashMap<>() { @@ -589,7 +622,7 @@ protected void addFields() { //FieldHelper.setRequiredWhen(getFieldGroup(), PathogenTestDto.PATTERN_PROFILE, tuberculosisMiruCodeDependencies); } - seroTypeTF.setVisible(false); + seroTypeField.setVisible(false); ComboBox seroTypeMetCB = addField(PathogenTestDto.SEROTYPING_METHOD, ComboBox.class); seroTypeMetCB.setVisible(false); @@ -1006,51 +1039,6 @@ protected void addFields() { Arrays.asList(PathogenTestType.PCR_RT_PCR, PathogenTestType.DNA_MICROARRAY, PathogenTestType.SEQUENCING), true); - // Serotype field visibility specification for CSM disease - Map> serotypeVisibilityDependencies = new HashMap>() { - - private static final long serialVersionUID = 1967952323596082247L; - - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.CSM)); - put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - } - }; - FieldHelper.setVisibleWhen(getFieldGroup(), Arrays.asList(PathogenTestDto.SEROTYPE), serotypeVisibilityDependencies, true); - // End of Serotype field visibility specification for CSM disease - - // IPI visibility check with a positive test result, show serotype and serotyping method fields - Map> ipiSeroTypeAndMethodVisibilityDependencies = new HashMap>() { - - private static final long serialVersionUID = 1967952323596082247L; - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION)); - put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.SEROGROUPING)); - put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - } - }; - FieldHelper.setVisibleWhen( - getFieldGroup(), - Arrays.asList(PathogenTestDto.SEROTYPE, PathogenTestDto.SEROTYPING_METHOD), - ipiSeroTypeAndMethodVisibilityDependencies, - true); - Map> ipiSeroTypeVisibilityDependencies = new HashMap>() { - - private static final long serialVersionUID = 1967952323596082247L; - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION)); - put( - PathogenTestDto.TEST_TYPE, - Arrays.asList( - PathogenTestType.WHOLE_GENOME_SEQUENCING, - PathogenTestType.SLIDE_AGGLUTINATION, - PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, - PathogenTestType.SEROGROUPING)); - put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - } - }; - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SEROTYPE, ipiSeroTypeVisibilityDependencies, true); - FieldHelper.setVisibleWhen( getFieldGroup(), PathogenTestDto.SERO_TYPING_METHOD_TEXT, @@ -1081,6 +1069,14 @@ protected void addFields() { PathogenTestDto.SERO_GROUP_SPECIFICATION, SeroGroupSpecification.OTHER, true); + + // antibody titre visibility + FieldHelper.setVisibleWhen( + getFieldGroup(), + PathogenTestDto.ANTIBODY_TITRE, + PathogenTestDto.TEST_TYPE, + PathogenTestType.NEUTRALIZING_ANTIBODIES, + true); // End of IMI serogroup specification //Cryptosporidiosis for all countries Genotyping specification Map> cryptoGenoTypingDependencies = new HashMap<>() { @@ -1091,10 +1087,9 @@ protected void addFields() { put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); } }; - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.GENOTYPE_RESULT, cryptoGenoTypingDependencies, true); + FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.GENOTYPE, cryptoGenoTypingDependencies, true); - FieldHelper - .setVisibleWhen(getFieldGroup(), PathogenTestDto.GENOTYPE_RESULT_TEXT, PathogenTestDto.GENOTYPE_RESULT, GenoTypeResult.OTHER, true); + FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.GENOTYPE_TEXT, PathogenTestDto.GENOTYPE, GenoType.OTHER, true); //disease variant specifications for RSV and Influenza Map> diseaseVariantDependencies = new HashMap<>() { @@ -1123,6 +1118,20 @@ protected void addFields() { updateDiseaseVariantField.accept((Disease) diseaseField.getValue()); + // Need to address these visibility issues + // @Herold + BiConsumer updateSerotypeField = (Disease disease, PathogenTestType testType) -> { + setVisibleClear( + SEROTYPE_VISIBILITY_MAP.containsKey(disease) && SEROTYPE_VISIBILITY_MAP.get(disease).contains(testType), + PathogenTestDto.SEROTYPE); + }; + + BiConsumer updateSpecieField = (Disease disease, PathogenTestType testType) -> { + setVisibleClear( + SPECIE_VISIBILITY_MAP.containsKey(disease) && SPECIE_VISIBILITY_MAP.get(disease).contains(testType), + PathogenTestDto.SPECIE); + }; + diseaseField.addValueChangeListener((ValueChangeListener) valueChangeEvent -> { Disease latestDisease = (Disease) valueChangeEvent.getProperty().getValue(); // If the disease changed, test type field should be updated with its respective test types @@ -1137,7 +1146,10 @@ protected void addFields() { Arrays.asList(PathogenTestType.values()), FieldVisibilityCheckers.withDisease(disease), PathogenTestType.class); - + // serotype values should be changed based on the disease + // FieldHelper.updateItems(seroTypeField, Arrays.asList(Serotype.values()), FieldVisibilityCheckers.withDisease(disease), Serotype.class); + FieldHelper.updateItems(disease, seroTypeField, Serotype.class); + FieldHelper.updateItems(disease, specieField, PathogenSpecie.class); if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { FieldHelper.updateItems( strainCallStatusField, @@ -1161,7 +1173,13 @@ protected void addFields() { testTypeField.addValueChangeListener(e -> { PathogenTestType testType = (PathogenTestType) e.getProperty().getValue(); if (testType != null) { - if (testType == PathogenTestType.IGM_SERUM_ANTIBODY || testType == PathogenTestType.IGG_SERUM_ANTIBODY) { + // For Dengue IGG serum antibody, fourFoldIncrease fild should be visible. + // and its caption will be renamed with the caption as Seroconversion/ 4-fold increase + if (Disease.DENGUE == (Disease) diseaseField.getValue() && testType == PathogenTestType.IGG_SERUM_ANTIBODY) { + fourFoldIncrease.setCaption(I18nProperties.getCaption(Captions.PathogenTest_fourFoldIncreaseAntibodyTiter_DENGUE)); + fourFoldIncrease.setVisible(true); + fourFoldIncrease.setEnabled(true); + } else if (testType == PathogenTestType.IGM_SERUM_ANTIBODY || testType == PathogenTestType.IGG_SERUM_ANTIBODY) { fourFoldIncrease.setVisible(true); fourFoldIncrease.setEnabled(caseSampleCount >= 2); } else { @@ -1201,7 +1219,15 @@ protected void addFields() { PathogenTestDto.TUBE_AG_TB2_GT10, PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10); - FieldHelper.updateItems((Disease) diseaseField.getValue(), genoTypingCB, GenoTypeResult.class); + FieldHelper.updateItems((Disease) diseaseField.getValue(), genoTypingCB, GenoType.class); + // verifying the serotype field visibility. reason for this pattern is that, this should display disease+pathogentest combination. + updateSerotypeField.accept(disease, testType); + + updateSpecieField.accept(disease, testType); + // Result details should be visible for Malaria and test-types with PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.Q_PCR + resultDetailsField.setVisible( + Disease.MALARIA == disease && Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.Q_PCR).contains(testType)); + seroTypeMetCB.setVisible(testType == PathogenTestType.SEROGROUPING && Disease.INVASIVE_PNEUMOCOCCAL_INFECTION == disease); } else { setVisibleClear( testTypeField.getValue() != null, @@ -1264,6 +1290,15 @@ protected void addFields() { initializeAccessAndAllowedAccesses(); initializeVisibilitiesAndAllowedVisibilities(); + // displaying the serotype text field only if the serotype is "other" and it has the visibility + if (isVisibleAllowed(PathogenTestDto.SEROTYPE)) { + // FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SEROTYPE, SEROTYPE_VISIBILITY_MAP, true); + FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SEROTYPE_TEXT, PathogenTestDto.SEROTYPE, Serotype.OTHER, true); + } + if (isVisibleAllowed(PathogenTestDto.SPECIE)) { + FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SPECIE_TEXT, PathogenTestDto.SPECIE, PathogenSpecie.OTHER, true); + } + // Hide/show prescriber heading after the visibilities have been initialized prescriberHeadingLabel.setVisible( isVisibleAllowed(PathogenTestDto.PRESCRIBER_PHYSICIAN_CODE) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java index ec370e9bae8..265213c6d67 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java @@ -18,7 +18,10 @@ package de.symeda.sormas.ui.samples.pathogentestlink; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.Nullable; @@ -50,7 +53,20 @@ public class PathogenTestListEntry extends SideComponentField { PathogenTestType.SLIDE_AGGLUTINATION, PathogenTestType.WHOLE_GENOME_SEQUENCING, PathogenTestType.SEQUENCING); - + //@formatter:off + public static final Map> VARIANT_MAP = Collections.unmodifiableMap(new HashMap<>() { + { + put(Disease.MALARIA, Collections.unmodifiableList(Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.RAPID_TEST, PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, PathogenTestType.LAMP, PathogenTestType.IFAT))); + put(Disease.DENGUE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.NAAT, PathogenTestType.NEUTRALIZING_ANTIBODIES, PathogenTestType.PCR_RT_PCR))); + put(Disease.MEASLES, Collections.unmodifiableList(Arrays.asList(PathogenTestType.GENOTYPING))); + put(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SEROGROUPING, PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, + PathogenTestType.SLIDE_AGGLUTINATION,PathogenTestType.WHOLE_GENOME_SEQUENCING, PathogenTestType.SEQUENCING))); + put(Disease.TUBERCULOSIS, Collections.unmodifiableList(Arrays.asList(PathogenTestType.MICROSCOPY, PathogenTestType.BEIJINGGENOTYPING, + PathogenTestType.SPOLIGOTYPING, PathogenTestType.MIRU_PATTERN_CODE))); + } + }); + //@formatter:on public PathogenTestListEntry(PathogenTestDto pathogenTest, boolean showTestResultText) { this.pathogenTest = pathogenTest; @@ -122,14 +138,7 @@ public PathogenTestListEntry(PathogenTestDto pathogenTest, boolean showTestResul PathogenTestType testType = pathogenTest.getTestType(); if (seroGrpTests.contains(pathogenTest.getTestType())) { resultText = pathogenTest.getSeroGroupSpecification() != null ? pathogenTest.getSeroGroupSpecification() : pathogenTest.getSerotype(); - } else if (pathogenTest.getTestedDisease() == Disease.TUBERCULOSIS - && Arrays - .asList( - PathogenTestType.MICROSCOPY, - PathogenTestType.BEIJINGGENOTYPING, - PathogenTestType.SPOLIGOTYPING, - PathogenTestType.MIRU_PATTERN_CODE) - .contains(testType)) { + } else if (pathogenTest.getTestedDisease() == Disease.TUBERCULOSIS && VARIANT_MAP.get(pathogenTest.getTestedDisease()).contains(testType)) { if (testType == PathogenTestType.MICROSCOPY) { resultText = StringUtils.abbreviate((pathogenTest.getTestScale() != null ? pathogenTest.getTestScale().toString() : ""), 125); } else if (testType == PathogenTestType.BEIJINGGENOTYPING) { @@ -141,7 +150,13 @@ public PathogenTestListEntry(PathogenTestDto pathogenTest, boolean showTestResul resultText = StringUtils.abbreviate(pathogenTest.getPatternProfile(), 125); } } else if (testType == PathogenTestType.GENOTYPING) { - resultText = StringUtils.abbreviate((pathogenTest.getGenoTypeResult() != null ? pathogenTest.getGenoTypeResult().toString() : ""), 125); + resultText = StringUtils.abbreviate((pathogenTest.getGenoType() != null ? pathogenTest.getGenoType().toString() : ""), 125); + } else if (pathogenTest.getTestedDisease() == Disease.MALARIA + && VARIANT_MAP.get(pathogenTest.getTestedDisease()).stream().anyMatch(testType::equals)) { + resultText = StringUtils.abbreviate((pathogenTest.getSpecie() != null ? pathogenTest.getSpecie().toString() : ""), 125); + } else if (pathogenTest.getTestedDisease() == Disease.DENGUE + && VARIANT_MAP.get(pathogenTest.getTestedDisease()).stream().anyMatch(testType::equals)) { + resultText = StringUtils.abbreviate((pathogenTest.getSerotype() != null ? pathogenTest.getSerotype().toString() : ""), 125); } else { resultText = pathogenTest.getTestResult(); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/sampleLink/SampleListEntry.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/sampleLink/SampleListEntry.java index 03fb3bb892b..0ddce545c6d 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/sampleLink/SampleListEntry.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/sampleLink/SampleListEntry.java @@ -14,6 +14,8 @@ */ package de.symeda.sormas.ui.samples.sampleLink; +import org.apache.commons.lang3.StringUtils; + import com.vaadin.icons.VaadinIcons; import com.vaadin.ui.Alignment; import com.vaadin.ui.Button; @@ -23,6 +25,7 @@ import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.themes.ValoTheme; +import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.feature.FeatureType; import de.symeda.sormas.api.i18n.Captions; @@ -39,6 +42,7 @@ import de.symeda.sormas.api.user.UserRight; import de.symeda.sormas.api.utils.DataHelper; import de.symeda.sormas.ui.UiUtil; +import de.symeda.sormas.ui.samples.pathogentestlink.PathogenTestListEntry; import de.symeda.sormas.ui.utils.ButtonHelper; import de.symeda.sormas.ui.utils.CssStyles; import de.symeda.sormas.ui.utils.DateFormatHelper; @@ -154,6 +158,24 @@ public SampleListEntry(SampleListEntryDto sampleListEntryDto) { cqValue.addStyleName(CssStyles.ALIGN_RIGHT); bottomLayout.addComponent(cqValue); } + // Need to include the variants + String variant = null; + if (latestTest.getTestedDisease() == Disease.MALARIA + && PathogenTestListEntry.VARIANT_MAP.get(latestTest.getTestedDisease()).stream().anyMatch(latestTest.getTestType()::equals)) { + variant = StringUtils.abbreviate((latestTest.getSpecie() != null ? latestTest.getSpecie().toString() : ""), 125); + } else if (latestTest.getTestedDisease() == Disease.DENGUE + && PathogenTestListEntry.VARIANT_MAP.get(latestTest.getTestedDisease()).stream().anyMatch(latestTest.getTestType()::equals)) { + variant = StringUtils.abbreviate((latestTest.getSerotype() != null ? latestTest.getSerotype().toString() : ""), 125); + } else if (latestTest.getTestedDisease() == Disease.MEASLES) { + variant = StringUtils.abbreviate((latestTest.getGenoType() != null ? latestTest.getGenoType().toString() : ""), 125); + } else { + variant = ""; + } + if (StringUtils.isNotBlank(variant)) { + Label variantLabel = new Label(DataHelper.toStringNullable(variant)); + CssStyles.style(variantLabel, CssStyles.LABEL_BOLD, CssStyles.LABEL_CRITICAL); + bottomLayout.addComponent(variantLabel); + } latestTestLayout.addComponents(heading, testDate, bottomLayout); topLeftLayout.addComponent(latestTestLayout); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/symptoms/SymptomsForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/symptoms/SymptomsForm.java index 80b66b8edd1..524866f4332 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/symptoms/SymptomsForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/symptoms/SymptomsForm.java @@ -1332,7 +1332,7 @@ private void addListenerForOnsetFields(ComboBox onsetSymptom, DateField onsetDat List allPropertyIds = Stream.concat(unconditionalSymptomFieldIds.stream(), conditionalBleedingSymptomFieldIds.stream()).collect(Collectors.toList()); allPropertyIds.add(LESIONS_THAT_ITCH); - + allPropertyIds.add(OTHER_COMPLICATIONS_TEXT); for (Object sourcePropertyId : allPropertyIds) { Field sourceField = getFieldGroup().getField(sourcePropertyId); sourceField.addValueChangeListener(event -> { @@ -1355,8 +1355,7 @@ private void addListenerForOnsetFields(ComboBox onsetSymptom, DateField onsetDat // If the symptom status set to Yes, it should be updated in the complications section, // otherwise delete it from the complications section. if (caseSymptomSideViewComponent != null) { - caseSymptomSideViewComponent - .toggleComplicationSymptom(sourceField.getId(), FieldHelper.getNullableSourceFieldValue(sourceField) == SymptomState.YES); + caseSymptomSideViewComponent.toggleComplicationSymptom(sourceField); caseSymptomSideViewComponent.refreshLayout(); } // If any one of the symptoms is set to yes, then the ClinicalManifestation field should be defaulting to "Disease with sign of severity (WHO definition)" From 70d2d77264b377fd6f711e36a4f2216b6bc21cc7 Mon Sep 17 00:00:00 2001 From: Karnaiah Pesula Date: Thu, 26 Mar 2026 15:44:52 +0100 Subject: [PATCH 17/55] Fixed the possible issues --- .../sormas/api/sample/PathogenSpecie.java | 2 ++ .../sormas/api/sample/PathogenTestDto.java | 6 +++++ .../sormas/api/sample/PathogenTestType.java | 7 ++--- .../de/symeda/sormas/api/sample/Serotype.java | 3 ++- sormas-api/src/main/resources/enum.properties | 1 - .../symeda/sormas/ui/caze/CaseController.java | 2 +- .../ui/caze/CaseSymptomSideViewComponent.java | 27 +++++++++++++------ .../sormas/ui/samples/PathogenTestForm.java | 6 ++--- .../PathogenTestListEntry.java | 2 +- 9 files changed, 36 insertions(+), 20 deletions(-) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java index 738989ba968..5aca901ebaf 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java @@ -52,6 +52,8 @@ public enum PathogenSpecie { PathogenTestType.OTHER_MOLECULAR_ASSAY, PathogenTestType.OTHER_SEROLOGICAL_TEST }) SPP, + @Diseases({ + Disease.MALARIA }) @ApplicableToPathogenTests(value = { PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.RAPID_TEST, diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestDto.java index 01078b2d266..41866c0b7b4 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestDto.java @@ -19,6 +19,7 @@ import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnore; import de.symeda.sormas.api.CountryHelper; @@ -229,6 +230,7 @@ public class PathogenTestDto extends PseudonymizableDto { private YesNoUnknown rifampicinResistant; private YesNoUnknown isoniazidResistant; private PathogenSpecie specie; + @Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong) private String specieText; private String patternProfile; private PathogenStrainCallStatus strainCallStatus; @@ -322,12 +324,14 @@ public class PathogenTestDto extends PseudonymizableDto { // Dengue and Malaria changes @Diseases(value = { Disease.DENGUE }) + @Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong) private String antibodyTitre; private Boolean performedByReferenceLaboratory; // defaulting this test to false. private Boolean retestRequested = false; @Diseases({ Disease.MALARIA }) + @Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong) private String resultDetails; public static PathogenTestDto build(SampleDto sample, UserDto currentUser) { @@ -813,6 +817,7 @@ public void setSeroTypingMethod(SerotypingMethod seroTypingMethod) { this.seroTypingMethod = seroTypingMethod; } + @JsonAlias("genoTypeResult") public GenoType getGenoType() { return genoType; } @@ -821,6 +826,7 @@ public void setGenoType(GenoType genoType) { this.genoType = genoType; } + @JsonAlias("genoTypeResultText") public String getGenoTypeText() { return genoTypeText; } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestType.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestType.java index cba443d521e..554cf4a7066 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestType.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenTestType.java @@ -111,7 +111,8 @@ public enum PathogenTestType { Disease.RESPIRATORY_SYNCYTIAL_VIRUS, Disease.INVASIVE_MENINGOCOCCAL_INFECTION, Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, - Disease.MEASLES }) + Disease.MEASLES, + Disease.MALARIA }) INDIRECT_FLUORESCENT_ANTIBODY, @Diseases(value = { @@ -295,10 +296,6 @@ public enum PathogenTestType { @Diseases({ Disease.MALARIA }) OTHER_SEROLOGICAL_TEST, - // Indirect Fluorescent Antibody Test - @Diseases({ - Disease.MALARIA }) - IFAT, OTHER; @Override diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/Serotype.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/Serotype.java index e522adaa985..accb5754deb 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/Serotype.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/Serotype.java @@ -19,6 +19,7 @@ package de.symeda.sormas.api.sample; import java.util.Arrays; +import java.util.Locale; import org.apache.commons.lang3.StringUtils; @@ -51,7 +52,7 @@ public static Serotype fromString(String serotype) { if (StringUtils.isBlank(serotype)) { return null; } - String serotypeEnumVal = serotype.toUpperCase().replaceAll("[-_]", "_"); + String serotypeEnumVal = StringUtils.trim(serotype).toUpperCase(Locale.ROOT).replaceAll("[-_\\s]+", "_"); return Arrays.asList(Serotype.values()).stream().noneMatch(s -> s.name().equals(serotypeEnumVal)) ? OTHER : Serotype.valueOf(serotypeEnumVal); } diff --git a/sormas-api/src/main/resources/enum.properties b/sormas-api/src/main/resources/enum.properties index 01ac659d163..bd747b32b0c 100644 --- a/sormas-api/src/main/resources/enum.properties +++ b/sormas-api/src/main/resources/enum.properties @@ -1204,7 +1204,6 @@ PathogenTestType.LAMP = Lamp PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST = Other antigen detection test PathogenTestType.OTHER_MOLECULAR_ASSAY = Other molecular assay PathogenTestType.OTHER_SEROLOGICAL_TEST = Other serological test -PathogenTestType.IFAT = Indirect Fluorescent Antibody Test PCRTestSpecification.VARIANT_SPECIFIC = Variant specific PCRTestSpecification.N501Y_MUTATION_DETECTION = N501Y mutation detection diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java index 9cc96af01d1..642c978c5e8 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java @@ -1440,7 +1440,7 @@ private void includeExposureDates(Date symptomOnsetDate, EpiDataDto epiDataDto, if (!diseaseConfigurationDto.getIncubationPeriodEnabled()) { return; } - if (diseaseConfigurationDto.getMaxIncubationPeriod() == 0 || diseaseConfigurationDto.getMaxIncubationPeriod() == null) { + if (diseaseConfigurationDto.getMaxIncubationPeriod() == null || diseaseConfigurationDto.getMaxIncubationPeriod() == 0) { return; } if (diseaseConfigurationDto.getMinIncubationPeriod() == null) { diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseSymptomSideViewComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseSymptomSideViewComponent.java index 44f28b3670e..901ea31bae2 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseSymptomSideViewComponent.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseSymptomSideViewComponent.java @@ -95,15 +95,26 @@ public void toggleComplicationSymptom(Field sourceField) { componentMap.put(sourceField.getId(), label); return; } - // if the source field value is String type, and it's value is not empty, and a map does not have that symptom, and its a complicated symptom, add it to the layout and map - // This is to set the entered value as a caption. - if (sourceFieldObj instanceof String - && StringUtils.isNotBlank((String) sourceFieldObj) - && !componentMap.containsKey(sourceField.getId()) - && complicatedSymptoms.contains(sourceField.getId())) { - Label label = new Label(sourceField.getValue().toString()); - componentMap.put(sourceField.getId(), label); + + // if the source field value is String type + if (sourceFieldObj instanceof String && StringUtils.isNotBlank((String) sourceFieldObj)) { + Label label; + if (componentMap.containsKey(sourceField.getId())) { + // if the map has a different value, then remove the old value from the layout and add the new value. + layout.removeComponent(componentMap.get(sourceField.getId())); + label = new Label(sourceField.getValue().toString()); + } else if (!componentMap.containsKey(sourceField.getId()) && complicatedSymptoms.contains(sourceField.getId())) { + // If the map not has a key, and it's a complicated symptom, then add it to the layout. + label = new Label(sourceField.getValue().toString()); + } else { + label = null; + } + if (label != null) { + componentMap.put(sourceField.getId(), label); + } } + // , and it's value is not empty, and a map does not have that symptom, and its a complicated symptom, add it to the layout and map + // This is to set the entered value as a caption. } /** diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index 4d49782fff5..cdacbda5ddf 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -159,8 +159,8 @@ public class PathogenTestForm extends AbstractEditForm { put(Disease.CRYPTOSPORIDIOSIS, new ArrayList<>(List.of(PathogenTestType.GENOTYPING))); put(Disease.DENGUE, new ArrayList<>(List.of(PathogenTestType.NAAT, PathogenTestType.NEUTRALIZING_ANTIBODIES, PathogenTestType.PCR_RT_PCR))); put(Disease.MALARIA, new ArrayList<>(List.of( PathogenTestType.ANTIGEN_DETECTION, PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.RAPID_TEST, - PathogenTestType.PCR_RT_PCR, PathogenTestType.Q_PCR, PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, PathogenTestType.LAMP, - PathogenTestType.IFAT, PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, PathogenTestType.OTHER_SEROLOGICAL_TEST, PathogenTestType.OTHER_MOLECULAR_ASSAY))); + PathogenTestType.INDIRECT_FLUORESCENT_ANTIBODY,PathogenTestType.PCR_RT_PCR, PathogenTestType.Q_PCR, PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, PathogenTestType.LAMP, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, PathogenTestType.OTHER_SEROLOGICAL_TEST, PathogenTestType.OTHER_MOLECULAR_ASSAY))); } }); @@ -207,7 +207,7 @@ public class PathogenTestForm extends AbstractEditForm { put(Disease.LATENT_TUBERCULOSIS, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SPOLIGOTYPING))); put(Disease.TUBERCULOSIS, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SPOLIGOTYPING))); put(Disease.MALARIA, Collections.unmodifiableList(Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.ANTIGEN_DETECTION, - PathogenTestType.RAPID_TEST, PathogenTestType.PCR_RT_PCR, PathogenTestType.Q_PCR, PathogenTestType.LAMP, PathogenTestType.IFAT, + PathogenTestType.RAPID_TEST, PathogenTestType.PCR_RT_PCR, PathogenTestType.Q_PCR, PathogenTestType.LAMP,PathogenTestType.INDIRECT_FLUORESCENT_ANTIBODY, PathogenTestType.OTHER_MOLECULAR_ASSAY, PathogenTestType.OTHER_SEROLOGICAL_TEST, PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY))); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java index 265213c6d67..e010d2a2669 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java @@ -57,7 +57,7 @@ public class PathogenTestListEntry extends SideComponentField { public static final Map> VARIANT_MAP = Collections.unmodifiableMap(new HashMap<>() { { put(Disease.MALARIA, Collections.unmodifiableList(Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.RAPID_TEST, PathogenTestType.PCR_RT_PCR, - PathogenTestType.Q_PCR, PathogenTestType.LAMP, PathogenTestType.IFAT))); + PathogenTestType.Q_PCR, PathogenTestType.LAMP, PathogenTestType.INDIRECT_FLUORESCENT_ANTIBODY))); put(Disease.DENGUE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.NAAT, PathogenTestType.NEUTRALIZING_ANTIBODIES, PathogenTestType.PCR_RT_PCR))); put(Disease.MEASLES, Collections.unmodifiableList(Arrays.asList(PathogenTestType.GENOTYPING))); put(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SEROGROUPING, PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, From 6d2ddbe95e511fb390285b70e524af8c2d4914cd Mon Sep 17 00:00:00 2001 From: Karnaiah Pesula Date: Fri, 27 Mar 2026 12:44:00 +0100 Subject: [PATCH 18/55] Review fixes for exposure dates --- .../symeda/sormas/api/epidata/EpiDataDto.java | 29 -------- .../sormas/api/sample/PathogenSpecie.java | 25 ++++--- sormas-api/src/main/resources/enum.properties | 27 ++++--- .../symeda/sormas/ui/caze/CaseController.java | 29 +------- .../sormas/ui/contact/ContactController.java | 2 +- .../symeda/sormas/ui/epidata/EpiDataForm.java | 71 ++++++++++++++++--- .../sormas/ui/samples/PathogenTestForm.java | 9 ++- .../PathogenTestListEntry.java | 21 +++++- .../samples/sampleLink/SampleListEntry.java | 26 +++++-- 9 files changed, 137 insertions(+), 102 deletions(-) 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 63bf927cab9..7c49bddcb97 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 @@ -69,8 +69,6 @@ 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 EXPOSURE_START_DATE = "exposureStartDate"; - public static final String EXPOSURE_END_DATE = "exposureEndDate"; public static final String OTHER_DETAILS = "otherDetails"; private YesNoUnknown exposureDetailsKnown; @@ -134,17 +132,6 @@ public class EpiDataDto extends PseudonymizableDto { Disease.CRYPTOSPORIDIOSIS }) private String infectionSourceText; - // this values only for display the data from UI. - - @Diseases({ - Disease.DENGUE, - Disease.MALARIA }) - private Date exposureStartDate; - @Diseases({ - Disease.DENGUE, - Disease.MALARIA }) - private Date exposureEndDate; - @Diseases({ Disease.GIARDIASIS }) private CountryReferenceDto country; @@ -302,22 +289,6 @@ public void setImportedCase(YesNoUnknown importedCase) { this.importedCase = importedCase; } - public Date getExposureStartDate() { - return exposureStartDate; - } - - public void setExposureStartDate(Date exposureStartDate) { - this.exposureStartDate = exposureStartDate; - } - - public Date getExposureEndDate() { - return exposureEndDate; - } - - public void setExposureEndDate(Date exposureEndDate) { - this.exposureEndDate = exposureEndDate; - } - public CountryReferenceDto getCountry() { return country; } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java index 5aca901ebaf..a4816017ac8 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java @@ -27,15 +27,27 @@ public enum PathogenSpecie { + @Diseases({ + Disease.TUBERCULOSIS, + Disease.LATENT_TUBERCULOSIS }) @ApplicableToPathogenTests(value = { PathogenTestType.SPOLIGOTYPING }) MYCOBATERIUM_AFRICANUM, + @Diseases({ + Disease.TUBERCULOSIS, + Disease.LATENT_TUBERCULOSIS }) @ApplicableToPathogenTests(value = { PathogenTestType.SPOLIGOTYPING }) MYCOBATERIUM_BOVIS, + @Diseases({ + Disease.TUBERCULOSIS, + Disease.LATENT_TUBERCULOSIS }) @ApplicableToPathogenTests(value = { PathogenTestType.SPOLIGOTYPING }) MYCOBATERIUM_TUBERCULOSIS, + @Diseases({ + Disease.TUBERCULOSIS, + Disease.LATENT_TUBERCULOSIS }) @ApplicableToPathogenTests(value = { PathogenTestType.SPOLIGOTYPING }) OTHER_MTBC_MEMBER, @@ -130,19 +142,6 @@ public enum PathogenSpecie { PathogenTestType.OTHER_MOLECULAR_ASSAY, PathogenTestType.OTHER_SEROLOGICAL_TEST }) CYNOMOLGI, - @Diseases({ - Disease.MALARIA }) - @ApplicableToPathogenTests(value = { - PathogenTestType.THIN_BLOOD_SMEAR, - PathogenTestType.RAPID_TEST, - PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, - PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, - PathogenTestType.PCR_RT_PCR, - PathogenTestType.Q_PCR, - PathogenTestType.LAMP, - PathogenTestType.OTHER_MOLECULAR_ASSAY, - PathogenTestType.OTHER_SEROLOGICAL_TEST }) - SPECIES, @Diseases({ Disease.MALARIA }) @ApplicableToPathogenTests(value = { diff --git a/sormas-api/src/main/resources/enum.properties b/sormas-api/src/main/resources/enum.properties index bd747b32b0c..b2376867cb0 100644 --- a/sormas-api/src/main/resources/enum.properties +++ b/sormas-api/src/main/resources/enum.properties @@ -1200,7 +1200,7 @@ PathogenTestType.NAAT = Nucleic acid amplification test (NAAT) PathogenTestType.THICK_BLOOD_SMEAR = Thick blood smear PathogenTestType.THIN_BLOOD_SMEAR = Thin blood smear PathogenTestType.Q_PCR = qPCR -PathogenTestType.LAMP = Lamp +PathogenTestType.LAMP = LAMP PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST = Other antigen detection test PathogenTestType.OTHER_MOLECULAR_ASSAY = Other molecular assay PathogenTestType.OTHER_SEROLOGICAL_TEST = Other serological test @@ -1209,22 +1209,21 @@ PCRTestSpecification.VARIANT_SPECIFIC = Variant specific PCRTestSpecification.N501Y_MUTATION_DETECTION = N501Y mutation detection # SpoligotypeSpecie -PathogenSpecie.MYCOBATERIUM_AFRICANUM = Mycobaterium Africanum -PathogenSpecie.MYCOBATERIUM_BOVIS = Mycobaterium bovis -PathogenSpecie.MYCOBATERIUM_TUBERCULOSIS = Mycobaterium tuberculosis +PathogenSpecie.MYCOBATERIUM_AFRICANUM = Mycobacterium Africanum +PathogenSpecie.MYCOBATERIUM_BOVIS = Mycobacterium bovis +PathogenSpecie.MYCOBATERIUM_TUBERCULOSIS = Mycobacterium tuberculosis PathogenSpecie.OTHER_MTBC_MEMBER = Other member of the MTBC PathogenSpecie.UNKNOWN = Unknown PathogenSpecie.NOT_APPLICABLE = Not Applicable -PathogenSpecie.SPP = Spp -PathogenSpecie.FALCIPARUM = Falciparum -PathogenSpecie.VIVAX = Vivax -PathogenSpecie.MALARIAE = Malariae -PathogenSpecie.OVALE = Ovale -PathogenSpecie.KNOWLESI = Knowlesi -PathogenSpecie.CYNOMOLGI = Cynomolgi -PathogenSpecie.SPECIES = Species -PathogenSpecie.NOT_SPECIFIED = Not specified -PathogenSpecie.COINFECTION = Coinfection +PathogenSpecie.SPP = Plasmodium spp +PathogenSpecie.FALCIPARUM = Plasmodium falciparum +PathogenSpecie.VIVAX = Plasmodium vivax +PathogenSpecie.MALARIAE = Plasmodium malariae +PathogenSpecie.OVALE = Plasmodium ovale +PathogenSpecie.KNOWLESI = Plasmodium knowlesi +PathogenSpecie.CYNOMOLGI = Plasmodium cynomolgi +PathogenSpecie.NOT_SPECIFIED = Plasmodium species not specified +PathogenSpecie.COINFECTION = Plasmodium coinfection PathogenSpecie.OTHER = Other # PathogenTestScale diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java index 642c978c5e8..8a442f49e00 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java @@ -78,7 +78,6 @@ import de.symeda.sormas.api.contact.ContactStatus; import de.symeda.sormas.api.contact.SimilarContactDto; import de.symeda.sormas.api.deletionconfiguration.DeletionInfoDto; -import de.symeda.sormas.api.disease.DiseaseConfigurationDto; import de.symeda.sormas.api.docgeneneration.DocumentWorkflow; import de.symeda.sormas.api.docgeneneration.RootEntityType; import de.symeda.sormas.api.document.DocumentRelatedEntityType; @@ -1398,14 +1397,14 @@ public CommitDiscardWrapperComponent getEpiDataComponent( // Exposure start date and end date should be calculated based on symptom onsetDate and incubation start periods Date symptomOnsetDate = caze.getSymptoms().getOnsetDate(); - includeExposureDates(symptomOnsetDate, epiDataDto, caze.getDisease()); EpiDataForm epiDataForm = new EpiDataForm( caze.getDisease(), CaseDataDto.class, caze.isPseudonymized(), caze.isInJurisdiction(), sourceContactsToggleCallback, - isEditAllowed); + isEditAllowed, + symptomOnsetDate); epiDataForm.setValue(epiDataDto); final CommitDiscardWrapperComponent editView = @@ -1427,30 +1426,6 @@ public CommitDiscardWrapperComponent getEpiDataComponent( return editView; } - // include the exposure dates. - private void includeExposureDates(Date symptomOnsetDate, EpiDataDto epiDataDto, Disease disease) { - // if symptomOnsetDate is null, return; - if (symptomOnsetDate == null) { - return; - } - DiseaseConfigurationDto diseaseConfigurationDto = FacadeProvider.getDiseaseConfigurationFacade().getDiseaseConfiguration(disease); - if (diseaseConfigurationDto == null) { - return; - } - if (!diseaseConfigurationDto.getIncubationPeriodEnabled()) { - return; - } - if (diseaseConfigurationDto.getMaxIncubationPeriod() == null || diseaseConfigurationDto.getMaxIncubationPeriod() == 0) { - return; - } - if (diseaseConfigurationDto.getMinIncubationPeriod() == null) { - return; - } - - epiDataDto.setExposureStartDate(DateHelper.subtractDays(symptomOnsetDate, diseaseConfigurationDto.getMaxIncubationPeriod())); - epiDataDto.setExposureEndDate(DateHelper.subtractDays(symptomOnsetDate, diseaseConfigurationDto.getMinIncubationPeriod())); - } - public CommitDiscardWrapperComponent getTherapyEditComponent(final String caseUuid, boolean isEditAllowed) { CaseDataDto caseDataDto = findCase(caseUuid); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactController.java index 6e5b9ec952a..394fd77934c 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactController.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactController.java @@ -1028,7 +1028,7 @@ public CommitDiscardWrapperComponent getEpiDataComponent(final Stri ContactDto contact = FacadeProvider.getContactFacade().getByUuid(contactUuid); EpiDataDto epiData = contact.getEpiData(); EpiDataForm epiDataForm = - new EpiDataForm(contact.getDisease(), ContactDto.class, epiData.isPseudonymized(), epiData.isInJurisdiction(), null, isEditAllowed); + new EpiDataForm(contact.getDisease(), ContactDto.class, epiData.isPseudonymized(), epiData.isInJurisdiction(), null, isEditAllowed, null); epiDataForm.setValue(epiData); final CommitDiscardWrapperComponent editView = diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java index 957ccd20c98..724fdcb5fd3 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; @@ -35,6 +36,8 @@ import org.apache.commons.lang3.StringUtils; import com.vaadin.shared.ui.ContentMode; +import com.vaadin.ui.CustomLayout; +import com.vaadin.ui.Label; import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.DateField; import com.vaadin.v7.ui.Field; @@ -47,6 +50,7 @@ import de.symeda.sormas.api.caze.CaseDataDto; import de.symeda.sormas.api.contact.ContactDto; import de.symeda.sormas.api.contact.ContactReferenceDto; +import de.symeda.sormas.api.disease.DiseaseConfigurationDto; import de.symeda.sormas.api.epidata.ClusterType; import de.symeda.sormas.api.epidata.EpiDataDto; import de.symeda.sormas.api.exposure.InfectionSource; @@ -54,6 +58,7 @@ import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.i18n.Strings; import de.symeda.sormas.api.infrastructure.country.CountryReferenceDto; +import de.symeda.sormas.api.utils.DateHelper; import de.symeda.sormas.api.utils.YesNoUnknown; import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; @@ -61,6 +66,7 @@ import de.symeda.sormas.ui.ActivityAsCase.ActivityAsCaseField; import de.symeda.sormas.ui.exposure.ExposuresField; import de.symeda.sormas.ui.utils.AbstractEditForm; +import de.symeda.sormas.ui.utils.CssStyles; import de.symeda.sormas.ui.utils.FieldAccessHelper; import de.symeda.sormas.ui.utils.FieldHelper; import de.symeda.sormas.ui.utils.NullableOptionGroup; @@ -77,11 +83,13 @@ public class EpiDataForm extends AbstractEditForm { private static final String LOC_SOURCE_CASE_CONTACTS_HEADING = "locSourceCaseContactsHeading"; private static final String LOC_EPI_DATA_FIELDS_HINT = "locEpiDataFieldsHint"; private static final String LOC_EXP_PERIOD_HEADING = "locExpPeriodHeading"; + private static final String EXPOSURE_DATES_LAYOUT = + fluidRowLocs(3, "EXPOSURE_START_DATE_LABEL", 3, "EXPOSURE_START_DATE_VALUE", 3, "EXPOSURE_END_DATE_LABEL", 3, "EXPOSURE_END_DATE_VALUE"); //@formatter:off private static final String MAIN_HTML_LAYOUT = loc(LOC_EXPOSURE_INVESTIGATION_HEADING) + - fluidRowLocs(6, EpiDataDto.EXPOSURE_START_DATE, 6, EpiDataDto.EXPOSURE_END_DATE)+ + fluidRowLocs("EXP_DATES_LAYOUT") + fluidRowLocs(6,EpiDataDto.CASE_IMPORTED_STATUS,6,"") + loc(LOC_EXP_PERIOD_HEADING) + loc(EpiDataDto.EXPOSURE_DETAILS_KNOWN) + @@ -110,6 +118,7 @@ public class EpiDataForm extends AbstractEditForm { private final Class parentClass; private final Consumer sourceContactsToggleCallback; private final boolean isPseudonymized; + private final Date symptomOnsetDate; public EpiDataForm( Disease disease, @@ -117,7 +126,8 @@ public EpiDataForm( boolean isPseudonymized, boolean inJurisdiction, Consumer sourceContactsToggleCallback, - boolean isEditAllowed) { + boolean isEditAllowed, + Date date) { super( EpiDataDto.class, EpiDataDto.I18N_PREFIX, @@ -129,6 +139,7 @@ public EpiDataForm( this.parentClass = parentClass; this.sourceContactsToggleCallback = sourceContactsToggleCallback; this.isPseudonymized = isPseudonymized; + this.symptomOnsetDate = date; addFields(); } @@ -178,17 +189,13 @@ protected void addFields() { addField(EpiDataDto.MODE_OF_TRANSMISSION_TYPE); addField(EpiDataDto.INFECTION_SOURCE); addField(EpiDataDto.INFECTION_SOURCE_TEXT); - DateField exposureStartDate = addField(EpiDataDto.EXPOSURE_START_DATE); - exposureStartDate.setCaption(I18nProperties.getString(Strings.exposureStartDate)); - exposureStartDate.setEnabled(false); - DateField exposureEndDate = addField(EpiDataDto.EXPOSURE_END_DATE); - exposureEndDate.setCaption(I18nProperties.getString(Strings.exposureEndDate)); - exposureEndDate.setEnabled(false); addField(EpiDataDto.IMPORTED_CASE, NullableOptionGroup.class); List countries = FacadeProvider.getCountryFacade().getAllActiveAsReference(); ComboBox country = addInfrastructureField(EpiDataDto.COUNTRY); country.addItems(countries); + includeExposureDates(symptomOnsetDate, disease); + TextField clusterTypeTF = addField(EpiDataDto.CLUSTER_TYPE_TEXT); FieldHelper .setVisibleWhen(getFieldGroup(), EpiDataDto.CLUSTER_TYPE, EpiDataDto.CLUSTER_RELATED, Collections.singletonList(Boolean.TRUE), true); @@ -211,6 +218,54 @@ protected void addFields() { }); } + /** + * Include the exposire start and dates when symptomOnsetDate is present. + * Disease incubation period is enabled with valid values. + * + * @param symptomOnsetDate + * @param disease + */ + private void includeExposureDates(Date symptomOnsetDate, Disease disease) { + // if symptomOnsetDate is null, return; + if (symptomOnsetDate == null) { + return; + } + DiseaseConfigurationDto diseaseConfigurationDto = FacadeProvider.getDiseaseConfigurationFacade().getDiseaseConfiguration(disease); + if (diseaseConfigurationDto == null) { + return; + } + if (!diseaseConfigurationDto.getIncubationPeriodEnabled()) { + return; + } + if (diseaseConfigurationDto.getMaxIncubationPeriod() == null || diseaseConfigurationDto.getMaxIncubationPeriod() == 0) { + return; + } + if (diseaseConfigurationDto.getMinIncubationPeriod() == null) { + return; + } + + CustomLayout exposureDatesLayout = new CustomLayout(); + exposureDatesLayout.setTemplateContents(EXPOSURE_DATES_LAYOUT); + Label exposureStartLabel = new Label(I18nProperties.getString(Strings.exposureStartDate)); + exposureStartLabel.addStyleNames(CssStyles.LABEL_BOLD, CssStyles.LABEL_UPPERCASE); + exposureDatesLayout.addComponent(exposureStartLabel, "EXPOSURE_START_DATE_LABEL"); + + DateField exposureStartDateValue = new DateField(); + exposureStartDateValue.setValue(DateHelper.subtractDays(symptomOnsetDate, diseaseConfigurationDto.getMaxIncubationPeriod())); + exposureStartDateValue.setReadOnly(true); + exposureDatesLayout.addComponent(exposureStartDateValue, "EXPOSURE_START_DATE_VALUE"); + + Label exposureEndLabel = new Label(I18nProperties.getString(Strings.exposureEndDate)); + exposureEndLabel.addStyleNames(CssStyles.LABEL_BOLD, CssStyles.LABEL_UPPERCASE); + exposureDatesLayout.addComponent(exposureEndLabel, "EXPOSURE_END_DATE_LABEL"); + DateField exposureEndDateValue = new DateField(); + exposureEndDateValue.setValue(DateHelper.subtractDays(symptomOnsetDate, diseaseConfigurationDto.getMinIncubationPeriod())); + exposureEndDateValue.setReadOnly(true); + exposureDatesLayout.addComponent(exposureEndDateValue, "EXPOSURE_END_DATE_VALUE"); + + getContent().addComponent(exposureDatesLayout, "EXP_DATES_LAYOUT"); + } + private void addActivityAsCaseFields() { getContent().addComponent( diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index cdacbda5ddf..7b08d74d525 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -1225,9 +1225,12 @@ protected void addFields() { updateSpecieField.accept(disease, testType); // Result details should be visible for Malaria and test-types with PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.Q_PCR - resultDetailsField.setVisible( - Disease.MALARIA == disease && Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.Q_PCR).contains(testType)); - seroTypeMetCB.setVisible(testType == PathogenTestType.SEROGROUPING && Disease.INVASIVE_PNEUMOCOCCAL_INFECTION == disease); + setVisibleClear( + Disease.MALARIA == disease && Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.Q_PCR).contains(testType), + PathogenTestDto.RESULT_DETAILS); + setVisibleClear( + testType == PathogenTestType.SEROGROUPING && Disease.INVASIVE_PNEUMOCOCCAL_INFECTION == disease, + PathogenTestDto.SEROTYPING_METHOD); } else { setVisibleClear( testTypeField.getValue() != null, diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java index e010d2a2669..7d567398782 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/pathogentestlink/PathogenTestListEntry.java @@ -35,9 +35,11 @@ import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.DiseaseHelper; import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.sample.PathogenSpecie; import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.sample.Serotype; import de.symeda.sormas.api.utils.DataHelper; import de.symeda.sormas.ui.utils.CssStyles; import de.symeda.sormas.ui.utils.DateFormatHelper; @@ -57,7 +59,8 @@ public class PathogenTestListEntry extends SideComponentField { public static final Map> VARIANT_MAP = Collections.unmodifiableMap(new HashMap<>() { { put(Disease.MALARIA, Collections.unmodifiableList(Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.RAPID_TEST, PathogenTestType.PCR_RT_PCR, - PathogenTestType.Q_PCR, PathogenTestType.LAMP, PathogenTestType.INDIRECT_FLUORESCENT_ANTIBODY))); + PathogenTestType.Q_PCR, PathogenTestType.LAMP, PathogenTestType.INDIRECT_FLUORESCENT_ANTIBODY, PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST, PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, PathogenTestType.ANTIGEN_DETECTION))); put(Disease.DENGUE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.NAAT, PathogenTestType.NEUTRALIZING_ANTIBODIES, PathogenTestType.PCR_RT_PCR))); put(Disease.MEASLES, Collections.unmodifiableList(Arrays.asList(PathogenTestType.GENOTYPING))); put(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SEROGROUPING, PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, @@ -153,10 +156,22 @@ public PathogenTestListEntry(PathogenTestDto pathogenTest, boolean showTestResul resultText = StringUtils.abbreviate((pathogenTest.getGenoType() != null ? pathogenTest.getGenoType().toString() : ""), 125); } else if (pathogenTest.getTestedDisease() == Disease.MALARIA && VARIANT_MAP.get(pathogenTest.getTestedDisease()).stream().anyMatch(testType::equals)) { - resultText = StringUtils.abbreviate((pathogenTest.getSpecie() != null ? pathogenTest.getSpecie().toString() : ""), 125); + // handling other specie + if (pathogenTest.getSpecie() == PathogenSpecie.OTHER) { + resultText = StringUtils.abbreviate((pathogenTest.getSpecieText() != null ? pathogenTest.getSpecieText().toString() : ""), 125); + } else { + resultText = StringUtils.abbreviate((pathogenTest.getSpecie() != null ? pathogenTest.getSpecie().toString() : ""), 125); + } + } else if (pathogenTest.getTestedDisease() == Disease.DENGUE && VARIANT_MAP.get(pathogenTest.getTestedDisease()).stream().anyMatch(testType::equals)) { - resultText = StringUtils.abbreviate((pathogenTest.getSerotype() != null ? pathogenTest.getSerotype().toString() : ""), 125); + // handling other serotypes + if (pathogenTest.getSerotype() == Serotype.OTHER) { + resultText = StringUtils.abbreviate((pathogenTest.getSerotypeText() != null ? pathogenTest.getSerotypeText().toString() : ""), 125); + } else { + resultText = StringUtils.abbreviate((pathogenTest.getSerotype() != null ? pathogenTest.getSerotype().toString() : ""), 125); + } + } else { resultText = pathogenTest.getTestResult(); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/sampleLink/SampleListEntry.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/sampleLink/SampleListEntry.java index 0ddce545c6d..3d516dc73a6 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/sampleLink/SampleListEntry.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/sampleLink/SampleListEntry.java @@ -33,11 +33,13 @@ import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.i18n.Strings; import de.symeda.sormas.api.sample.AdditionalTestingStatus; +import de.symeda.sormas.api.sample.PathogenSpecie; import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.SampleListEntryDto; import de.symeda.sormas.api.sample.SamplePurpose; import de.symeda.sormas.api.sample.SamplingReason; +import de.symeda.sormas.api.sample.Serotype; import de.symeda.sormas.api.sample.SpecimenCondition; import de.symeda.sormas.api.user.UserRight; import de.symeda.sormas.api.utils.DataHelper; @@ -162,18 +164,34 @@ public SampleListEntry(SampleListEntryDto sampleListEntryDto) { String variant = null; if (latestTest.getTestedDisease() == Disease.MALARIA && PathogenTestListEntry.VARIANT_MAP.get(latestTest.getTestedDisease()).stream().anyMatch(latestTest.getTestType()::equals)) { - variant = StringUtils.abbreviate((latestTest.getSpecie() != null ? latestTest.getSpecie().toString() : ""), 125); + if (latestTest.getSpecie() == PathogenSpecie.OTHER) { + variant = StringUtils.abbreviate((latestTest.getSpecieText() != null ? latestTest.getSpecieText().toString() : ""), 125); + } else { + variant = StringUtils.abbreviate((latestTest.getSpecie() != null ? latestTest.getSpecie().toString() : ""), 125); + } } else if (latestTest.getTestedDisease() == Disease.DENGUE && PathogenTestListEntry.VARIANT_MAP.get(latestTest.getTestedDisease()).stream().anyMatch(latestTest.getTestType()::equals)) { - variant = StringUtils.abbreviate((latestTest.getSerotype() != null ? latestTest.getSerotype().toString() : ""), 125); + if (latestTest.getSerotype() == Serotype.OTHER) { + variant = StringUtils.abbreviate((latestTest.getSerotypeText() != null ? latestTest.getSerotypeText().toString() : ""), 125); + } else { + variant = StringUtils.abbreviate((latestTest.getSerotype() != null ? latestTest.getSerotype().toString() : ""), 125); + } } else if (latestTest.getTestedDisease() == Disease.MEASLES) { - variant = StringUtils.abbreviate((latestTest.getGenoType() != null ? latestTest.getGenoType().toString() : ""), 125); + if (latestTest.getGenoType() != null) { + variant = StringUtils.abbreviate((latestTest.getGenoTypeText() != null ? latestTest.getGenoTypeText().toString() : ""), 125); + } else { + variant = StringUtils.abbreviate((latestTest.getGenoType() != null ? latestTest.getGenoType().toString() : ""), 125); + } } else { variant = ""; } if (StringUtils.isNotBlank(variant)) { Label variantLabel = new Label(DataHelper.toStringNullable(variant)); - CssStyles.style(variantLabel, CssStyles.LABEL_BOLD, CssStyles.LABEL_CRITICAL); + if (latestTest.getTestResult() == PathogenTestResultType.POSITIVE) { + CssStyles.style(variantLabel, CssStyles.LABEL_BOLD, CssStyles.LABEL_CRITICAL); + } else { + CssStyles.style(variantLabel, CssStyles.LABEL_WARNING); + } bottomLayout.addComponent(variantLabel); } latestTestLayout.addComponents(heading, testDate, bottomLayout); From 40ae639a5e08227e517efc84d2d8c64d593c7bff Mon Sep 17 00:00:00 2001 From: Karnaiah Pesula Date: Fri, 27 Mar 2026 13:17:30 +0100 Subject: [PATCH 19/55] Review fixes for exposure dates --- .../java/de/symeda/sormas/api/sample/PathogenSpecie.java | 7 +++++++ .../main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java index a4816017ac8..ed6bdced87f 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/sample/PathogenSpecie.java @@ -181,6 +181,10 @@ public enum PathogenSpecie { PathogenTestType.OTHER_MOLECULAR_ASSAY, PathogenTestType.OTHER_SEROLOGICAL_TEST }) OTHER, + @Diseases({ + Disease.TUBERCULOSIS, + Disease.LATENT_TUBERCULOSIS, + Disease.MALARIA }) @ApplicableToPathogenTests(value = { PathogenTestType.SPOLIGOTYPING, PathogenTestType.THIN_BLOOD_SMEAR, @@ -193,6 +197,9 @@ public enum PathogenSpecie { PathogenTestType.OTHER_MOLECULAR_ASSAY, PathogenTestType.OTHER_SEROLOGICAL_TEST }) UNKNOWN, + @Diseases({ + Disease.TUBERCULOSIS, + Disease.LATENT_TUBERCULOSIS }) @ApplicableToPathogenTests(value = { PathogenTestType.SPOLIGOTYPING }) NOT_APPLICABLE; diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java index 724fdcb5fd3..75e340125a8 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java @@ -219,7 +219,7 @@ protected void addFields() { } /** - * Include the exposire start and dates when symptomOnsetDate is present. + * Include the exposure start and dates when symptomOnsetDate is present. * Disease incubation period is enabled with valid values. * * @param symptomOnsetDate @@ -234,7 +234,7 @@ private void includeExposureDates(Date symptomOnsetDate, Disease disease) { if (diseaseConfigurationDto == null) { return; } - if (!diseaseConfigurationDto.getIncubationPeriodEnabled()) { + if (diseaseConfigurationDto.getIncubationPeriodEnabled() == null || !diseaseConfigurationDto.getIncubationPeriodEnabled()) { return; } if (diseaseConfigurationDto.getMaxIncubationPeriod() == null || diseaseConfigurationDto.getMaxIncubationPeriod() == 0) { From 23e9c83c15c6ac0db912959706bc86ac3975a54b Mon Sep 17 00:00:00 2001 From: Karnaiah Pesula Date: Tue, 31 Mar 2026 11:38:17 +0200 Subject: [PATCH 20/55] fix for possible script failure --- .../src/main/resources/sql/sormas_schema.sql | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index b106a76e3ca..5cd6e52d1dc 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15610,8 +15610,15 @@ INSERT INTO schema_version (version_number, comment) VALUES (615, '#13828 - Add -- 23-03-2026 new fields related to Malaria and Dengue samples and pathogenform. ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS serotypetext varchar(255); UPDATE pathogentest SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; -ALTER TABLE pathogentest rename column genotyperesult to genotype; -ALTER TABLE pathogentest rename column genotyperesulttext to genotypetext; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest' AND column_name = 'genotyperesult') THEN + ALTER TABLE pathogentest RENAME COLUMN genotyperesult TO genotype; + END IF; + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest' AND column_name = 'genotyperesulttext') THEN + ALTER TABLE pathogentest RENAME COLUMN genotyperesulttext TO genotypetext; + END IF; +END $$; ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS antibodyTitre varchar(255); ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS performedByReferenceLaboratory boolean; ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS retestRequested boolean default false; @@ -15623,8 +15630,16 @@ ALTER TABLE healthconditions ADD COLUMN IF NOT EXISTS malariainfectedyear intege ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS serotypetext varchar(255); UPDATE pathogentest_history SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; -ALTER TABLE pathogentest_history rename column genotyperesult to genotype; -ALTER TABLE pathogentest_history rename column genotyperesulttext to genotypetext; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest_history' AND column_name = 'genotyperesult') THEN + ALTER TABLE pathogentest_history RENAME COLUMN genotyperesult TO genotype; + END IF; + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest_history' AND column_name = 'genotyperesulttext') THEN + ALTER TABLE pathogentest_history RENAME COLUMN genotyperesulttext TO genotypetext; + END IF; +END $$; + ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS antibodyTitre varchar(255); ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS performedByReferenceLaboratory boolean; ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS retestRequested boolean default false; From 67899d64c740dae35edca2e26b706abc558b1358 Mon Sep 17 00:00:00 2001 From: Harold Date: Wed, 1 Apr 2026 17:08:26 +0300 Subject: [PATCH 21/55] #13711 - Update Android app to support multiple contact proximities. Migrate contactProximity from single enum to Set in the Android app to match the API/backend changes. --- .../src/main/resources/captions.properties | 1 + .../main/resources/descriptions.properties | 1 + .../app/backend/common/AbstractAdoDao.java | 4 +- .../app/backend/common/DatabaseHelper.java | 49 ++++++------ .../sormas/app/backend/contact/Contact.java | 46 +++++++++-- .../app/backend/contact/ContactDao.java | 36 ++++----- .../app/backend/contact/ContactDtoHelper.java | 4 +- .../controls/ControlTextReadField.java | 14 ++++ .../app/contact/edit/ContactEditFragment.java | 74 ++++++++++-------- .../app/contact/edit/ContactNewFragment.java | 76 +++++++++++-------- .../app/util/TextViewBindingAdapters.java | 13 ++++ .../layout/fragment_contact_edit_layout.xml | 10 +-- .../layout/fragment_contact_new_layout.xml | 6 +- .../layout/fragment_contact_read_layout.xml | 4 +- .../row_read_contact_list_item_layout.xml | 4 +- 15 files changed, 215 insertions(+), 127 deletions(-) diff --git a/sormas-api/src/main/resources/captions.properties b/sormas-api/src/main/resources/captions.properties index 5c48be795db..66ede9cd756 100644 --- a/sormas-api/src/main/resources/captions.properties +++ b/sormas-api/src/main/resources/captions.properties @@ -780,6 +780,7 @@ Contact.contactIdentificationSourceDetails=Contact identification source details Contact.tracingApp=Tracing app Contact.tracingAppDetails=Tracing app details, e.g. name Contact.contactProximity=Type of contact +Contact.contactProximities=Types of contact Contact.contactProximityLongForm=Type of contact - if multiple pick the closest contact proximity Contact.contactStatus=Contact status Contact.deletionReason=Reason for deletion diff --git a/sormas-api/src/main/resources/descriptions.properties b/sormas-api/src/main/resources/descriptions.properties index ef13a8f5d4d..fda7587b57e 100644 --- a/sormas-api/src/main/resources/descriptions.properties +++ b/sormas-api/src/main/resources/descriptions.properties @@ -46,6 +46,7 @@ CasePreviousHospitalization.admissionDate = Please provide the date of admission # Contact Contact.community = Users associated to the responsible region/district/community will be able to access this contact. Contact.contactProximity = Select the type of contact with the case +Contact.contactProximities = Select all applicable types of contact with the case Contact.district = Users associated to the responsible region/district/community will be able to access this contact. Contact.followUpStatus =
  • Under follow-up: The follow-up process is running.
  • Follow-up completed: The follow-up has been completed.
  • Follow-up canceled: The follow-up process has been canceled, e.g. because the person died or the contact became irrelevant.
  • Lost to follow-up: The follow-up process could not be continued, e.g. because the person could not be found.
  • No follow-up: No contact follow-up is being done.
Contact.region = Users associated to the responsible region/district/community will be able to access this contact. diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java index 126b1fcfe4e..3caac314570 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java @@ -50,6 +50,7 @@ import de.symeda.sormas.api.utils.DateHelper; import de.symeda.sormas.app.R; import de.symeda.sormas.app.backend.caze.Case; +import de.symeda.sormas.app.backend.contact.Contact; import de.symeda.sormas.app.backend.environment.Environment; import de.symeda.sormas.app.backend.environment.environmentsample.EnvironmentSample; import de.symeda.sormas.app.backend.feature.FeatureConfiguration; @@ -684,7 +685,8 @@ public ADO mergeOrCreate(ADO source) throws DaoException { || Environment.WATER_USE.equals(property.getName()) || EnvironmentSample.REQUESTED_PATHOGEN_TESTS.equals(property.getName()) || EnvironmentSample.WEATHER_CONDITIONS.equals(property.getName()) - || User.LIMITED_DISEASES.equals(property.getName())) + || User.LIMITED_DISEASES.equals(property.getName()) + || Contact.CONTACT_PROXIMITIES.equals(property.getName())) continue; // we now have to write the value from source into target and base diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java index f14c7de5135..c94b603765f 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java @@ -190,7 +190,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public static final String DATABASE_NAME = "sormas.db"; // any time you make changes to your database objects, you may have to increase the database version - public static final int DATABASE_VERSION = 360; + public static final int DATABASE_VERSION = 361; private static DatabaseHelper instance = null; @@ -3201,30 +3201,33 @@ public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN septicaemia varchar(255);"); getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN whoopsound varchar(255);"); - case 360: + case 360: currentVersion = 360; - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN acuteEncephalitis varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN eggyBurps varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN weightLoss varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN weightLossAmount float8;"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN reoccurrence varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN symptomCurrentStatus varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN durationOfSymptoms int;"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN overnightStayRequired varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN bloating varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN difficultyBreathingDuringMeals varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN paradoxicalBreathing varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN respiratoryFatigue varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN parentTimeOffWork varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN timeOffWorkDays float8;"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN unknownSymptom varchar(255);"); - getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN skinRashOnsetDate timestamp;"); - - - // ATTENTION: break should only be done after last version - break; - + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN acuteEncephalitis varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN eggyBurps varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN weightLoss varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN weightLossAmount float8;"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN reoccurrence varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN symptomCurrentStatus varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN durationOfSymptoms int;"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN overnightStayRequired varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN bloating varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN difficultyBreathingDuringMeals varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN paradoxicalBreathing varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN respiratoryFatigue varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN parentTimeOffWork varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN timeOffWorkDays float8;"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN unknownSymptom varchar(255);"); + getDao(Symptoms.class).executeRaw("ALTER TABLE symptoms ADD COLUMN skinRashOnsetDate timestamp;"); + + case 361: + currentVersion = 361; + getDao(Contact.class).executeRaw("ALTER TABLE contacts ADD COLUMN contactProximities varchar(512);"); + getDao(Contact.class).executeRaw( + "UPDATE contacts SET contactProximities = '[\"' || contactProximity || '\"]' WHERE contactProximity IS NOT NULL AND contactProximity != '';"); + // ATTENTION: break should only be done after last version + break; default: throw new IllegalStateException("onUpgrade() with unknown oldVersion " + oldVersion); diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java index 9102367b19f..9f82e9bed2b 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java @@ -18,13 +18,19 @@ import static de.symeda.sormas.api.utils.FieldConstraints.CHARACTER_LIMIT_BIG; import static de.symeda.sormas.api.utils.FieldConstraints.CHARACTER_LIMIT_DEFAULT; +import java.lang.reflect.Type; import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import com.j256.ormlite.field.DataType; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; @@ -76,7 +82,8 @@ public class Contact extends PseudonymizableAdo { public static final String CONTACT_IDENTIFICATION_SOURCE_DETAILS = "contactIdentificationSourceDetails"; public static final String TRACING_APP = "tracingApp"; public static final String TRACING_APP_DETAILS = "tracingAppDetails"; - public static final String CONTACT_PROXIMITY = "contactProximity"; + public static final String CONTACT_PROXIMITIES = "contactProximities"; + public static final String CONTACT_PROXIMITIES_JSON = "contactProximitiesJson"; public static final String CONTACT_CLASSIFICATION = "contactClassification"; public static final String FOLLOW_UP_STATUS = "followUpStatus"; public static final String FOLLOW_UP_COMMENT = "followUpComment"; @@ -137,8 +144,9 @@ public class Contact extends PseudonymizableAdo { private TracingApp tracingApp; @DatabaseField private String tracingAppDetails; - @Enumerated(EnumType.STRING) - private ContactProximity contactProximity; + @Column(name = "contactProximities", length = CHARACTER_LIMIT_DEFAULT) + private String contactProximitiesJson; + private Set contactProximities; @Enumerated(EnumType.STRING) private ContactClassification contactClassification; @Enumerated(EnumType.STRING) @@ -346,12 +354,36 @@ public void setTracingAppDetails(String tracingAppDetails) { this.tracingAppDetails = tracingAppDetails; } - public ContactProximity getContactProximity() { - return contactProximity; + public String getContactProximitiesJson() { + return contactProximitiesJson; + } + + public void setContactProximitiesJson(String contactProximitiesJson) { + this.contactProximitiesJson = contactProximitiesJson; + } + + public Set getContactProximities() { + if (contactProximities == null) { + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + contactProximities = gson.fromJson(contactProximitiesJson, type); + } + if (contactProximities == null) { + contactProximities = new HashSet<>(); + } + return contactProximities; } - public void setContactProximity(ContactProximity contactProximity) { - this.contactProximity = contactProximity; + public void setContactProximities(Set contactProximities) { + if (contactProximities == null) { + this.contactProximities = null; + this.contactProximitiesJson = null; + } else { + this.contactProximities = contactProximities; + Gson gson = new Gson(); + contactProximitiesJson = gson.toJson(contactProximities.stream().map(ContactProximity::name).collect(Collectors.toSet())); + } } public FollowUpStatus getFollowUpStatus() { diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/ContactDao.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/ContactDao.java index 17326caca2d..36b16d98369 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/ContactDao.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/ContactDao.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Set; import org.apache.commons.lang3.StringUtils; @@ -160,15 +161,16 @@ public Contact saveAndSnapshot(final Contact contact) throws DaoException { /** * This is only the status. On the server we also update the follow up unitl field - * + * * @param contact */ private void updateFollowUpStatus(Contact contact) { Disease disease = contact.getDisease(); boolean changeStatus = contact.getFollowUpStatus() != FollowUpStatus.CANCELED && contact.getFollowUpStatus() != FollowUpStatus.LOST; - ContactProximity contactProximity = contact.getContactProximity(); - if (!DiseaseConfigurationCache.getInstance().hasFollowUp(disease) || (contactProximity != null && !contactProximity.hasFollowUp())) { + Set contactProximities = contact.getContactProximities(); + boolean noFollowUpProximity = !contactProximities.isEmpty() && contactProximities.stream().noneMatch(ContactProximity::hasFollowUp); + if (!DiseaseConfigurationCache.getInstance().hasFollowUp(disease) || noFollowUpProximity) { contact.setFollowUpUntil(null); if (changeStatus) { contact.setFollowUpStatus(FollowUpStatus.NO_FOLLOW_UP); @@ -220,18 +222,18 @@ private QueryBuilder buildQueryBuilder(ContactCriteria contactCri // Only use user filter if no restricting case is specified if (contactCriteria.getIncludeContactsFromOtherJurisdictions().equals(false)) { // whereStatements.add(where.or(createJurisdictionFilterForCase(where), createJurisdictionFilter(where))); - Where caseJurisdictionWhere = createJurisdictionFilterForCase(where); - Where jurisdictionWhere = createJurisdictionFilter(where); - if (caseJurisdictionWhere != null && jurisdictionWhere != null) { - whereStatements.add(where.or(caseJurisdictionWhere, jurisdictionWhere)); - } else { - if (caseJurisdictionWhere != null) { - whereStatements.add(caseJurisdictionWhere); - } - if (jurisdictionWhere != null) { - whereStatements.add(jurisdictionWhere); - } - } + Where caseJurisdictionWhere = createJurisdictionFilterForCase(where); + Where jurisdictionWhere = createJurisdictionFilter(where); + if (caseJurisdictionWhere != null && jurisdictionWhere != null) { + whereStatements.add(where.or(caseJurisdictionWhere, jurisdictionWhere)); + } else { + if (caseJurisdictionWhere != null) { + whereStatements.add(caseJurisdictionWhere); + } + if (jurisdictionWhere != null) { + whereStatements.add(jurisdictionWhere); + } + } } if (contactCriteria != null) { @@ -295,7 +297,7 @@ public Where createJurisdictionFilterForCase(Where if (!whereJurisdictionFilterStatements.isEmpty()) { where.or(whereJurisdictionFilterStatements.size()); - return where; + return where; } return null; } @@ -329,7 +331,7 @@ public Where createJurisdictionFilter(Where where) if (!whereUserFilterStatements.isEmpty()) { where.or(whereUserFilterStatements.size()); - return where; + return where; } return null; diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/ContactDtoHelper.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/ContactDtoHelper.java index e9abf1f423a..fc52049a2a6 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/ContactDtoHelper.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/ContactDtoHelper.java @@ -96,7 +96,7 @@ public void fillInnerFromDto(Contact target, ContactDto source) { target.setTracingApp(source.getTracingApp()); target.setTracingAppDetails(source.getTracingAppDetails()); target.setContactCategory(source.getContactCategory()); - target.setContactProximity(source.getContactProximity()); + target.setContactProximities(source.getContactProximities()); target.setContactProximityDetails(source.getContactProximityDetails()); target.setContactClassification(source.getContactClassification()); target.setContactStatus(source.getContactStatus()); @@ -229,7 +229,7 @@ public void fillInnerFromAdo(ContactDto target, Contact source) { target.setTracingApp(source.getTracingApp()); target.setTracingAppDetails(source.getTracingAppDetails()); target.setContactCategory(source.getContactCategory()); - target.setContactProximity(source.getContactProximity()); + target.setContactProximities(source.getContactProximities()); target.setContactProximityDetails(source.getContactProximityDetails()); target.setContactClassification(source.getContactClassification()); target.setContactStatus(source.getContactStatus()); diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/component/controls/ControlTextReadField.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/component/controls/ControlTextReadField.java index b0006112548..437c4540f52 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/component/controls/ControlTextReadField.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/component/controls/ControlTextReadField.java @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.util.Date; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -545,4 +546,17 @@ public static void setPathogenValue(ControlTextReadField textField, Pathogen pat textField.setValue(pathogen.getCaption() + (StringUtils.isNotBlank(pathogenDetails) ? " (" + pathogenDetails + ")" : "")); } } + + // Set of Enums + @BindingAdapter(value = { + "value", + "defaultValue" }, requireAll = false) + public static void setValue(ControlTextReadField textField, Set> enumSet, String defaultValue) { + if (enumSet == null || enumSet.isEmpty()) { + textField.setValue(textField.getDefaultValue(defaultValue)); + textField.applyDefaultValueStyle(); + } else { + textField.setValue(enumSet.stream().map(Enum::toString).collect(Collectors.joining(", "))); + } + } } diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java index 25cc150e405..3ab4c95a4e6 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Set; import de.symeda.sormas.api.CountryHelper; import de.symeda.sormas.api.Disease; @@ -207,16 +208,16 @@ public void onLayoutBinding(FragmentContactEditLayoutBinding contentBinding) { record.getCommunity()); contentBinding.contactDisease.initializeSpinner(diseaseList); contentBinding.contactDisease.addValueChangedListener(e -> { - contentBinding.contactContactProximity.setVisibility(e.getValue() == null ? GONE : VISIBLE); - contentBinding.contactContactProximity.clear(true); - contentBinding.contactContactProximity - .setItems(DataUtils.toItems(Arrays.asList(ContactProximity.getValues((Disease) e.getValue(), ConfigProvider.getServerLocale())))); + contentBinding.contactContactProximities.setVisibility(e.getValue() == null ? GONE : VISIBLE); + contentBinding.contactContactProximities.setValue(null); + contentBinding.contactContactProximities + .setItems(Arrays.asList(ContactProximity.getValues((Disease) e.getValue(), ConfigProvider.getServerLocale()))); }); contentBinding.contactFirstContactDate.addValueChangedListener(e -> contentBinding.contactLastContactDate.setRequired(e.getValue() != null)); - contentBinding.contactContactProximity - .setItems(DataUtils.toItems(Arrays.asList(ContactProximity.getValues(record.getDisease(), ConfigProvider.getServerLocale())))); + contentBinding.contactContactProximities + .setItems(Arrays.asList(ContactProximity.getValues(record.getDisease(), ConfigProvider.getServerLocale()))); contentBinding.contactQuarantine.addValueChangedListener(e -> { boolean visible = QuarantineType.HOME.equals(contentBinding.contactQuarantine.getValue()) @@ -338,8 +339,8 @@ private void reduceQuarantine() { } }); if (ConfigProvider.isConfiguredServer(CountryHelper.COUNTRY_CODE_GERMANY)) { - contentBinding.contactContactProximity.addValueChangedListener( - e -> updateContactCategory(contentBinding, (ContactProximity) contentBinding.contactContactProximity.getValue())); + contentBinding.contactContactProximities.addValueChangedListener( + e -> updateContactCategory(contentBinding, (Set) contentBinding.contactContactProximities.getValue())); } else { contentBinding.contactContactIdentificationSource.setVisibility(GONE); contentBinding.contactContactProximityDetails.setVisibility(GONE); @@ -370,34 +371,43 @@ private void reduceQuarantine() { /* * Only used for Systems in Germany. Follows specific rules for german systems. */ - private void updateContactCategory(FragmentContactEditLayoutBinding contentBinding, ContactProximity proximity) { - if (proximity != null) { - switch (proximity) { - case FACE_TO_FACE_LONG: - case TOUCHED_FLUID: - case AEROSOL: - contentBinding.contactContactCategory.setValue(ContactCategory.HIGH_RISK); - break; - case MEDICAL_UNSAFE: - contentBinding.contactContactCategory.setValue(ContactCategory.HIGH_RISK_MED); - break; - case MEDICAL_LIMITED: - contentBinding.contactContactCategory.setValue(ContactCategory.MEDIUM_RISK_MED); - break; - case SAME_ROOM: - case FACE_TO_FACE_SHORT: - case MEDICAL_SAME_ROOM: - contentBinding.contactContactCategory.setValue(ContactCategory.LOW_RISK); - break; - case MEDICAL_DISTANT: - case MEDICAL_SAFE: - contentBinding.contactContactCategory.setValue(ContactCategory.NO_RISK); - break; - default: + private void updateContactCategory(FragmentContactEditLayoutBinding contentBinding, Set proximities) { + if (proximities != null && !proximities.isEmpty()) { + ContactCategory highestCategory = null; + for (ContactProximity proximity : proximities) { + ContactCategory category = getContactCategoryForProximity(proximity); + if (category != null && (highestCategory == null || category.ordinal() < highestCategory.ordinal())) { + highestCategory = category; + } + } + if (highestCategory != null) { + contentBinding.contactContactCategory.setValue(highestCategory); } } } + private ContactCategory getContactCategoryForProximity(ContactProximity proximity) { + switch (proximity) { + case FACE_TO_FACE_LONG: + case TOUCHED_FLUID: + case AEROSOL: + return ContactCategory.HIGH_RISK; + case MEDICAL_UNSAFE: + return ContactCategory.HIGH_RISK_MED; + case MEDICAL_LIMITED: + return ContactCategory.MEDIUM_RISK_MED; + case SAME_ROOM: + case FACE_TO_FACE_SHORT: + case MEDICAL_SAME_ROOM: + return ContactCategory.LOW_RISK; + case MEDICAL_DISTANT: + case MEDICAL_SAFE: + return ContactCategory.NO_RISK; + default: + return null; + } + } + @Override public void onAfterLayoutBinding(FragmentContactEditLayoutBinding contentBinding) { setUpFieldVisibilities(contentBinding); diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java index 333343e256e..0079d367e6b 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Calendar; import java.util.List; +import java.util.Set; import de.symeda.sormas.api.CountryHelper; import de.symeda.sormas.api.Disease; @@ -115,19 +116,19 @@ public void onLayoutBinding(FragmentContactNewLayoutBinding contentBinding) { diseaseList, record.getDisease() != null ? record.getDisease() : DiseaseConfigurationCache.getInstance().getDefaultDisease(), e -> { - contentBinding.contactContactProximity.setVisibility(e.getValue() == null ? GONE : VISIBLE); + contentBinding.contactContactProximities.setVisibility(e.getValue() == null ? GONE : VISIBLE); if (ConfigProvider.isConfiguredServer(CountryHelper.COUNTRY_CODE_GERMANY)) { contentBinding.contactContactProximityDetails.setVisibility(e.getValue() == null ? GONE : VISIBLE); contentBinding.contactContactCategory.setVisibility(e.getValue() == null ? GONE : VISIBLE); } - contentBinding.contactContactProximity.clear(); - contentBinding.contactContactProximity - .setItems(DataUtils.toItems(Arrays.asList(ContactProximity.getValues((Disease) e.getValue(), ConfigProvider.getServerLocale())))); + contentBinding.contactContactProximities.setValue(null); + contentBinding.contactContactProximities + .setItems(Arrays.asList(ContactProximity.getValues((Disease) e.getValue(), ConfigProvider.getServerLocale()))); }); if (ConfigProvider.isConfiguredServer(CountryHelper.COUNTRY_CODE_GERMANY)) { - contentBinding.contactContactProximity.addValueChangedListener( - e -> updateContactCategory(contentBinding, (ContactProximity) contentBinding.contactContactProximity.getValue())); + contentBinding.contactContactProximities.addValueChangedListener( + e -> updateContactCategory(contentBinding, (Set) contentBinding.contactContactProximities.getValue())); } else { contentBinding.contactContactProximityDetails.setVisibility(GONE); contentBinding.contactContactCategory.setVisibility(GONE); @@ -137,8 +138,8 @@ public void onLayoutBinding(FragmentContactNewLayoutBinding contentBinding) { contentBinding.contactDisease.setVisibility(GONE); contentBinding.contactCaseIdExternalSystem.setVisibility(GONE); contentBinding.contactCaseOrEventInformation.setVisibility(GONE); - contentBinding.contactContactProximity - .setItems(DataUtils.toItems(Arrays.asList(ContactProximity.getValues(sourceCase.getDisease(), ConfigProvider.getServerLocale())))); + contentBinding.contactContactProximities + .setItems(Arrays.asList(ContactProximity.getValues(sourceCase.getDisease(), ConfigProvider.getServerLocale()))); } else { contentBinding.contactDisease.setRequired(true); contentBinding.contactRegion.setRequired(true); @@ -146,7 +147,7 @@ public void onLayoutBinding(FragmentContactNewLayoutBinding contentBinding) { } if (getPrimaryData().getDisease() == null) { - contentBinding.contactContactProximity.setVisibility(GONE); + contentBinding.contactContactProximities.setVisibility(GONE); contentBinding.contactContactProximityDetails.setVisibility(GONE); contentBinding.contactContactCategory.setVisibility(GONE); } @@ -157,34 +158,43 @@ public void onLayoutBinding(FragmentContactNewLayoutBinding contentBinding) { /* * Only used for Systems in Germany. Follows specific rules for german systems. */ - private void updateContactCategory(FragmentContactNewLayoutBinding contentBinding, ContactProximity proximity) { - if (proximity != null) { - switch (proximity) { - case FACE_TO_FACE_LONG: - case TOUCHED_FLUID: - case AEROSOL: - contentBinding.contactContactCategory.setValue(ContactCategory.HIGH_RISK); - break; - case MEDICAL_UNSAFE: - contentBinding.contactContactCategory.setValue(ContactCategory.HIGH_RISK_MED); - break; - case MEDICAL_LIMITED: - contentBinding.contactContactCategory.setValue(ContactCategory.MEDIUM_RISK_MED); - break; - case SAME_ROOM: - case FACE_TO_FACE_SHORT: - case MEDICAL_SAME_ROOM: - contentBinding.contactContactCategory.setValue(ContactCategory.LOW_RISK); - break; - case MEDICAL_DISTANT: - case MEDICAL_SAFE: - contentBinding.contactContactCategory.setValue(ContactCategory.NO_RISK); - break; - default: + private void updateContactCategory(FragmentContactNewLayoutBinding contentBinding, Set proximities) { + if (proximities != null && !proximities.isEmpty()) { + ContactCategory highestCategory = null; + for (ContactProximity proximity : proximities) { + ContactCategory category = getContactCategoryForProximity(proximity); + if (category != null && (highestCategory == null || category.ordinal() < highestCategory.ordinal())) { + highestCategory = category; + } + } + if (highestCategory != null) { + contentBinding.contactContactCategory.setValue(highestCategory); } } } + private ContactCategory getContactCategoryForProximity(ContactProximity proximity) { + switch (proximity) { + case FACE_TO_FACE_LONG: + case TOUCHED_FLUID: + case AEROSOL: + return ContactCategory.HIGH_RISK; + case MEDICAL_UNSAFE: + return ContactCategory.HIGH_RISK_MED; + case MEDICAL_LIMITED: + return ContactCategory.MEDIUM_RISK_MED; + case SAME_ROOM: + case FACE_TO_FACE_SHORT: + case MEDICAL_SAME_ROOM: + return ContactCategory.LOW_RISK; + case MEDICAL_DISTANT: + case MEDICAL_SAFE: + return ContactCategory.NO_RISK; + default: + return null; + } + } + @Override public void onAfterLayoutBinding(FragmentContactNewLayoutBinding contentBinding) { contentBinding.personSex.initializeSpinner(sexList); diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/util/TextViewBindingAdapters.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/util/TextViewBindingAdapters.java index ce3dead3af2..080abd2dfb5 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/util/TextViewBindingAdapters.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/util/TextViewBindingAdapters.java @@ -22,6 +22,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -1229,6 +1231,17 @@ public static void setDependencyParentField(TextView field, TextView dependencyP setVisibilityDependencies(field, visibilityDependencies, true); } + @BindingAdapter(value = { + "value", + "defaultValue" }, requireAll = false) + public static void setValue(TextView textField, Set> enumSet, String defaultValue) { + if (enumSet == null || enumSet.isEmpty()) { + textField.setText(defaultValue != null ? defaultValue : textField.getContext().getResources().getString(R.string.notAnswered)); + } else { + textField.setText(enumSet.stream().map(Enum::toString).collect(Collectors.joining(", "))); + } + } + @BindingAdapter(value = { "value" }) public static void setPathogenValue(TextView textField, Pathogen pathogen) { diff --git a/sormas-app/app/src/main/res/layout/fragment_contact_edit_layout.xml b/sormas-app/app/src/main/res/layout/fragment_contact_edit_layout.xml index cfe6068e26e..eb8ffb588d1 100644 --- a/sormas-app/app/src/main/res/layout/fragment_contact_edit_layout.xml +++ b/sormas-app/app/src/main/res/layout/fragment_contact_edit_layout.xml @@ -377,7 +377,7 @@ app:value="@={data.reportDateTime}" app:required="true" style="@style/ControlSecondOfTwoColumnsStyle" /> - + - @@ -627,4 +627,4 @@ - \ No newline at end of file + diff --git a/sormas-app/app/src/main/res/layout/fragment_contact_new_layout.xml b/sormas-app/app/src/main/res/layout/fragment_contact_new_layout.xml index 14ce1c4ff95..4513fa7a9f5 100644 --- a/sormas-app/app/src/main/res/layout/fragment_contact_new_layout.xml +++ b/sormas-app/app/src/main/res/layout/fragment_contact_new_layout.xml @@ -181,9 +181,9 @@ app:value="@={data.caseOrEventInformation}" style="@style/ControlSingleColumnStyle" /> - diff --git a/sormas-app/app/src/main/res/layout/fragment_contact_read_layout.xml b/sormas-app/app/src/main/res/layout/fragment_contact_read_layout.xml index d7f9011ccee..93166e2c914 100644 --- a/sormas-app/app/src/main/res/layout/fragment_contact_read_layout.xml +++ b/sormas-app/app/src/main/res/layout/fragment_contact_read_layout.xml @@ -437,8 +437,8 @@ + app:value="@{data.contactProximities}" + app:goneIfEmpty="@{data.contactProximities}" /> Date: Wed, 1 Apr 2026 17:44:23 +0300 Subject: [PATCH 22/55] Resolve PR comments in-line with the initial commit --- .../sormas/app/backend/common/DatabaseHelper.java | 8 ++++++-- .../symeda/sormas/app/backend/contact/Contact.java | 3 +++ .../component/controls/ControlTextReadField.java | 13 +++++++------ .../app/contact/edit/ContactEditFragment.java | 8 +++----- .../sormas/app/contact/edit/ContactNewFragment.java | 8 +++----- .../symeda/sormas/app/util/FormBindingAdapters.java | 3 +++ 6 files changed, 25 insertions(+), 18 deletions(-) diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java index c94b603765f..cb59ac9350a 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java @@ -3222,9 +3222,13 @@ public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int case 361: currentVersion = 361; - getDao(Contact.class).executeRaw("ALTER TABLE contacts ADD COLUMN contactProximities varchar(512);"); + if (columnDoesNotExist("contacts", "contactProximities")) { + getDao(Contact.class).executeRaw("ALTER TABLE contacts ADD COLUMN contactProximities varchar(512);"); + } getDao(Contact.class).executeRaw( - "UPDATE contacts SET contactProximities = '[\"' || contactProximity || '\"]' WHERE contactProximity IS NOT NULL AND contactProximity != '';"); + "UPDATE contacts SET contactProximities = '[\"' || contactProximity || '\"]' " + + "WHERE (contactProximities IS NULL OR contactProximities = '') " + + "AND contactProximity IS NOT NULL AND contactProximity != '';"); // ATTENTION: break should only be done after last version break; diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java index 9f82e9bed2b..1d7fb886c5f 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java @@ -28,6 +28,7 @@ import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.Transient; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -146,6 +147,7 @@ public class Contact extends PseudonymizableAdo { private String tracingAppDetails; @Column(name = "contactProximities", length = CHARACTER_LIMIT_DEFAULT) private String contactProximitiesJson; + @Transient private Set contactProximities; @Enumerated(EnumType.STRING) private ContactClassification contactClassification; @@ -360,6 +362,7 @@ public String getContactProximitiesJson() { public void setContactProximitiesJson(String contactProximitiesJson) { this.contactProximitiesJson = contactProximitiesJson; + this.contactProximities = null; } public Set getContactProximities() { diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/component/controls/ControlTextReadField.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/component/controls/ControlTextReadField.java index 437c4540f52..5746dcb1dee 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/component/controls/ControlTextReadField.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/component/controls/ControlTextReadField.java @@ -552,11 +552,12 @@ public static void setPathogenValue(ControlTextReadField textField, Pathogen pat "value", "defaultValue" }, requireAll = false) public static void setValue(ControlTextReadField textField, Set> enumSet, String defaultValue) { - if (enumSet == null || enumSet.isEmpty()) { - textField.setValue(textField.getDefaultValue(defaultValue)); - textField.applyDefaultValueStyle(); - } else { - textField.setValue(enumSet.stream().map(Enum::toString).collect(Collectors.joining(", "))); - } + setValue( + textField, + enumSet == null || enumSet.isEmpty() ? null : enumSet.stream().map(Enum::toString).collect(Collectors.joining(", ")), + null, + null, + defaultValue, + enumSet); } } diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java index 3ab4c95a4e6..1c6a111f5ba 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java @@ -372,18 +372,16 @@ private void reduceQuarantine() { * Only used for Systems in Germany. Follows specific rules for german systems. */ private void updateContactCategory(FragmentContactEditLayoutBinding contentBinding, Set proximities) { - if (proximities != null && !proximities.isEmpty()) { - ContactCategory highestCategory = null; + ContactCategory highestCategory = null; + if (proximities != null) { for (ContactProximity proximity : proximities) { ContactCategory category = getContactCategoryForProximity(proximity); if (category != null && (highestCategory == null || category.ordinal() < highestCategory.ordinal())) { highestCategory = category; } } - if (highestCategory != null) { - contentBinding.contactContactCategory.setValue(highestCategory); - } } + contentBinding.contactContactCategory.setValue(highestCategory); } private ContactCategory getContactCategoryForProximity(ContactProximity proximity) { diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java index 0079d367e6b..fb071cfc22b 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java @@ -159,18 +159,16 @@ public void onLayoutBinding(FragmentContactNewLayoutBinding contentBinding) { * Only used for Systems in Germany. Follows specific rules for german systems. */ private void updateContactCategory(FragmentContactNewLayoutBinding contentBinding, Set proximities) { - if (proximities != null && !proximities.isEmpty()) { - ContactCategory highestCategory = null; + ContactCategory highestCategory = null; + if (proximities != null) { for (ContactProximity proximity : proximities) { ContactCategory category = getContactCategoryForProximity(proximity); if (category != null && (highestCategory == null || category.ordinal() < highestCategory.ordinal())) { highestCategory = category; } } - if (highestCategory != null) { - contentBinding.contactContactCategory.setValue(highestCategory); - } } + contentBinding.contactContactCategory.setValue(highestCategory); } private ContactCategory getContactCategoryForProximity(ContactProximity proximity) { diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/util/FormBindingAdapters.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/util/FormBindingAdapters.java index 197ae9dd8f5..ab248a18426 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/util/FormBindingAdapters.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/util/FormBindingAdapters.java @@ -29,6 +29,7 @@ import androidx.core.content.ContextCompat; import androidx.databinding.BindingAdapter; +import java.util.Collection; import java.util.List; import de.symeda.sormas.api.sample.PathogenTestResultType; @@ -228,6 +229,8 @@ private static boolean isEmptyObject(Object o) { return true; } else if (o instanceof String && DataHelper.isNullOrEmpty((String) o)) { return true; + } else if (o instanceof Collection && ((Collection) o).isEmpty()) { + return true; } else if (o instanceof YesNoUnknown && YesNoUnknown.NO.equals(o)) { return true; } else if (o instanceof SymptomState && SymptomState.NO.equals(o)) { From 00cd76b3cbc1ea85e7e289360f7ede5bb0068253 Mon Sep 17 00:00:00 2001 From: Harold Date: Wed, 1 Apr 2026 17:53:21 +0300 Subject: [PATCH 23/55] Resolve PR comments in-line with the initial commit --- .../java/de/symeda/sormas/app/util/TextViewBindingAdapters.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/util/TextViewBindingAdapters.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/util/TextViewBindingAdapters.java index 080abd2dfb5..ae1ff50c011 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/util/TextViewBindingAdapters.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/util/TextViewBindingAdapters.java @@ -1238,7 +1238,7 @@ public static void setValue(TextView textField, Set> enumSet, if (enumSet == null || enumSet.isEmpty()) { textField.setText(defaultValue != null ? defaultValue : textField.getContext().getResources().getString(R.string.notAnswered)); } else { - textField.setText(enumSet.stream().map(Enum::toString).collect(Collectors.joining(", "))); + textField.setText(enumSet.stream().map(Enum::toString).sorted().collect(Collectors.joining(", "))); } } From ebb192b05f87e4f6ca5b4e8886889a8c291ac0b1 Mon Sep 17 00:00:00 2001 From: Harold Date: Wed, 1 Apr 2026 18:13:44 +0300 Subject: [PATCH 24/55] Avoid silent drift between the transient set and persisted JSON & Add null check in both updateContactCategory() and getContactCategoryForProximity() to handle malformed proximity data --- .../java/de/symeda/sormas/app/backend/contact/Contact.java | 6 +++--- .../symeda/sormas/app/contact/edit/ContactEditFragment.java | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java index 1d7fb886c5f..cf139b59877 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java @@ -375,7 +375,7 @@ public Set getContactProximities() { if (contactProximities == null) { contactProximities = new HashSet<>(); } - return contactProximities; + return Collections.unmodifiableSet(contactProximities); } public void setContactProximities(Set contactProximities) { @@ -383,9 +383,9 @@ public void setContactProximities(Set contactProximities) { this.contactProximities = null; this.contactProximitiesJson = null; } else { - this.contactProximities = contactProximities; + this.contactProximities = new HashSet<>(contactProximities); Gson gson = new Gson(); - contactProximitiesJson = gson.toJson(contactProximities.stream().map(ContactProximity::name).collect(Collectors.toSet())); + contactProximitiesJson = gson.toJson(contactProximities.stream().map(ContactProximity::name).sorted().collect(Collectors.toSet())); } } diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java index 1c6a111f5ba..00ca7e782e7 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactEditFragment.java @@ -375,6 +375,9 @@ private void updateContactCategory(FragmentContactEditLayoutBinding contentBindi ContactCategory highestCategory = null; if (proximities != null) { for (ContactProximity proximity : proximities) { + if (proximity == null) { + continue; + } ContactCategory category = getContactCategoryForProximity(proximity); if (category != null && (highestCategory == null || category.ordinal() < highestCategory.ordinal())) { highestCategory = category; @@ -385,6 +388,9 @@ private void updateContactCategory(FragmentContactEditLayoutBinding contentBindi } private ContactCategory getContactCategoryForProximity(ContactProximity proximity) { + if (proximity == null) { + return null; + } switch (proximity) { case FACE_TO_FACE_LONG: case TOUCHED_FLUID: From 0d7ec2ca258f80b9a9ae6853db9691550fceb7c8 Mon Sep 17 00:00:00 2001 From: Harold Date: Wed, 1 Apr 2026 18:18:58 +0300 Subject: [PATCH 25/55] Fix missing import --- .../main/java/de/symeda/sormas/app/backend/contact/Contact.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java index cf139b59877..1aeabc8035b 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/contact/Contact.java @@ -19,6 +19,7 @@ import static de.symeda.sormas.api.utils.FieldConstraints.CHARACTER_LIMIT_DEFAULT; import java.lang.reflect.Type; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Set; From 906c3a1bf279a3ffcdad2800d10eb812e1d795a1 Mon Sep 17 00:00:00 2001 From: Harold Date: Wed, 1 Apr 2026 18:38:34 +0300 Subject: [PATCH 26/55] Prevents NPE when Gson deserializes unknown enum values as null entries in the Set --- .../symeda/sormas/app/contact/edit/ContactNewFragment.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java index fb071cfc22b..0643a63a969 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/contact/edit/ContactNewFragment.java @@ -162,6 +162,9 @@ private void updateContactCategory(FragmentContactNewLayoutBinding contentBindin ContactCategory highestCategory = null; if (proximities != null) { for (ContactProximity proximity : proximities) { + if (proximity == null) { + continue; + } ContactCategory category = getContactCategoryForProximity(proximity); if (category != null && (highestCategory == null || category.ordinal() < highestCategory.ordinal())) { highestCategory = category; @@ -172,6 +175,9 @@ private void updateContactCategory(FragmentContactNewLayoutBinding contentBindin } private ContactCategory getContactCategoryForProximity(ContactProximity proximity) { + if (proximity == null) { + return null; + } switch (proximity) { case FACE_TO_FACE_LONG: case TOUCHED_FLUID: From 115da069dc054fe4a64d3fd9033165a30e79608a Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Wed, 1 Apr 2026 20:11:14 +0200 Subject: [PATCH 27/55] Changed OpenAPI Canary to fail on incompatible --- .github/workflows/openapi_canary.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/openapi_canary.yml b/.github/workflows/openapi_canary.yml index 11406966c44..be9a2168ef1 100644 --- a/.github/workflows/openapi_canary.yml +++ b/.github/workflows/openapi_canary.yml @@ -2,16 +2,9 @@ name: OpenAPI Canary on: - workflow_dispatch: - inputs: - reason: - description: 'Reason for manual run' - required: false - default: 'Testing on specific branch' -# Disabled until fixing the issue with the diff tool -# schedule: + schedule: # 2.30 UTC -# - cron: '30 2 * * *' + - cron: '30 2 * * *' jobs: canary: @@ -31,9 +24,9 @@ jobs: ref: master path: base - # --fail-on-incompatible is also possible + # --fail-on-changed is also possible (fails on any change, including backward-compatible) - name: Run OpenAPI Diff for external journal API run: | - java -jar /app/openapi-diff.jar --fail-on-changed base/sormas-rest/swagger.yaml \ + java -jar /app/openapi-diff.jar --fail-on-incompatible base/sormas-rest/swagger.yaml \ head/sormas-rest/swagger.yaml From a179d89476cc440f394a0ed2144898d3694e6680 Mon Sep 17 00:00:00 2001 From: raulbob Date: Thu, 2 Apr 2026 11:53:57 +0200 Subject: [PATCH 28/55] Changed image to ubuntu-latest for app ci action --- .github/workflows/sormas_app_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sormas_app_ci.yml b/.github/workflows/sormas_app_ci.yml index 22c5bbd70bd..23828c0f625 100644 --- a/.github/workflows/sormas_app_ci.yml +++ b/.github/workflows/sormas_app_ci.yml @@ -26,7 +26,7 @@ on: jobs: test: name: android app test - runs-on: macos-latest + runs-on: ubuntu-latest strategy: matrix: # Even though we are using macos machines which should be faster thanks to hardware accelerations From 19453ba5b8e6f576b31abba7068eb09184f6b2fe Mon Sep 17 00:00:00 2001 From: Karnaiah Pesula Date: Thu, 2 Apr 2026 11:36:21 +0200 Subject: [PATCH 29/55] Resolved test failures --- .../de/symeda/sormas/api/i18n/Captions.java | 1 + .../symeda/sormas/api/i18n/Descriptions.java | 1 + .../app/backend/common/AbstractAdoDao.java | 187 ++++++++++-------- .../app/backend/common/DatabaseHelper.java | 4 + .../app/backend/sample/PathogenTest.java | 10 +- .../fragment_pathogen_test_edit_layout.xml | 5 +- .../fragment_pathogen_test_read_layout.xml | 2 +- .../sormas/backend/util/PatchHelper.java | 4 +- 8 files changed, 119 insertions(+), 95 deletions(-) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java index d2543b9e84d..8771bcaa829 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java @@ -1021,6 +1021,7 @@ public interface Captions { String Contact_contactIdentificationSourceDetails = "Contact.contactIdentificationSourceDetails"; String Contact_contactOfficer = "Contact.contactOfficer"; String Contact_contactOfficerUuid = "Contact.contactOfficerUuid"; + String Contact_contactProximities = "Contact.contactProximities"; String Contact_contactProximity = "Contact.contactProximity"; String Contact_contactProximityDetails = "Contact.contactProximityDetails"; String Contact_contactProximityLongForm = "Contact.contactProximityLongForm"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Descriptions.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Descriptions.java index 12b932a53dc..08c327efe59 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Descriptions.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Descriptions.java @@ -31,6 +31,7 @@ public interface Descriptions { String CaseHospitalization_isolated = "CaseHospitalization.isolated"; String CasePreviousHospitalization_admissionDate = "CasePreviousHospitalization.admissionDate"; String Contact_community = "Contact.community"; + String Contact_contactProximities = "Contact.contactProximities"; String Contact_contactProximity = "Contact.contactProximity"; String Contact_district = "Contact.district"; String Contact_followUpStatus = "Contact.followUpStatus"; diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java index 3caac314570..dff4d5fe145 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java @@ -85,7 +85,7 @@ public ADO queryUuid(String uuid) { try { List results = queryBuilder().where().eq(AbstractDomainObject.UUID, uuid).and().eq(AbstractDomainObject.SNAPSHOT, false).query(); - if (results.size() == 0) { + if (results.isEmpty()) { return null; } else if (results.size() == 1) { return results.get(0); @@ -111,13 +111,13 @@ public List queryUuids(List uuids) { public ADO queryUuidReference(String uuid) { try { List results = queryBuilder() - .selectColumns(AbstractDomainObject.ID, AbstractDomainObject.UUID, AbstractDomainObject.MODIFIED, AbstractDomainObject.SNAPSHOT) - .where() - .eq(AbstractDomainObject.UUID, uuid) - .and() - .eq(AbstractDomainObject.SNAPSHOT, false) - .query(); - if (results.size() == 0) { + .selectColumns(AbstractDomainObject.ID, AbstractDomainObject.UUID, AbstractDomainObject.MODIFIED, AbstractDomainObject.SNAPSHOT) + .where() + .eq(AbstractDomainObject.UUID, uuid) + .and() + .eq(AbstractDomainObject.SNAPSHOT, false) + .query(); + if (results.isEmpty()) { return null; } else if (results.size() == 1) { return results.get(0); @@ -189,7 +189,7 @@ public ADO querySnapshotByUuid(String uuid) { try { List results = queryBuilder().where().eq(AbstractDomainObject.UUID, uuid).and().eq(AbstractDomainObject.SNAPSHOT, true).query(); - if (results.size() == 0) { + if (results.isEmpty()) { return null; } else if (results.size() == 1) { return results.get(0); @@ -296,16 +296,16 @@ public List queryForObsolete() { if (DatabaseHelper.getFeatureConfigurationDao().isFeatureEnabled(FeatureType.LIMITED_SYNCHRONIZATION)) { Integer maxChangeDatePeriod = DatabaseHelper.getFeatureConfigurationDao() - .getIntegerPropertyValue(FeatureType.LIMITED_SYNCHRONIZATION, FeatureTypeProperty.MAX_CHANGE_DATE_PERIOD); + .getIntegerPropertyValue(FeatureType.LIMITED_SYNCHRONIZATION, FeatureTypeProperty.MAX_CHANGE_DATE_PERIOD); if (maxChangeDatePeriod != null && maxChangeDatePeriod >= 0) { Date maxChangeDate = DateHelper.getStartOfDay(DateHelper.subtractDays(new Date(), maxChangeDatePeriod)); try { QueryBuilder builder = queryBuilder(); Where where = builder.where(); where.and( - where.isNotNull(AbstractDomainObject.CHANGE_DATE), - where.eq(ADO.MODIFIED, false), - where.lt(AbstractDomainObject.CHANGE_DATE, maxChangeDate)); + where.isNotNull(AbstractDomainObject.CHANGE_DATE), + where.eq(ADO.MODIFIED, false), + where.lt(AbstractDomainObject.CHANGE_DATE, maxChangeDate)); return builder.query(); } catch (SQLException e) { throw new RuntimeException(e); @@ -331,12 +331,12 @@ public Date getLatestChangeDate() { String query = "SELECT MAX(" + AbstractDomainObject.CHANGE_DATE + ") FROM " + getTableName(); GenericRawResults maxChangeDateResult = queryRaw( - query, - new DataType[] { - DataType.DATE_LONG }); + query, + new DataType[] { + DataType.DATE_LONG }); try { List dateResults = maxChangeDateResult.getResults(); - if (dateResults.size() > 0) { + if (!dateResults.isEmpty()) { return (Date) dateResults.get(0)[0]; } } catch (SQLException e) { @@ -351,7 +351,7 @@ public Date getLatestChangeDate() { */ protected Date getLatestChangeDateJoin(String joinTableName, String joinColumnName) { String query = "SELECT MAX(jo." + AbstractDomainObject.CHANGE_DATE + ") FROM " + getTableName() + " AS ta" + " LEFT JOIN " + joinTableName - + " AS jo ON jo." + AbstractDomainObject.ID + " = ta." + joinColumnName + "_ID"; + + " AS jo ON jo." + AbstractDomainObject.ID + " = ta." + joinColumnName + "_ID"; return getLatestChangeDateJoinFromQuery(query); } @@ -360,8 +360,8 @@ protected Date getLatestChangeDateJoin(String joinTableName, String joinColumnNa */ protected Date getLatestChangeDateSubJoin(String joinTableName, String joinColumnName, String subJoinTableName) { String query = "SELECT MAX(sjo." + AbstractDomainObject.CHANGE_DATE + ") FROM " + getTableName() + " AS ta" + " LEFT JOIN " + joinTableName - + " AS jo ON jo." + AbstractDomainObject.ID + " = ta." + joinColumnName + "_id" + " LEFT JOIN " + subJoinTableName + " AS sjo ON jo." - + AbstractDomainObject.ID + " = sjo." + joinColumnName + "_id"; + + " AS jo ON jo." + AbstractDomainObject.ID + " = ta." + joinColumnName + "_id" + " LEFT JOIN " + subJoinTableName + " AS sjo ON jo." + + AbstractDomainObject.ID + " = sjo." + joinColumnName + "_id"; return getLatestChangeDateJoinFromQuery(query); } @@ -370,19 +370,19 @@ protected Date getLatestChangeDateSubJoin(String joinTableName, String joinColum */ protected Date getLatestChangeDateSubJoinReverse(String joinTableName, String joinColumnName, String subJoinTableName, String subJoinColumnName) { String query = "SELECT MAX(sjo." + AbstractDomainObject.CHANGE_DATE + ") FROM " + getTableName() + " AS ta" + " LEFT JOIN " + joinTableName - + " AS jo ON jo." + AbstractDomainObject.ID + " = ta." + joinColumnName + "_id" + " LEFT JOIN " + subJoinTableName + " AS sjo ON jo." - + subJoinColumnName + "_id = sjo." + AbstractDomainObject.ID; + + " AS jo ON jo." + AbstractDomainObject.ID + " = ta." + joinColumnName + "_id" + " LEFT JOIN " + subJoinTableName + " AS sjo ON jo." + + subJoinColumnName + "_id = sjo." + AbstractDomainObject.ID; return getLatestChangeDateJoinFromQuery(query); } protected Date getLatestChangeDateJoinFromQuery(String query) { GenericRawResults maxChangeDateResult = queryRaw( - query, - new DataType[] { - DataType.DATE_LONG }); + query, + new DataType[] { + DataType.DATE_LONG }); try { List dateResults = maxChangeDateResult.getResults(); - if (dateResults.size() > 0) { + if (!dateResults.isEmpty()) { return (Date) dateResults.get(0)[0]; } } catch (SQLException e) { @@ -494,7 +494,7 @@ public ADO saveAndSnapshot(ADO ado) throws DaoException { } public void saveCollectionWithSnapshot(Collection existingCollection, Collection modifiedCollection, AbstractDomainObject parent) - throws DaoException { + throws DaoException { // delete no longer existing elements existingCollection.removeAll(modifiedCollection); // ignore kept @@ -678,15 +678,15 @@ public ADO mergeOrCreate(ADO source) throws DaoException { for (PropertyDescriptor property : AdoPropertyHelper.getPropertyDescriptors(source.getClass())) { // ignore some types and specific properties if (!AdoPropertyHelper.isModifiableProperty(property) - || parentProperty.equals(property.getName()) - || property.getReadMethod().isAnnotationPresent(JoinTableReference.class) - || Case.COMPLETENESS.equals(property.getName()) - || FeatureConfiguration.PROPERTIES_MAP.equals(property.getName()) - || Environment.WATER_USE.equals(property.getName()) - || EnvironmentSample.REQUESTED_PATHOGEN_TESTS.equals(property.getName()) - || EnvironmentSample.WEATHER_CONDITIONS.equals(property.getName()) - || User.LIMITED_DISEASES.equals(property.getName()) - || Contact.CONTACT_PROXIMITIES.equals(property.getName())) + || parentProperty.equals(property.getName()) + || property.getReadMethod().isAnnotationPresent(JoinTableReference.class) + || Case.COMPLETENESS.equals(property.getName()) + || FeatureConfiguration.PROPERTIES_MAP.equals(property.getName()) + || Environment.WATER_USE.equals(property.getName()) + || EnvironmentSample.REQUESTED_PATHOGEN_TESTS.equals(property.getName()) + || EnvironmentSample.WEATHER_CONDITIONS.equals(property.getName()) + || User.LIMITED_DISEASES.equals(property.getName()) + || Contact.CONTACT_PROXIMITIES.equals(property.getName())) continue; // we now have to write the value from source into target and base @@ -705,7 +705,7 @@ public ADO mergeOrCreate(ADO source) throws DaoException { if (embeddedSource != null) { // merge it - will return the merged result AbstractDomainObject embeddedCurrent = - DatabaseHelper.getAdoDao(embeddedSource.getClass()).mergeOrCreateWithCast(embeddedSource); + DatabaseHelper.getAdoDao(embeddedSource.getClass()).mergeOrCreateWithCast(embeddedSource); if (embeddedCurrent == null) { throw new IllegalArgumentException("No merge result was created for " + embeddedSource); @@ -719,7 +719,7 @@ public ADO mergeOrCreate(ADO source) throws DaoException { // 4. reference domain objects like a reference to a Person or a District // -> just copy reference value from source into target and base else if (DataHelper.isValueType(property.getPropertyType()) - || AbstractDomainObject.class.isAssignableFrom(property.getPropertyType())) { + || AbstractDomainObject.class.isAssignableFrom(property.getPropertyType())) { Object sourceFieldValue = property.getReadMethod().invoke(source); @@ -736,10 +736,10 @@ else if (DataHelper.isValueType(property.getPropertyType()) if (!DataHelper.equal(snapshotFieldValue, currentFieldValue) && !DataHelper.equal(currentFieldValue, sourceFieldValue)) { // we have a conflict Log.i( - source.getClass().getName(), - "Overriding " + property.getName() + "; Snapshot '" + DataHelper.toStringNullable(snapshotFieldValue) + "'; Yours: '" - + DataHelper.toStringNullable(currentFieldValue) + "'; Server: '" + DataHelper.toStringNullable(sourceFieldValue) - + "'"); + source.getClass().getName(), + "Overriding " + property.getName() + "; Snapshot '" + DataHelper.toStringNullable(snapshotFieldValue) + "'; Yours: '" + + DataHelper.toStringNullable(currentFieldValue) + "'; Server: '" + DataHelper.toStringNullable(sourceFieldValue) + + "'"); conflictStringBuilder.append(I18nProperties.getCaption(source.getI18nPrefix() + "." + property.getName())); @@ -828,7 +828,7 @@ else if (Collection.class.isAssignableFrom(property.getPropertyType())) { } private void mergeCollection(Collection existingCollection, Collection sourceCollection, ADO parent) - throws DaoException { + throws DaoException { try { @@ -839,14 +839,14 @@ private void mergeCollection(Collection existingCollection for (AbstractDomainObject existingEntry : existingCollection) { if (existingEntry.isModified()) { AbstractDomainObject existingSnapshot = - DatabaseHelper.getAdoDao(existingEntry.getClass()).querySnapshotByUuid(existingEntry.getUuid()); + DatabaseHelper.getAdoDao(existingEntry.getClass()).querySnapshotByUuid(existingEntry.getUuid()); if (existingSnapshot == null) { // entry is new (not yet sent to the server) -> keep it continue; } else if (AdoPropertyHelper.hasModifiedProperty(existingEntry, existingSnapshot, true)) { // entry exists and is modified -> inform the user that the changes are deleted conflictStringBuilder - .append(DatabaseHelper.getContext().getResources().getString(R.string.error_modified_list_entry_deleted)); + .append(DatabaseHelper.getContext().getResources().getString(R.string.error_modified_list_entry_deleted)); conflictStringBuilder.append("
"); conflictStringBuilder.append(DatabaseHelper.getContext().getResources().getString(R.string.synclog_yours)); conflictStringBuilder.append(""); @@ -881,7 +881,7 @@ private void mergeCollection(Collection existingCollection // Explicitely used for location-person relation because locations don't have a distinct parent accessor; // This only works if the field has the same name as its class (e.g. Person person) methodName = "set" + parent.getClass().getSimpleName().substring(0, 1).toUpperCase() - + parent.getClass().getSimpleName().substring(1); + + parent.getClass().getSimpleName().substring(1); } parentSetter = resultElement.getClass().getMethod(methodName, parent.getClass()); } @@ -943,18 +943,33 @@ public void accept(ADO ado) throws DaoException { try { // accept all collection elements - Collection sourceCollection = (Collection) property.getReadMethod().invoke(ado); - for (AbstractDomainObject sourceElement : sourceCollection) { - DatabaseHelper.getAdoDao(sourceElement.getClass()).acceptWithCast(sourceElement); - } + Collection sourceCollection = (Collection) property.getReadMethod().invoke(ado); + if(sourceCollection != null && !sourceCollection.isEmpty()) { + for (Object sourceElement : sourceCollection) { + if(!(sourceElement instanceof AbstractDomainObject)) { + continue; + } + AbstractDomainObject e = (AbstractDomainObject) sourceElement; + DatabaseHelper.getAdoDao(e.getClass()).acceptWithCast(e); + } - if (snapshot != null) { - // delete remaining snapshots - Collection snapshotCollection = - (Collection) property.getReadMethod().invoke(snapshot); - snapshotCollection.removeAll(sourceCollection); - for (AbstractDomainObject snapshotElement : snapshotCollection) { - DatabaseHelper.getAdoDao(snapshotElement.getClass()).deleteCascadeWithCast(snapshotElement); + if (snapshot != null) { + // delete remaining snapshots + Collection snapshotCollection = + (Collection) property.getReadMethod().invoke(snapshot); + if (snapshotCollection != null && !snapshotCollection.isEmpty()) { + for (Object snapshotElement : snapshotCollection) { + if (!(snapshotElement instanceof AbstractDomainObject)) { + continue; + } + if(sourceCollection.contains(snapshotElement)) { + continue; + } + final AbstractDomainObject e = (AbstractDomainObject) snapshotElement; + + DatabaseHelper.getAdoDao(e.getClass()).deleteCascadeWithCast(e); + } + } } } } catch (ClassCastException e) { @@ -1013,7 +1028,7 @@ public void deleteCascade(ADO ado) throws SQLException { if (ado.isModified() && ado.getClass().getAnnotation(EmbeddedAdo.class) == null) { // let user know if changes are lost (not for embedded entities) DatabaseHelper.getSyncLogDao() - .createWithParentStack(ado.toString(), DatabaseHelper.getString(R.string.error_changes_dropped_no_access)); + .createWithParentStack(ado.toString(), DatabaseHelper.getString(R.string.error_changes_dropped_no_access)); // TODO include JSON backup } delete(ado); @@ -1070,7 +1085,7 @@ public void deleteInvalid(final List validUuids, Optional 0) { + if (!invalidEntities.isEmpty()) { Log.d(getTableName(), "Deleted invalid entities: " + deletionCounter + " of " + invalidEntities.size()); } @@ -1097,7 +1112,7 @@ public Void call() throws Exception { deletionCounter++; } - if (entities.size() > 0) { + if (!entities.isEmpty()) { Log.d(getTableName(), "Deleted entities: " + deletionCounter + " of " + entities.size()); } return null; @@ -1108,10 +1123,10 @@ public Void call() throws Exception { public List filterMissing(List uuids) { try { GenericRawResults existingUuids = dao.queryRaw( - "SELECT uuid FROM " + getTableName(), - new DataType[] { - DataType.STRING }); - List results = new ArrayList(uuids); + "SELECT uuid FROM " + getTableName(), + new DataType[] { + DataType.STRING }); + List results = new ArrayList<>(uuids); for (Object[] existingUuid : existingUuids) { results.remove(existingUuid[0]); } @@ -1140,9 +1155,9 @@ public ADO build() { Class propertyType = property.getPropertyType(); if (parentProperty.equals(property.getName()) // ignore parent property - || property.getReadMethod().isAnnotationPresent(JoinTableReference.class) - // ignore nullable properties - || (propertyType.isAnnotationPresent(EmbeddedAdo.class) && propertyType.getAnnotation(EmbeddedAdo.class).nullable())) + || property.getReadMethod().isAnnotationPresent(JoinTableReference.class) + // ignore nullable properties + || (propertyType.isAnnotationPresent(EmbeddedAdo.class) && propertyType.getAnnotation(EmbeddedAdo.class).nullable())) continue; // build embedded @@ -1286,8 +1301,8 @@ public void create(ADO data) throws SQLException { int resultRowCount = dao.create(data); if (resultRowCount < 1) throw new SQLException( - "Database entry was not created. Go back and try again.\n" + "Type: " + data.getClass().getSimpleName() + ", UUID: " - + data.getUuid()); + "Database entry was not created. Go back and try again.\n" + "Type: " + data.getClass().getSimpleName() + ", UUID: " + + data.getUuid()); // } catch (SQLException e) { // throw new RuntimeException(e); // } @@ -1329,8 +1344,8 @@ public void delete(ADO data) throws SQLException { int resultRowCount = dao.delete(data); if (resultRowCount < 1) throw new SQLException( - "Database entry was not deleted - go back and try again.\n" + "Type: " + data.getClass().getSimpleName() + ", UUID: " - + data.getUuid()); + "Database entry was not deleted - go back and try again.\n" + "Type: " + data.getClass().getSimpleName() + ", UUID: " + + data.getUuid()); // } catch (SQLException e) { // throw new RuntimeException(e); // } @@ -1383,33 +1398,33 @@ public ConnectionSource getConnectionSource() { // dao utilities protected void addDateFromCriteria( - List> whereStatements, - Where where, - Date dateFrom, - String date) - throws SQLException { + List> whereStatements, + Where where, + Date dateFrom, + String date) + throws SQLException { if (dateFrom != null) { whereStatements.add(where.ge(date, DateHelper.getStartOfDay(dateFrom))); } } protected void addDateToCriteria( - List> whereStatements, - Where where, - Date dateTo, - String date) - throws SQLException { + List> whereStatements, + Where where, + Date dateTo, + String date) + throws SQLException { if (dateTo != null) { whereStatements.add(where.le(date, DateHelper.getEndOfDay(dateTo))); } } protected void addEqualsCriteria( - List> whereStatements, - Where where, - Object criteriaValue, - String columnName) - throws SQLException { + List> whereStatements, + Where where, + Object criteriaValue, + String columnName) + throws SQLException { if (criteriaValue != null) { whereStatements.add(where.eq(columnName, criteriaValue)); } diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java index cb59ac9350a..89a11f09cfd 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java @@ -3232,6 +3232,10 @@ public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int // ATTENTION: break should only be done after last version break; + case 362: + currentVersion = 362; + getDao(PathogenTest.class).executeRaw("ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS serotypetext varchar(255);"); + getDao(PathogenTest.class).executeRaw("UPDATE pathogentest SET serotypetext = serotype, serotype = \"'OTHER\"' WHERE serotype IS NOT null and serotypetext is null;"); default: throw new IllegalStateException("onUpgrade() with unknown oldVersion " + oldVersion); diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/sample/PathogenTest.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/sample/PathogenTest.java index b39f5e0c7ce..c0c60ba591d 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/sample/PathogenTest.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/sample/PathogenTest.java @@ -39,6 +39,7 @@ import de.symeda.sormas.api.sample.PCRTestSpecification; import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.sample.Serotype; import de.symeda.sormas.app.backend.common.DatabaseHelper; import de.symeda.sormas.app.backend.common.PseudonymizableAdo; import de.symeda.sormas.app.backend.environment.environmentsample.EnvironmentSample; @@ -116,8 +117,9 @@ public class PathogenTest extends PseudonymizableAdo { @Column private boolean fourFoldIncreaseAntibodyTiter; - @Column(length = CHARACTER_LIMIT_DEFAULT) - private String serotype; + @Column + @Enumerated(EnumType.STRING) + private Serotype serotype; @DatabaseField private Float cqValue; @@ -333,11 +335,11 @@ public void setTestTypeText(String testTypeText) { this.testTypeText = testTypeText; } - public String getSerotype() { + public Serotype getSerotype() { return serotype; } - public void setSerotype(String serotype) { + public void setSerotype(Serotype serotype) { this.serotype = serotype; } diff --git a/sormas-app/app/src/main/res/layout/fragment_pathogen_test_edit_layout.xml b/sormas-app/app/src/main/res/layout/fragment_pathogen_test_edit_layout.xml index 938776afa76..bd24f1f42af 100644 --- a/sormas-app/app/src/main/res/layout/fragment_pathogen_test_edit_layout.xml +++ b/sormas-app/app/src/main/res/layout/fragment_pathogen_test_edit_layout.xml @@ -29,6 +29,7 @@ + @@ -172,13 +173,13 @@ android:layout_height="wrap_content" android:orientation="horizontal"> - - void updateObjectList(T existingObject, JsonN } } - if (existingObjectField.getType().isAssignableFrom(List.class)) { + if (Collection.class.isAssignableFrom(existingObjectField.getType())) { Object existingObjectFieldInstance = null; try { existingObjectFieldInstance = existingObjectField.get(existingObject); From f675c3b029397191b00216be252c7b5521644276 Mon Sep 17 00:00:00 2001 From: Karnaiah Pesula Date: Thu, 2 Apr 2026 12:31:41 +0200 Subject: [PATCH 30/55] corrected the latest case --- .../de/symeda/sormas/app/backend/common/DatabaseHelper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java index 89a11f09cfd..4509476cd66 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/DatabaseHelper.java @@ -3230,13 +3230,13 @@ public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int + "WHERE (contactProximities IS NULL OR contactProximities = '') " + "AND contactProximity IS NOT NULL AND contactProximity != '';"); - // ATTENTION: break should only be done after last version - break; + case 362: currentVersion = 362; getDao(PathogenTest.class).executeRaw("ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS serotypetext varchar(255);"); getDao(PathogenTest.class).executeRaw("UPDATE pathogentest SET serotypetext = serotype, serotype = \"'OTHER\"' WHERE serotype IS NOT null and serotypetext is null;"); - + // ATTENTION: break should only be done after last version + break; default: throw new IllegalStateException("onUpgrade() with unknown oldVersion " + oldVersion); } From cdbd1bbbeafbe734f8e4a413f21f91c028d77d86 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Thu, 2 Apr 2026 12:52:23 +0200 Subject: [PATCH 31/55] Updated Jacoco tool version to 0.8.14 to avoid bytecode failures --- sormas-app/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sormas-app/app/build.gradle b/sormas-app/app/build.gradle index c79e9ab11b5..dfe4a58824b 100644 --- a/sormas-app/app/build.gradle +++ b/sormas-app/app/build.gradle @@ -155,7 +155,7 @@ dependencies { apply plugin: "jacoco" jacoco { - toolVersion = "0.8.5" + toolVersion = "0.8.14" reportsDirectory = file("$buildDir/reports") } task jacocoUnitTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) { From 02f494b0d164d9dd675e7b3469bc9dee51b6b6b6 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Thu, 2 Apr 2026 13:05:42 +0200 Subject: [PATCH 32/55] Corrected coderabit suggestion --- .../app/backend/common/AbstractAdoDao.java | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java index dff4d5fe145..75e8b5dddee 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/common/AbstractAdoDao.java @@ -332,8 +332,8 @@ public Date getLatestChangeDate() { String query = "SELECT MAX(" + AbstractDomainObject.CHANGE_DATE + ") FROM " + getTableName(); GenericRawResults maxChangeDateResult = queryRaw( query, - new DataType[] { - DataType.DATE_LONG }); + new DataType[]{ + DataType.DATE_LONG}); try { List dateResults = maxChangeDateResult.getResults(); if (!dateResults.isEmpty()) { @@ -378,8 +378,8 @@ protected Date getLatestChangeDateSubJoinReverse(String joinTableName, String jo protected Date getLatestChangeDateJoinFromQuery(String query) { GenericRawResults maxChangeDateResult = queryRaw( query, - new DataType[] { - DataType.DATE_LONG }); + new DataType[]{ + DataType.DATE_LONG}); try { List dateResults = maxChangeDateResult.getResults(); if (!dateResults.isEmpty()) { @@ -943,35 +943,37 @@ public void accept(ADO ado) throws DaoException { try { // accept all collection elements - Collection sourceCollection = (Collection) property.getReadMethod().invoke(ado); - if(sourceCollection != null && !sourceCollection.isEmpty()) { + final Collection sourceCollection = (Collection) property.getReadMethod().invoke(ado); + if (sourceCollection != null && !sourceCollection.isEmpty()) { for (Object sourceElement : sourceCollection) { - if(!(sourceElement instanceof AbstractDomainObject)) { + if (!(sourceElement instanceof AbstractDomainObject)) { continue; } - AbstractDomainObject e = (AbstractDomainObject) sourceElement; + final AbstractDomainObject e = (AbstractDomainObject) sourceElement; DatabaseHelper.getAdoDao(e.getClass()).acceptWithCast(e); } + } - if (snapshot != null) { - // delete remaining snapshots - Collection snapshotCollection = - (Collection) property.getReadMethod().invoke(snapshot); - if (snapshotCollection != null && !snapshotCollection.isEmpty()) { - for (Object snapshotElement : snapshotCollection) { - if (!(snapshotElement instanceof AbstractDomainObject)) { - continue; - } - if(sourceCollection.contains(snapshotElement)) { - continue; - } - final AbstractDomainObject e = (AbstractDomainObject) snapshotElement; - - DatabaseHelper.getAdoDao(e.getClass()).deleteCascadeWithCast(e); + if (snapshot != null) { + // delete remaining snapshots + final Collection snapshotCollection = + (Collection) property.getReadMethod().invoke(snapshot); + if (snapshotCollection != null && !snapshotCollection.isEmpty()) { + for (Object snapshotElement : snapshotCollection) { + if (!(snapshotElement instanceof AbstractDomainObject)) { + continue; + } + if (sourceCollection != null && sourceCollection.contains(snapshotElement)) { + continue; } + final AbstractDomainObject e = (AbstractDomainObject) snapshotElement; + + DatabaseHelper.getAdoDao(e.getClass()).deleteCascadeWithCast(e); } } + } + } catch (ClassCastException e) { // Collection does not contain ADOs and doesn't have to be handled } @@ -1124,8 +1126,8 @@ public List filterMissing(List uuids) { try { GenericRawResults existingUuids = dao.queryRaw( "SELECT uuid FROM " + getTableName(), - new DataType[] { - DataType.STRING }); + new DataType[]{ + DataType.STRING}); List results = new ArrayList<>(uuids); for (Object[] existingUuid : existingUuids) { results.remove(existingUuid[0]); From 132aa738eababca3cd1881a828e216ebdf9d87a0 Mon Sep 17 00:00:00 2001 From: sormas-robot Date: Tue, 7 Apr 2026 06:37:08 +0000 Subject: [PATCH 33/55] [GitHub Actions] Update openAPI spec files --- sormas-rest/swagger.json | 1793 +++++++++++++++++++++--- sormas-rest/swagger.yaml | 2771 +++++++++++++++++++++++++++++++++++++- 2 files changed, 4356 insertions(+), 208 deletions(-) diff --git a/sormas-rest/swagger.json b/sormas-rest/swagger.json index a52291f2691..e61129e240a 100644 --- a/sormas-rest/swagger.json +++ b/sormas-rest/swagger.json @@ -7,7 +7,7 @@ "url" : "https://www.gnu.org/licenses/gpl-3.0.html" }, "title" : "SORMAS REST API", - "version" : "1.98.0-SNAPSHOT" + "version" : "1.104.0-SNAPSHOT" }, "servers" : [ { "url" : "/sormas-rest" @@ -3467,7 +3467,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] } } } @@ -5790,7 +5790,7 @@ "name" : "disease", "schema" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] } } ], "requestBody" : { @@ -6180,7 +6180,7 @@ "name" : "disease", "schema" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] } } ], "responses" : { @@ -9889,7 +9889,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "CASE_VIEW", "CASE_CREATE", "CASE_EDIT", "CASE_DELETE", "CASE_ARCHIVE", "CASE_VIEW_ARCHIVED", "CASE_IMPORT", "CASE_EXPORT", "CASE_INVESTIGATE", "CASE_CLASSIFY", "CASE_CHANGE_DISEASE", "CASE_CHANGE_EPID_NUMBER", "CASE_TRANSFER", "CASE_REFER_FROM_POE", "CASE_MERGE", "CASE_SHARE", "CASE_RESPONSIBLE", "GRANT_SPECIAL_CASE_ACCESS", "IMMUNIZATION_VIEW", "IMMUNIZATION_CREATE", "IMMUNIZATION_EDIT", "IMMUNIZATION_DELETE", "IMMUNIZATION_ARCHIVE", "IMMUNIZATION_VIEW_ARCHIVED", "PERSON_VIEW", "PERSON_EDIT", "PERSON_DELETE", "PERSON_EXPORT", "PERSON_CONTACT_DETAILS_DELETE", "PERSON_MERGE", "SAMPLE_VIEW", "SAMPLE_CREATE", "SAMPLE_EDIT", "SAMPLE_DELETE", "SAMPLE_EXPORT", "SAMPLE_TRANSFER", "SAMPLE_EDIT_NOT_OWNED", "PATHOGEN_TEST_CREATE", "PATHOGEN_TEST_EDIT", "PATHOGEN_TEST_DELETE", "ADDITIONAL_TEST_VIEW", "ADDITIONAL_TEST_CREATE", "ADDITIONAL_TEST_EDIT", "ADDITIONAL_TEST_DELETE", "CONTACT_VIEW", "CONTACT_CREATE", "CONTACT_EDIT", "CONTACT_DELETE", "CONTACT_ARCHIVE", "CONTACT_VIEW_ARCHIVED", "CONTACT_IMPORT", "CONTACT_EXPORT", "CONTACT_CONVERT", "CONTACT_REASSIGN_CASE", "CONTACT_MERGE", "CONTACT_RESPONSIBLE", "VISIT_CREATE", "VISIT_EDIT", "VISIT_DELETE", "VISIT_EXPORT", "TASK_VIEW", "TASK_CREATE", "TASK_EDIT", "TASK_DELETE", "TASK_EXPORT", "TASK_ASSIGN", "TASK_ARCHIVE", "TASK_VIEW_ARCHIVED", "ACTION_CREATE", "ACTION_DELETE", "ACTION_EDIT", "EVENT_VIEW", "EVENT_CREATE", "EVENT_EDIT", "EVENT_DELETE", "EVENT_ARCHIVE", "EVENT_VIEW_ARCHIVED", "EVENT_IMPORT", "EVENT_EXPORT", "EVENT_RESPONSIBLE", "EVENTPARTICIPANT_VIEW", "EVENTPARTICIPANT_CREATE", "EVENTPARTICIPANT_EDIT", "EVENTPARTICIPANT_DELETE", "EVENTPARTICIPANT_ARCHIVE", "EVENTPARTICIPANT_VIEW_ARCHIVED", "EVENTPARTICIPANT_IMPORT", "EVENTGROUP_CREATE", "EVENTGROUP_EDIT", "EVENTGROUP_ARCHIVE", "EVENTGROUP_VIEW_ARCHIVED", "EVENTGROUP_DELETE", "EVENTGROUP_LINK", "USER_VIEW", "USER_CREATE", "USER_EDIT", "USER_ROLE_VIEW", "USER_ROLE_EDIT", "USER_ROLE_DELETE", "STATISTICS_ACCESS", "STATISTICS_EXPORT", "INFRASTRUCTURE_VIEW", "INFRASTRUCTURE_CREATE", "INFRASTRUCTURE_EDIT", "INFRASTRUCTURE_IMPORT", "INFRASTRUCTURE_ARCHIVE", "INFRASTRUCTURE_VIEW_ARCHIVED", "INFRASTRUCTURE_EXPORT", "POPULATION_MANAGE", "DASHBOARD_SURVEILLANCE_VIEW", "DASHBOARD_CONTACT_VIEW", "DASHBOARD_CONTACT_VIEW_TRANSMISSION_CHAINS", "DASHBOARD_CAMPAIGNS_VIEW", "DASHBOARD_SAMPLES_VIEW", "CASE_CLINICIAN_VIEW", "THERAPY_VIEW", "PRESCRIPTION_CREATE", "PRESCRIPTION_EDIT", "PRESCRIPTION_DELETE", "TREATMENT_CREATE", "TREATMENT_EDIT", "TREATMENT_DELETE", "CLINICAL_COURSE_VIEW", "CLINICAL_COURSE_EDIT", "CLINICAL_VISIT_CREATE", "CLINICAL_VISIT_EDIT", "CLINICAL_VISIT_DELETE", "PORT_HEALTH_INFO_VIEW", "PORT_HEALTH_INFO_EDIT", "WEEKLYREPORT_VIEW", "WEEKLYREPORT_CREATE", "AGGREGATE_REPORT_VIEW", "AGGREGATE_REPORT_EDIT", "AGGREGATE_REPORT_EXPORT", "SEE_PERSONAL_DATA_IN_JURISDICTION", "SEE_PERSONAL_DATA_OUTSIDE_JURISDICTION", "SEE_SENSITIVE_DATA_IN_JURISDICTION", "SEE_SENSITIVE_DATA_OUTSIDE_JURISDICTION", "CAMPAIGN_VIEW", "CAMPAIGN_EDIT", "CAMPAIGN_ARCHIVE", "CAMPAIGN_VIEW_ARCHIVED", "CAMPAIGN_DELETE", "CAMPAIGN_FORM_DATA_VIEW", "CAMPAIGN_FORM_DATA_EDIT", "CAMPAIGN_FORM_DATA_ARCHIVE", "CAMPAIGN_FORM_DATA_DELETE", "CAMPAIGN_FORM_DATA_VIEW_ARCHIVED", "CAMPAIGN_FORM_DATA_EXPORT", "TRAVEL_ENTRY_MANAGEMENT_ACCESS", "TRAVEL_ENTRY_VIEW", "TRAVEL_ENTRY_CREATE", "TRAVEL_ENTRY_EDIT", "TRAVEL_ENTRY_ARCHIVE", "TRAVEL_ENTRY_VIEW_ARCHIVED", "TRAVEL_ENTRY_DELETE", "ENVIRONMENT_VIEW", "ENVIRONMENT_CREATE", "ENVIRONMENT_EDIT", "ENVIRONMENT_ARCHIVE", "ENVIRONMENT_VIEW_ARCHIVED", "ENVIRONMENT_DELETE", "ENVIRONMENT_IMPORT", "ENVIRONMENT_EXPORT", "ENVIRONMENT_SAMPLE_VIEW", "ENVIRONMENT_SAMPLE_CREATE", "ENVIRONMENT_SAMPLE_EDIT", "ENVIRONMENT_SAMPLE_EDIT_DISPATCH", "ENVIRONMENT_SAMPLE_EDIT_RECEIVAL", "ENVIRONMENT_SAMPLE_DELETE", "ENVIRONMENT_SAMPLE_IMPORT", "ENVIRONMENT_SAMPLE_EXPORT", "ENVIRONMENT_PATHOGEN_TEST_CREATE", "ENVIRONMENT_PATHOGEN_TEST_EDIT", "ENVIRONMENT_PATHOGEN_TEST_DELETE", "SELF_REPORT_VIEW", "SELF_REPORT_CREATE", "SELF_REPORT_EDIT", "SELF_REPORT_DELETE", "SELF_REPORT_ARCHIVE", "SELF_REPORT_PROCESS", "SELF_REPORT_IMPORT", "SELF_REPORT_EXPORT", "DOCUMENT_VIEW", "DOCUMENT_UPLOAD", "DOCUMENT_DELETE", "PERFORM_BULK_OPERATIONS", "PERFORM_BULK_OPERATIONS_PSEUDONYM", "QUARANTINE_ORDER_CREATE", "SORMAS_REST", "SORMAS_UI", "DATABASE_EXPORT_ACCESS", "EXPORT_DATA_PROTECTION_DATA", "BAG_EXPORT", "SEND_MANUAL_EXTERNAL_MESSAGES", "MANAGE_EXTERNAL_SYMPTOM_JOURNAL", "EXTERNAL_VISITS", "SORMAS_TO_SORMAS_CLIENT", "SORMAS_TO_SORMAS_SHARE", "SORMAS_TO_SORMAS_PROCESS", "EXTERNAL_SURVEILLANCE_SHARE", "EXTERNAL_SURVEILLANCE_DELETE", "EXTERNAL_MESSAGE_VIEW", "EXTERNAL_MESSAGE_PROCESS", "EXTERNAL_MESSAGE_PUSH", "EXTERNAL_MESSAGE_DELETE", "OUTBREAK_VIEW", "OUTBREAK_EDIT", "MANAGE_PUBLIC_EXPORT_CONFIGURATION", "DOCUMENT_TEMPLATE_MANAGEMENT", "LINE_LISTING_CONFIGURE", "DEV_MODE", "EMAIL_TEMPLATE_MANAGEMENT", "EXTERNAL_EMAIL_SEND", "EXTERNAL_EMAIL_ATTACH_DOCUMENTS", "CUSTOMIZABLE_ENUM_MANAGEMENT" ] + "enum" : [ "CASE_VIEW", "CASE_CREATE", "CASE_EDIT", "CASE_DELETE", "CASE_ARCHIVE", "CASE_VIEW_ARCHIVED", "CASE_IMPORT", "CASE_EXPORT", "CASE_INVESTIGATE", "CASE_CLASSIFY", "CASE_CHANGE_DISEASE", "CASE_CHANGE_EPID_NUMBER", "CASE_TRANSFER", "CASE_REFER_FROM_POE", "CASE_MERGE", "CASE_SHARE", "CASE_RESPONSIBLE", "GRANT_SPECIAL_CASE_ACCESS", "IMMUNIZATION_VIEW", "IMMUNIZATION_CREATE", "IMMUNIZATION_EDIT", "IMMUNIZATION_DELETE", "IMMUNIZATION_ARCHIVE", "IMMUNIZATION_VIEW_ARCHIVED", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_CREATE", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EDIT", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT", "PERSON_VIEW", "PERSON_EDIT", "PERSON_DELETE", "PERSON_EXPORT", "PERSON_CONTACT_DETAILS_DELETE", "PERSON_MERGE", "SAMPLE_VIEW", "SAMPLE_CREATE", "SAMPLE_EDIT", "SAMPLE_DELETE", "SAMPLE_EXPORT", "SAMPLE_TRANSFER", "SAMPLE_EDIT_NOT_OWNED", "PATHOGEN_TEST_CREATE", "PATHOGEN_TEST_EDIT", "PATHOGEN_TEST_DELETE", "ADDITIONAL_TEST_VIEW", "ADDITIONAL_TEST_CREATE", "ADDITIONAL_TEST_EDIT", "ADDITIONAL_TEST_DELETE", "CONTACT_VIEW", "CONTACT_CREATE", "CONTACT_EDIT", "CONTACT_DELETE", "CONTACT_ARCHIVE", "CONTACT_VIEW_ARCHIVED", "CONTACT_IMPORT", "CONTACT_EXPORT", "CONTACT_CONVERT", "CONTACT_REASSIGN_CASE", "CONTACT_MERGE", "CONTACT_RESPONSIBLE", "VISIT_CREATE", "VISIT_EDIT", "VISIT_DELETE", "VISIT_EXPORT", "TASK_VIEW", "TASK_CREATE", "TASK_EDIT", "TASK_DELETE", "TASK_EXPORT", "TASK_ASSIGN", "TASK_ARCHIVE", "TASK_VIEW_ARCHIVED", "ACTION_CREATE", "ACTION_DELETE", "ACTION_EDIT", "EVENT_VIEW", "EVENT_CREATE", "EVENT_EDIT", "EVENT_DELETE", "EVENT_ARCHIVE", "EVENT_VIEW_ARCHIVED", "EVENT_IMPORT", "EVENT_EXPORT", "EVENT_RESPONSIBLE", "EVENTPARTICIPANT_VIEW", "EVENTPARTICIPANT_CREATE", "EVENTPARTICIPANT_EDIT", "EVENTPARTICIPANT_DELETE", "EVENTPARTICIPANT_ARCHIVE", "EVENTPARTICIPANT_VIEW_ARCHIVED", "EVENTPARTICIPANT_IMPORT", "EVENTGROUP_CREATE", "EVENTGROUP_EDIT", "EVENTGROUP_ARCHIVE", "EVENTGROUP_VIEW_ARCHIVED", "EVENTGROUP_DELETE", "EVENTGROUP_LINK", "USER_VIEW", "USER_CREATE", "USER_EDIT", "USER_ROLE_VIEW", "USER_ROLE_EDIT", "USER_ROLE_DELETE", "STATISTICS_ACCESS", "STATISTICS_EXPORT", "INFRASTRUCTURE_VIEW", "INFRASTRUCTURE_CREATE", "INFRASTRUCTURE_EDIT", "INFRASTRUCTURE_IMPORT", "INFRASTRUCTURE_ARCHIVE", "INFRASTRUCTURE_VIEW_ARCHIVED", "INFRASTRUCTURE_EXPORT", "POPULATION_MANAGE", "DASHBOARD_SURVEILLANCE_VIEW", "DASHBOARD_CONTACT_VIEW", "DASHBOARD_CONTACT_VIEW_TRANSMISSION_CHAINS", "DASHBOARD_CAMPAIGNS_VIEW", "DASHBOARD_SAMPLES_VIEW", "DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW", "CASE_CLINICIAN_VIEW", "THERAPY_VIEW", "PRESCRIPTION_CREATE", "PRESCRIPTION_EDIT", "PRESCRIPTION_DELETE", "TREATMENT_CREATE", "TREATMENT_EDIT", "TREATMENT_DELETE", "CLINICAL_COURSE_VIEW", "CLINICAL_COURSE_EDIT", "CLINICAL_VISIT_CREATE", "CLINICAL_VISIT_EDIT", "CLINICAL_VISIT_DELETE", "PORT_HEALTH_INFO_VIEW", "PORT_HEALTH_INFO_EDIT", "WEEKLYREPORT_VIEW", "WEEKLYREPORT_CREATE", "AGGREGATE_REPORT_VIEW", "AGGREGATE_REPORT_EDIT", "AGGREGATE_REPORT_EXPORT", "SEE_PERSONAL_DATA_IN_JURISDICTION", "SEE_PERSONAL_DATA_OUTSIDE_JURISDICTION", "SEE_SENSITIVE_DATA_IN_JURISDICTION", "SEE_SENSITIVE_DATA_OUTSIDE_JURISDICTION", "CAMPAIGN_VIEW", "CAMPAIGN_EDIT", "CAMPAIGN_ARCHIVE", "CAMPAIGN_VIEW_ARCHIVED", "CAMPAIGN_DELETE", "CAMPAIGN_FORM_DATA_VIEW", "CAMPAIGN_FORM_DATA_EDIT", "CAMPAIGN_FORM_DATA_ARCHIVE", "CAMPAIGN_FORM_DATA_DELETE", "CAMPAIGN_FORM_DATA_VIEW_ARCHIVED", "CAMPAIGN_FORM_DATA_EXPORT", "TRAVEL_ENTRY_MANAGEMENT_ACCESS", "TRAVEL_ENTRY_VIEW", "TRAVEL_ENTRY_CREATE", "TRAVEL_ENTRY_EDIT", "TRAVEL_ENTRY_ARCHIVE", "TRAVEL_ENTRY_VIEW_ARCHIVED", "TRAVEL_ENTRY_DELETE", "ENVIRONMENT_VIEW", "ENVIRONMENT_CREATE", "ENVIRONMENT_EDIT", "ENVIRONMENT_ARCHIVE", "ENVIRONMENT_VIEW_ARCHIVED", "ENVIRONMENT_DELETE", "ENVIRONMENT_IMPORT", "ENVIRONMENT_EXPORT", "ENVIRONMENT_LINK", "ENVIRONMENT_SAMPLE_VIEW", "ENVIRONMENT_SAMPLE_CREATE", "ENVIRONMENT_SAMPLE_EDIT", "ENVIRONMENT_SAMPLE_EDIT_DISPATCH", "ENVIRONMENT_SAMPLE_EDIT_RECEIVAL", "ENVIRONMENT_SAMPLE_DELETE", "ENVIRONMENT_SAMPLE_IMPORT", "ENVIRONMENT_SAMPLE_EXPORT", "ENVIRONMENT_PATHOGEN_TEST_CREATE", "ENVIRONMENT_PATHOGEN_TEST_EDIT", "ENVIRONMENT_PATHOGEN_TEST_DELETE", "SELF_REPORT_VIEW", "SELF_REPORT_CREATE", "SELF_REPORT_EDIT", "SELF_REPORT_DELETE", "SELF_REPORT_ARCHIVE", "SELF_REPORT_PROCESS", "SELF_REPORT_IMPORT", "SELF_REPORT_EXPORT", "DOCUMENT_VIEW", "DOCUMENT_UPLOAD", "DOCUMENT_DELETE", "PERFORM_BULK_OPERATIONS", "PERFORM_BULK_OPERATIONS_PSEUDONYM", "QUARANTINE_ORDER_CREATE", "SORMAS_REST", "SORMAS_UI", "DATABASE_EXPORT_ACCESS", "EXPORT_DATA_PROTECTION_DATA", "BAG_EXPORT", "SEND_MANUAL_EXTERNAL_MESSAGES", "MANAGE_EXTERNAL_SYMPTOM_JOURNAL", "EXTERNAL_VISITS", "SORMAS_TO_SORMAS_CLIENT", "SORMAS_TO_SORMAS_SHARE", "SORMAS_TO_SORMAS_PROCESS", "EXTERNAL_SURVEILLANCE_SHARE", "EXTERNAL_SURVEILLANCE_DELETE", "EXTERNAL_MESSAGE_PUSH", "EXTERNAL_MESSAGE_ACCESS", "EXTERNAL_MESSAGE_LABORATORY_VIEW", "EXTERNAL_MESSAGE_DOCTOR_DECLARATION_VIEW", "EXTERNAL_MESSAGE_LABORATORY_PROCESS", "EXTERNAL_MESSAGE_DOCTOR_DECLARATION_PROCESS", "EXTERNAL_MESSAGE_LABORATORY_DELETE", "EXTERNAL_MESSAGE_DOCTOR_DECLARATION_DELETE", "SURVEY_VIEW", "SURVEY_CREATE", "SURVEY_EDIT", "SURVEY_DELETE", "SURVEY_TOKEN_VIEW", "SURVEY_TOKEN_CREATE", "SURVEY_TOKEN_EDIT", "SURVEY_TOKEN_DELETE", "SURVEY_TOKEN_IMPORT", "OUTBREAK_VIEW", "OUTBREAK_EDIT", "MANAGE_PUBLIC_EXPORT_CONFIGURATION", "DOCUMENT_TEMPLATE_MANAGEMENT", "LINE_LISTING_CONFIGURE", "DEV_MODE", "EMAIL_TEMPLATE_MANAGEMENT", "EXTERNAL_EMAIL_SEND", "EXTERNAL_EMAIL_ATTACH_DOCUMENTS", "CUSTOMIZABLE_ENUM_MANAGEMENT", "SYSTEM_CONFIGURATION", "DISEASE_MANAGEMENT", "EPIPULSE_EXPORT_VIEW", "EPIPULSE_EXPORT_CREATE", "EPIPULSE_EXPORT_DOWNLOAD", "EPIPULSE_EXPORT_DELETE" ] } } } @@ -9919,7 +9919,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "CASE_VIEW", "CASE_CREATE", "CASE_EDIT", "CASE_DELETE", "CASE_ARCHIVE", "CASE_VIEW_ARCHIVED", "CASE_IMPORT", "CASE_EXPORT", "CASE_INVESTIGATE", "CASE_CLASSIFY", "CASE_CHANGE_DISEASE", "CASE_CHANGE_EPID_NUMBER", "CASE_TRANSFER", "CASE_REFER_FROM_POE", "CASE_MERGE", "CASE_SHARE", "CASE_RESPONSIBLE", "GRANT_SPECIAL_CASE_ACCESS", "IMMUNIZATION_VIEW", "IMMUNIZATION_CREATE", "IMMUNIZATION_EDIT", "IMMUNIZATION_DELETE", "IMMUNIZATION_ARCHIVE", "IMMUNIZATION_VIEW_ARCHIVED", "PERSON_VIEW", "PERSON_EDIT", "PERSON_DELETE", "PERSON_EXPORT", "PERSON_CONTACT_DETAILS_DELETE", "PERSON_MERGE", "SAMPLE_VIEW", "SAMPLE_CREATE", "SAMPLE_EDIT", "SAMPLE_DELETE", "SAMPLE_EXPORT", "SAMPLE_TRANSFER", "SAMPLE_EDIT_NOT_OWNED", "PATHOGEN_TEST_CREATE", "PATHOGEN_TEST_EDIT", "PATHOGEN_TEST_DELETE", "ADDITIONAL_TEST_VIEW", "ADDITIONAL_TEST_CREATE", "ADDITIONAL_TEST_EDIT", "ADDITIONAL_TEST_DELETE", "CONTACT_VIEW", "CONTACT_CREATE", "CONTACT_EDIT", "CONTACT_DELETE", "CONTACT_ARCHIVE", "CONTACT_VIEW_ARCHIVED", "CONTACT_IMPORT", "CONTACT_EXPORT", "CONTACT_CONVERT", "CONTACT_REASSIGN_CASE", "CONTACT_MERGE", "CONTACT_RESPONSIBLE", "VISIT_CREATE", "VISIT_EDIT", "VISIT_DELETE", "VISIT_EXPORT", "TASK_VIEW", "TASK_CREATE", "TASK_EDIT", "TASK_DELETE", "TASK_EXPORT", "TASK_ASSIGN", "TASK_ARCHIVE", "TASK_VIEW_ARCHIVED", "ACTION_CREATE", "ACTION_DELETE", "ACTION_EDIT", "EVENT_VIEW", "EVENT_CREATE", "EVENT_EDIT", "EVENT_DELETE", "EVENT_ARCHIVE", "EVENT_VIEW_ARCHIVED", "EVENT_IMPORT", "EVENT_EXPORT", "EVENT_RESPONSIBLE", "EVENTPARTICIPANT_VIEW", "EVENTPARTICIPANT_CREATE", "EVENTPARTICIPANT_EDIT", "EVENTPARTICIPANT_DELETE", "EVENTPARTICIPANT_ARCHIVE", "EVENTPARTICIPANT_VIEW_ARCHIVED", "EVENTPARTICIPANT_IMPORT", "EVENTGROUP_CREATE", "EVENTGROUP_EDIT", "EVENTGROUP_ARCHIVE", "EVENTGROUP_VIEW_ARCHIVED", "EVENTGROUP_DELETE", "EVENTGROUP_LINK", "USER_VIEW", "USER_CREATE", "USER_EDIT", "USER_ROLE_VIEW", "USER_ROLE_EDIT", "USER_ROLE_DELETE", "STATISTICS_ACCESS", "STATISTICS_EXPORT", "INFRASTRUCTURE_VIEW", "INFRASTRUCTURE_CREATE", "INFRASTRUCTURE_EDIT", "INFRASTRUCTURE_IMPORT", "INFRASTRUCTURE_ARCHIVE", "INFRASTRUCTURE_VIEW_ARCHIVED", "INFRASTRUCTURE_EXPORT", "POPULATION_MANAGE", "DASHBOARD_SURVEILLANCE_VIEW", "DASHBOARD_CONTACT_VIEW", "DASHBOARD_CONTACT_VIEW_TRANSMISSION_CHAINS", "DASHBOARD_CAMPAIGNS_VIEW", "DASHBOARD_SAMPLES_VIEW", "CASE_CLINICIAN_VIEW", "THERAPY_VIEW", "PRESCRIPTION_CREATE", "PRESCRIPTION_EDIT", "PRESCRIPTION_DELETE", "TREATMENT_CREATE", "TREATMENT_EDIT", "TREATMENT_DELETE", "CLINICAL_COURSE_VIEW", "CLINICAL_COURSE_EDIT", "CLINICAL_VISIT_CREATE", "CLINICAL_VISIT_EDIT", "CLINICAL_VISIT_DELETE", "PORT_HEALTH_INFO_VIEW", "PORT_HEALTH_INFO_EDIT", "WEEKLYREPORT_VIEW", "WEEKLYREPORT_CREATE", "AGGREGATE_REPORT_VIEW", "AGGREGATE_REPORT_EDIT", "AGGREGATE_REPORT_EXPORT", "SEE_PERSONAL_DATA_IN_JURISDICTION", "SEE_PERSONAL_DATA_OUTSIDE_JURISDICTION", "SEE_SENSITIVE_DATA_IN_JURISDICTION", "SEE_SENSITIVE_DATA_OUTSIDE_JURISDICTION", "CAMPAIGN_VIEW", "CAMPAIGN_EDIT", "CAMPAIGN_ARCHIVE", "CAMPAIGN_VIEW_ARCHIVED", "CAMPAIGN_DELETE", "CAMPAIGN_FORM_DATA_VIEW", "CAMPAIGN_FORM_DATA_EDIT", "CAMPAIGN_FORM_DATA_ARCHIVE", "CAMPAIGN_FORM_DATA_DELETE", "CAMPAIGN_FORM_DATA_VIEW_ARCHIVED", "CAMPAIGN_FORM_DATA_EXPORT", "TRAVEL_ENTRY_MANAGEMENT_ACCESS", "TRAVEL_ENTRY_VIEW", "TRAVEL_ENTRY_CREATE", "TRAVEL_ENTRY_EDIT", "TRAVEL_ENTRY_ARCHIVE", "TRAVEL_ENTRY_VIEW_ARCHIVED", "TRAVEL_ENTRY_DELETE", "ENVIRONMENT_VIEW", "ENVIRONMENT_CREATE", "ENVIRONMENT_EDIT", "ENVIRONMENT_ARCHIVE", "ENVIRONMENT_VIEW_ARCHIVED", "ENVIRONMENT_DELETE", "ENVIRONMENT_IMPORT", "ENVIRONMENT_EXPORT", "ENVIRONMENT_SAMPLE_VIEW", "ENVIRONMENT_SAMPLE_CREATE", "ENVIRONMENT_SAMPLE_EDIT", "ENVIRONMENT_SAMPLE_EDIT_DISPATCH", "ENVIRONMENT_SAMPLE_EDIT_RECEIVAL", "ENVIRONMENT_SAMPLE_DELETE", "ENVIRONMENT_SAMPLE_IMPORT", "ENVIRONMENT_SAMPLE_EXPORT", "ENVIRONMENT_PATHOGEN_TEST_CREATE", "ENVIRONMENT_PATHOGEN_TEST_EDIT", "ENVIRONMENT_PATHOGEN_TEST_DELETE", "SELF_REPORT_VIEW", "SELF_REPORT_CREATE", "SELF_REPORT_EDIT", "SELF_REPORT_DELETE", "SELF_REPORT_ARCHIVE", "SELF_REPORT_PROCESS", "SELF_REPORT_IMPORT", "SELF_REPORT_EXPORT", "DOCUMENT_VIEW", "DOCUMENT_UPLOAD", "DOCUMENT_DELETE", "PERFORM_BULK_OPERATIONS", "PERFORM_BULK_OPERATIONS_PSEUDONYM", "QUARANTINE_ORDER_CREATE", "SORMAS_REST", "SORMAS_UI", "DATABASE_EXPORT_ACCESS", "EXPORT_DATA_PROTECTION_DATA", "BAG_EXPORT", "SEND_MANUAL_EXTERNAL_MESSAGES", "MANAGE_EXTERNAL_SYMPTOM_JOURNAL", "EXTERNAL_VISITS", "SORMAS_TO_SORMAS_CLIENT", "SORMAS_TO_SORMAS_SHARE", "SORMAS_TO_SORMAS_PROCESS", "EXTERNAL_SURVEILLANCE_SHARE", "EXTERNAL_SURVEILLANCE_DELETE", "EXTERNAL_MESSAGE_VIEW", "EXTERNAL_MESSAGE_PROCESS", "EXTERNAL_MESSAGE_PUSH", "EXTERNAL_MESSAGE_DELETE", "OUTBREAK_VIEW", "OUTBREAK_EDIT", "MANAGE_PUBLIC_EXPORT_CONFIGURATION", "DOCUMENT_TEMPLATE_MANAGEMENT", "LINE_LISTING_CONFIGURE", "DEV_MODE", "EMAIL_TEMPLATE_MANAGEMENT", "EXTERNAL_EMAIL_SEND", "EXTERNAL_EMAIL_ATTACH_DOCUMENTS", "CUSTOMIZABLE_ENUM_MANAGEMENT" ] + "enum" : [ "CASE_VIEW", "CASE_CREATE", "CASE_EDIT", "CASE_DELETE", "CASE_ARCHIVE", "CASE_VIEW_ARCHIVED", "CASE_IMPORT", "CASE_EXPORT", "CASE_INVESTIGATE", "CASE_CLASSIFY", "CASE_CHANGE_DISEASE", "CASE_CHANGE_EPID_NUMBER", "CASE_TRANSFER", "CASE_REFER_FROM_POE", "CASE_MERGE", "CASE_SHARE", "CASE_RESPONSIBLE", "GRANT_SPECIAL_CASE_ACCESS", "IMMUNIZATION_VIEW", "IMMUNIZATION_CREATE", "IMMUNIZATION_EDIT", "IMMUNIZATION_DELETE", "IMMUNIZATION_ARCHIVE", "IMMUNIZATION_VIEW_ARCHIVED", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_CREATE", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EDIT", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT", "PERSON_VIEW", "PERSON_EDIT", "PERSON_DELETE", "PERSON_EXPORT", "PERSON_CONTACT_DETAILS_DELETE", "PERSON_MERGE", "SAMPLE_VIEW", "SAMPLE_CREATE", "SAMPLE_EDIT", "SAMPLE_DELETE", "SAMPLE_EXPORT", "SAMPLE_TRANSFER", "SAMPLE_EDIT_NOT_OWNED", "PATHOGEN_TEST_CREATE", "PATHOGEN_TEST_EDIT", "PATHOGEN_TEST_DELETE", "ADDITIONAL_TEST_VIEW", "ADDITIONAL_TEST_CREATE", "ADDITIONAL_TEST_EDIT", "ADDITIONAL_TEST_DELETE", "CONTACT_VIEW", "CONTACT_CREATE", "CONTACT_EDIT", "CONTACT_DELETE", "CONTACT_ARCHIVE", "CONTACT_VIEW_ARCHIVED", "CONTACT_IMPORT", "CONTACT_EXPORT", "CONTACT_CONVERT", "CONTACT_REASSIGN_CASE", "CONTACT_MERGE", "CONTACT_RESPONSIBLE", "VISIT_CREATE", "VISIT_EDIT", "VISIT_DELETE", "VISIT_EXPORT", "TASK_VIEW", "TASK_CREATE", "TASK_EDIT", "TASK_DELETE", "TASK_EXPORT", "TASK_ASSIGN", "TASK_ARCHIVE", "TASK_VIEW_ARCHIVED", "ACTION_CREATE", "ACTION_DELETE", "ACTION_EDIT", "EVENT_VIEW", "EVENT_CREATE", "EVENT_EDIT", "EVENT_DELETE", "EVENT_ARCHIVE", "EVENT_VIEW_ARCHIVED", "EVENT_IMPORT", "EVENT_EXPORT", "EVENT_RESPONSIBLE", "EVENTPARTICIPANT_VIEW", "EVENTPARTICIPANT_CREATE", "EVENTPARTICIPANT_EDIT", "EVENTPARTICIPANT_DELETE", "EVENTPARTICIPANT_ARCHIVE", "EVENTPARTICIPANT_VIEW_ARCHIVED", "EVENTPARTICIPANT_IMPORT", "EVENTGROUP_CREATE", "EVENTGROUP_EDIT", "EVENTGROUP_ARCHIVE", "EVENTGROUP_VIEW_ARCHIVED", "EVENTGROUP_DELETE", "EVENTGROUP_LINK", "USER_VIEW", "USER_CREATE", "USER_EDIT", "USER_ROLE_VIEW", "USER_ROLE_EDIT", "USER_ROLE_DELETE", "STATISTICS_ACCESS", "STATISTICS_EXPORT", "INFRASTRUCTURE_VIEW", "INFRASTRUCTURE_CREATE", "INFRASTRUCTURE_EDIT", "INFRASTRUCTURE_IMPORT", "INFRASTRUCTURE_ARCHIVE", "INFRASTRUCTURE_VIEW_ARCHIVED", "INFRASTRUCTURE_EXPORT", "POPULATION_MANAGE", "DASHBOARD_SURVEILLANCE_VIEW", "DASHBOARD_CONTACT_VIEW", "DASHBOARD_CONTACT_VIEW_TRANSMISSION_CHAINS", "DASHBOARD_CAMPAIGNS_VIEW", "DASHBOARD_SAMPLES_VIEW", "DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW", "CASE_CLINICIAN_VIEW", "THERAPY_VIEW", "PRESCRIPTION_CREATE", "PRESCRIPTION_EDIT", "PRESCRIPTION_DELETE", "TREATMENT_CREATE", "TREATMENT_EDIT", "TREATMENT_DELETE", "CLINICAL_COURSE_VIEW", "CLINICAL_COURSE_EDIT", "CLINICAL_VISIT_CREATE", "CLINICAL_VISIT_EDIT", "CLINICAL_VISIT_DELETE", "PORT_HEALTH_INFO_VIEW", "PORT_HEALTH_INFO_EDIT", "WEEKLYREPORT_VIEW", "WEEKLYREPORT_CREATE", "AGGREGATE_REPORT_VIEW", "AGGREGATE_REPORT_EDIT", "AGGREGATE_REPORT_EXPORT", "SEE_PERSONAL_DATA_IN_JURISDICTION", "SEE_PERSONAL_DATA_OUTSIDE_JURISDICTION", "SEE_SENSITIVE_DATA_IN_JURISDICTION", "SEE_SENSITIVE_DATA_OUTSIDE_JURISDICTION", "CAMPAIGN_VIEW", "CAMPAIGN_EDIT", "CAMPAIGN_ARCHIVE", "CAMPAIGN_VIEW_ARCHIVED", "CAMPAIGN_DELETE", "CAMPAIGN_FORM_DATA_VIEW", "CAMPAIGN_FORM_DATA_EDIT", "CAMPAIGN_FORM_DATA_ARCHIVE", "CAMPAIGN_FORM_DATA_DELETE", "CAMPAIGN_FORM_DATA_VIEW_ARCHIVED", "CAMPAIGN_FORM_DATA_EXPORT", "TRAVEL_ENTRY_MANAGEMENT_ACCESS", "TRAVEL_ENTRY_VIEW", "TRAVEL_ENTRY_CREATE", "TRAVEL_ENTRY_EDIT", "TRAVEL_ENTRY_ARCHIVE", "TRAVEL_ENTRY_VIEW_ARCHIVED", "TRAVEL_ENTRY_DELETE", "ENVIRONMENT_VIEW", "ENVIRONMENT_CREATE", "ENVIRONMENT_EDIT", "ENVIRONMENT_ARCHIVE", "ENVIRONMENT_VIEW_ARCHIVED", "ENVIRONMENT_DELETE", "ENVIRONMENT_IMPORT", "ENVIRONMENT_EXPORT", "ENVIRONMENT_LINK", "ENVIRONMENT_SAMPLE_VIEW", "ENVIRONMENT_SAMPLE_CREATE", "ENVIRONMENT_SAMPLE_EDIT", "ENVIRONMENT_SAMPLE_EDIT_DISPATCH", "ENVIRONMENT_SAMPLE_EDIT_RECEIVAL", "ENVIRONMENT_SAMPLE_DELETE", "ENVIRONMENT_SAMPLE_IMPORT", "ENVIRONMENT_SAMPLE_EXPORT", "ENVIRONMENT_PATHOGEN_TEST_CREATE", "ENVIRONMENT_PATHOGEN_TEST_EDIT", "ENVIRONMENT_PATHOGEN_TEST_DELETE", "SELF_REPORT_VIEW", "SELF_REPORT_CREATE", "SELF_REPORT_EDIT", "SELF_REPORT_DELETE", "SELF_REPORT_ARCHIVE", "SELF_REPORT_PROCESS", "SELF_REPORT_IMPORT", "SELF_REPORT_EXPORT", "DOCUMENT_VIEW", "DOCUMENT_UPLOAD", "DOCUMENT_DELETE", "PERFORM_BULK_OPERATIONS", "PERFORM_BULK_OPERATIONS_PSEUDONYM", "QUARANTINE_ORDER_CREATE", "SORMAS_REST", "SORMAS_UI", "DATABASE_EXPORT_ACCESS", "EXPORT_DATA_PROTECTION_DATA", "BAG_EXPORT", "SEND_MANUAL_EXTERNAL_MESSAGES", "MANAGE_EXTERNAL_SYMPTOM_JOURNAL", "EXTERNAL_VISITS", "SORMAS_TO_SORMAS_CLIENT", "SORMAS_TO_SORMAS_SHARE", "SORMAS_TO_SORMAS_PROCESS", "EXTERNAL_SURVEILLANCE_SHARE", "EXTERNAL_SURVEILLANCE_DELETE", "EXTERNAL_MESSAGE_PUSH", "EXTERNAL_MESSAGE_ACCESS", "EXTERNAL_MESSAGE_LABORATORY_VIEW", "EXTERNAL_MESSAGE_DOCTOR_DECLARATION_VIEW", "EXTERNAL_MESSAGE_LABORATORY_PROCESS", "EXTERNAL_MESSAGE_DOCTOR_DECLARATION_PROCESS", "EXTERNAL_MESSAGE_LABORATORY_DELETE", "EXTERNAL_MESSAGE_DOCTOR_DECLARATION_DELETE", "SURVEY_VIEW", "SURVEY_CREATE", "SURVEY_EDIT", "SURVEY_DELETE", "SURVEY_TOKEN_VIEW", "SURVEY_TOKEN_CREATE", "SURVEY_TOKEN_EDIT", "SURVEY_TOKEN_DELETE", "SURVEY_TOKEN_IMPORT", "OUTBREAK_VIEW", "OUTBREAK_EDIT", "MANAGE_PUBLIC_EXPORT_CONFIGURATION", "DOCUMENT_TEMPLATE_MANAGEMENT", "LINE_LISTING_CONFIGURE", "DEV_MODE", "EMAIL_TEMPLATE_MANAGEMENT", "EXTERNAL_EMAIL_SEND", "EXTERNAL_EMAIL_ATTACH_DOCUMENTS", "CUSTOMIZABLE_ENUM_MANAGEMENT", "SYSTEM_CONFIGURATION", "DISEASE_MANAGEMENT", "EPIPULSE_EXPORT_VIEW", "EPIPULSE_EXPORT_CREATE", "EPIPULSE_EXPORT_DOWNLOAD", "EPIPULSE_EXPORT_DELETE" ] } } } @@ -10754,7 +10754,7 @@ }, "typeOfPlace" : { "type" : "string", - "enum" : [ "FACILITY", "FACILITY_23_IFSG", "COMMUNITY_FACILITY", "FACILITY_36_IFSG", "FESTIVITIES", "HOME", "MEANS_OF_TRANSPORT", "PUBLIC_PLACE", "SCATTERED", "UNKNOWN", "OTHER" ] + "enum" : [ "FACILITY", "FACILITY_23_IFSG", "COMMUNITY_FACILITY", "FACILITY_36_IFSG", "FESTIVITIES", "HOME", "MEANS_OF_TRANSPORT", "PUBLIC_PLACE", "SCATTERED", "SCHOOL", "EDUCATION_AND_CHILDCARE", "NURSING_HOME", "ASYLUM_SEEKERS_SHELTER", "UNKNOWN", "OTHER" ] }, "typeOfPlaceDetails" : { "type" : "string", @@ -10930,7 +10930,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "districtId" : { "type" : "integer", @@ -10997,7 +10997,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -11048,7 +11048,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -11443,10 +11443,18 @@ "type" : "integer", "format" : "int32" }, + "birthdateFrom" : { + "type" : "string", + "format" : "date-time" + }, "birthdateMM" : { "type" : "integer", "format" : "int32" }, + "birthdateTo" : { + "type" : "string", + "format" : "date-time" + }, "birthdateYYYY" : { "type" : "integer", "format" : "int32" @@ -11489,7 +11497,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseVariant" : { "$ref" : "#/components/schemas/DiseaseVariant" @@ -11538,6 +11546,9 @@ "includeCasesFromOtherJurisdictions" : { "type" : "boolean" }, + "includePartialMatch" : { + "type" : "boolean" + }, "investigationStatus" : { "type" : "string", "enum" : [ "PENDING", "DONE", "DISCARDED" ] @@ -11645,13 +11656,28 @@ "surveillanceOfficer" : { "$ref" : "#/components/schemas/UserReferenceDto" }, + "survey" : { + "$ref" : "#/components/schemas/SurveyReferenceDto" + }, + "surveyAssignedFrom" : { + "type" : "string", + "format" : "date-time" + }, + "surveyAssignedTo" : { + "type" : "string", + "format" : "date-time" + }, + "surveyResponseStatus" : { + "type" : "string", + "enum" : [ "RECEIVED", "NOT_RECEIVED" ] + }, "symptomJournalStatus" : { "type" : "string", "enum" : [ "UNREGISTERED", "REGISTERED", "ACCEPTED", "REJECTED", "DELETED" ] }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] }, "withExtendedQuarantine" : { "type" : "boolean" @@ -11776,9 +11802,12 @@ "type" : "string", "enum" : [ "DENGUE_FEVER", "DENGUE_HEMORRHAGIC_FEVER", "DENUGE_SHOCK_SYNDROME" ] }, + "department" : { + "type" : "string" + }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string", @@ -11929,6 +11958,9 @@ "notACaseReasonPhysicianInformation" : { "type" : "boolean" }, + "notifier" : { + "$ref" : "#/components/schemas/NotifierReferenceDto" + }, "notifyingClinic" : { "type" : "string", "enum" : [ "PEDIATRIC_INPATIENT", "NURSERY", "EPU", "CHER", "OPD", "EYE", "ENT", "CARDIOLOGY", "OTHER" ] @@ -11943,6 +11975,9 @@ "maxLength" : 1000000, "minLength" : 0 }, + "otherDiagnosticCriteria" : { + "type" : "string" + }, "outcome" : { "type" : "string", "enum" : [ "NO_OUTCOME", "DECEASED", "RECOVERED", "UNKNOWN" ] @@ -11975,6 +12010,9 @@ "portHealthInfo" : { "$ref" : "#/components/schemas/PortHealthInfoDto" }, + "postMortem" : { + "type" : "boolean" + }, "postpartum" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -12091,6 +12129,10 @@ "type" : "string", "enum" : [ "FURIOUS_RABIES", "PARALYTIC_RABIES" ] }, + "radiographyCompatibility" : { + "type" : "string", + "enum" : [ "COMPATIBLE_WITH_TB", "NOT_COMPATIBLE_WITH_TB" ] + }, "reInfection" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -12184,6 +12226,17 @@ "therapy" : { "$ref" : "#/components/schemas/TherapyDto" }, + "treatmentNotApplicable" : { + "type" : "boolean" + }, + "treatmentStartDate" : { + "type" : "string", + "format" : "date-time" + }, + "treatmentStarted" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "trimester" : { "type" : "string", "enum" : [ "FIRST", "SECOND", "THIRD", "UNKNOWN" ] @@ -12196,7 +12249,12 @@ }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] + }, + "vaccinationStatusDetails" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 }, "wasInQuarantineBeforeIsolation" : { "type" : "string", @@ -12212,10 +12270,18 @@ "type" : "integer", "format" : "int32" }, + "birthdateFrom" : { + "type" : "string", + "format" : "date-time" + }, "birthdateMM" : { "type" : "integer", "format" : "int32" }, + "birthdateTo" : { + "type" : "string", + "format" : "date-time" + }, "birthdateYYYY" : { "type" : "integer", "format" : "int32" @@ -12258,7 +12324,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseVariant" : { "$ref" : "#/components/schemas/DiseaseVariant" @@ -12307,6 +12373,9 @@ "includeCasesFromOtherJurisdictions" : { "type" : "boolean" }, + "includePartialMatch" : { + "type" : "boolean" + }, "interval" : { "type" : "integer", "format" : "int32" @@ -12422,13 +12491,28 @@ "surveillanceOfficer" : { "$ref" : "#/components/schemas/UserReferenceDto" }, + "survey" : { + "$ref" : "#/components/schemas/SurveyReferenceDto" + }, + "surveyAssignedFrom" : { + "type" : "string", + "format" : "date-time" + }, + "surveyAssignedTo" : { + "type" : "string", + "format" : "date-time" + }, + "surveyResponseStatus" : { + "type" : "string", + "enum" : [ "RECEIVED", "NOT_RECEIVED" ] + }, "symptomJournalStatus" : { "type" : "string", "enum" : [ "UNREGISTERED", "REGISTERED", "ACCEPTED", "REJECTED", "DELETED" ] }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] }, "withExtendedQuarantine" : { "type" : "boolean" @@ -12452,7 +12536,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "firstName" : { "type" : "string" @@ -12532,7 +12616,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string" @@ -12602,6 +12686,9 @@ "type" : "integer", "format" : "int32" }, + "nationalHealthId" : { + "type" : "string" + }, "otherDeletionReason" : { "type" : "string" }, @@ -12709,7 +12796,7 @@ }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] }, "visitCount" : { "type" : "integer", @@ -12747,7 +12834,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string" @@ -12796,6 +12883,9 @@ "type" : "integer", "format" : "int32" }, + "nationalHealthId" : { + "type" : "string" + }, "otherDeletionReason" : { "type" : "string" }, @@ -12873,7 +12963,7 @@ }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] }, "visitCount" : { "type" : "integer", @@ -13020,7 +13110,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "OTHER" ] + "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "ENZYME_LINKED_IMMUNOSORBENT_ASSAY", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "IGRA", "TST", "BEIJINGGENOTYPING", "SPOLIGOTYPING", "MIRU_PATTERN_CODE", "ANTIBIOTIC_SUSCEPTIBILITY", "MULTILOCUS_SEQUENCE_TYPING", "SLIDE_AGGLUTINATION", "WHOLE_GENOME_SEQUENCING", "SEROGROUPING", "GENOTYPING", "NAAT", "THICK_BLOOD_SMEAR", "THIN_BLOOD_SMEAR", "Q_PCR", "LAMP", "OTHER_ANTIGEN_DETECTION_TEST", "OTHER_MOLECULAR_ASSAY", "OTHER_SEROLOGICAL_TEST", "OTHER" ] } } } @@ -13049,14 +13139,14 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "OTHER" ] + "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "ENZYME_LINKED_IMMUNOSORBENT_ASSAY", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "IGRA", "TST", "BEIJINGGENOTYPING", "SPOLIGOTYPING", "MIRU_PATTERN_CODE", "ANTIBIOTIC_SUSCEPTIBILITY", "MULTILOCUS_SEQUENCE_TYPING", "SLIDE_AGGLUTINATION", "WHOLE_GENOME_SEQUENCING", "SEROGROUPING", "GENOTYPING", "NAAT", "THICK_BLOOD_SMEAR", "THIN_BLOOD_SMEAR", "Q_PCR", "LAMP", "OTHER_ANTIGEN_DETECTION_TEST", "OTHER_MOLECULAR_ASSAY", "OTHER_SEROLOGICAL_TEST", "OTHER" ] } }, "sampleTestTypes" : { "type" : "array", "items" : { "type" : "string", - "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "OTHER" ] + "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "ENZYME_LINKED_IMMUNOSORBENT_ASSAY", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "IGRA", "TST", "BEIJINGGENOTYPING", "SPOLIGOTYPING", "MIRU_PATTERN_CODE", "ANTIBIOTIC_SUSCEPTIBILITY", "MULTILOCUS_SEQUENCE_TYPING", "SLIDE_AGGLUTINATION", "WHOLE_GENOME_SEQUENCING", "SEROGROUPING", "GENOTYPING", "NAAT", "THICK_BLOOD_SMEAR", "THIN_BLOOD_SMEAR", "Q_PCR", "LAMP", "OTHER_ANTIGEN_DETECTION_TEST", "OTHER_MOLECULAR_ASSAY", "OTHER_SEROLOGICAL_TEST", "OTHER" ] }, "writeOnly" : true } @@ -13186,7 +13276,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "inJurisdiction" : { "type" : "boolean" @@ -13318,6 +13408,9 @@ "maxLength" : 255, "minLength" : 0 }, + "nutsCode" : { + "type" : "string" + }, "region" : { "$ref" : "#/components/schemas/RegionReferenceDto" }, @@ -13352,10 +13445,18 @@ "type" : "integer", "format" : "int32" }, + "birthdateFrom" : { + "type" : "string", + "format" : "date-time" + }, "birthdateMM" : { "type" : "integer", "format" : "int32" }, + "birthdateTo" : { + "type" : "string", + "format" : "date-time" + }, "birthdateYYYY" : { "type" : "integer", "format" : "int32" @@ -13412,7 +13513,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseVariant" : { "$ref" : "#/components/schemas/DiseaseVariant" @@ -13462,6 +13563,9 @@ "includeContactsFromOtherJurisdictions" : { "type" : "boolean" }, + "includePartialMatch" : { + "type" : "boolean" + }, "lastContactDateFrom" : { "type" : "string", "format" : "date-time" @@ -13491,6 +13595,16 @@ "personLike" : { "type" : "string" }, + "prescribedDrug" : { + "type" : "string", + "enum" : [ "AMIKACIN", "BEDAQUILINE", "CAPREOMYCIN", "CIPROFLOXACIN", "DELAMANID", "ETHAMBUTOL", "GATIFLOXACIN", "ISONIAZID", "KANAMYCIN", "LEVOFLOXACIN", "MOXIFLOXACIN", "OFLOXACIN", "RIFAMPICIN", "STREPTOMYCIN", "CEFTRIAXONE", "PENICILLIN", "ERYTHROMYCIN", "OTHER" ] + }, + "prescribedDrugText" : { + "type" : "string" + }, + "prophylaxisPrescribed" : { + "type" : "boolean" + }, "quarantineFrom" : { "type" : "string", "format" : "date-time" @@ -13557,7 +13671,7 @@ }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] }, "withCase" : { "type" : "boolean" @@ -13635,7 +13749,8 @@ "items" : { "type" : "string", "enum" : [ "TOUCHED_FLUID", "PHYSICAL_CONTACT", "CLOTHES_OR_OTHER", "CLOSE_CONTACT", "FACE_TO_FACE_LONG", "MEDICAL_UNSAFE", "SAME_ROOM", "AIRPLANE", "FACE_TO_FACE_SHORT", "MEDICAL_SAFE", "MEDICAL_SAME_ROOM", "AEROSOL", "MEDICAL_DISTANT", "MEDICAL_LIMITED" ] - } + }, + "uniqueItems" : true }, "contactProximityDetails" : { "type" : "string", @@ -13664,7 +13779,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string", @@ -13729,6 +13844,9 @@ "highPriority" : { "type" : "boolean" }, + "immuneGlobulinProposed" : { + "type" : "boolean" + }, "immunosuppressiveTherapyBasicDisease" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -13767,6 +13885,13 @@ "person" : { "$ref" : "#/components/schemas/PersonReferenceDto" }, + "prescribedDrug" : { + "type" : "string", + "enum" : [ "AMIKACIN", "BEDAQUILINE", "CAPREOMYCIN", "CIPROFLOXACIN", "DELAMANID", "ETHAMBUTOL", "GATIFLOXACIN", "ISONIAZID", "KANAMYCIN", "LEVOFLOXACIN", "MOXIFLOXACIN", "OFLOXACIN", "RIFAMPICIN", "STREPTOMYCIN", "CEFTRIAXONE", "PENICILLIN", "ERYTHROMYCIN", "OTHER" ] + }, + "prescribedDrugText" : { + "type" : "string" + }, "previousQuarantineTo" : { "type" : "string", "format" : "date-time" @@ -13783,6 +13908,9 @@ "type" : "string", "format" : "date-time" }, + "prophylaxisPrescribed" : { + "type" : "boolean" + }, "pseudonymized" : { "type" : "boolean" }, @@ -13924,9 +14052,20 @@ "minLength" : 20, "pattern" : "^[0-9a-zA-Z-]*$" }, + "vaccinationDoseOneDate" : { + "type" : "string", + "format" : "date-time" + }, + "vaccinationDoseTwoDate" : { + "type" : "string", + "format" : "date-time" + }, + "vaccinationProposed" : { + "type" : "boolean" + }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] } }, "required" : [ "disease", "person", "reportDateTime" ] @@ -13985,7 +14124,8 @@ "items" : { "type" : "string", "enum" : [ "TOUCHED_FLUID", "PHYSICAL_CONTACT", "CLOTHES_OR_OTHER", "CLOSE_CONTACT", "FACE_TO_FACE_LONG", "MEDICAL_UNSAFE", "SAME_ROOM", "AIRPLANE", "FACE_TO_FACE_SHORT", "MEDICAL_SAFE", "MEDICAL_SAME_ROOM", "AEROSOL", "MEDICAL_DISTANT", "MEDICAL_LIMITED" ] - } + }, + "uniqueItems" : true }, "contactStatus" : { "type" : "string", @@ -13997,7 +14137,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string" @@ -14032,6 +14172,10 @@ "houseNumber" : { "type" : "string" }, + "id" : { + "type" : "integer", + "format" : "int64" + }, "inJurisdiction" : { "type" : "boolean" }, @@ -14055,6 +14199,9 @@ "type" : "integer", "format" : "int32" }, + "nationalHealthId" : { + "type" : "string" + }, "otherDeletionReason" : { "type" : "string" }, @@ -14067,6 +14214,16 @@ "postalCode" : { "type" : "string" }, + "prescribedDrug" : { + "type" : "string", + "enum" : [ "AMIKACIN", "BEDAQUILINE", "CAPREOMYCIN", "CIPROFLOXACIN", "DELAMANID", "ETHAMBUTOL", "GATIFLOXACIN", "ISONIAZID", "KANAMYCIN", "LEVOFLOXACIN", "MOXIFLOXACIN", "OFLOXACIN", "RIFAMPICIN", "STREPTOMYCIN", "CEFTRIAXONE", "PENICILLIN", "ERYTHROMYCIN", "OTHER" ] + }, + "prescribedDrugText" : { + "type" : "string" + }, + "prophylaxisPrescribed" : { + "type" : "boolean" + }, "pseudonymized" : { "type" : "boolean" }, @@ -14103,7 +14260,7 @@ }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] }, "visitCount" : { "type" : "integer", @@ -14156,7 +14313,8 @@ "items" : { "type" : "string", "enum" : [ "TOUCHED_FLUID", "PHYSICAL_CONTACT", "CLOTHES_OR_OTHER", "CLOSE_CONTACT", "FACE_TO_FACE_LONG", "MEDICAL_UNSAFE", "SAME_ROOM", "AIRPLANE", "FACE_TO_FACE_SHORT", "MEDICAL_SAFE", "MEDICAL_SAME_ROOM", "AEROSOL", "MEDICAL_DISTANT", "MEDICAL_LIMITED" ] - } + }, + "uniqueItems" : true }, "contactStatus" : { "type" : "string", @@ -14168,7 +14326,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string" @@ -14196,6 +14354,10 @@ "type" : "string", "format" : "date-time" }, + "id" : { + "type" : "integer", + "format" : "int64" + }, "inJurisdiction" : { "type" : "boolean" }, @@ -14213,12 +14375,25 @@ "type" : "integer", "format" : "int32" }, + "nationalHealthId" : { + "type" : "string" + }, "otherDeletionReason" : { "type" : "string" }, "personUuid" : { "type" : "string" }, + "prescribedDrug" : { + "type" : "string", + "enum" : [ "AMIKACIN", "BEDAQUILINE", "CAPREOMYCIN", "CIPROFLOXACIN", "DELAMANID", "ETHAMBUTOL", "GATIFLOXACIN", "ISONIAZID", "KANAMYCIN", "LEVOFLOXACIN", "MOXIFLOXACIN", "OFLOXACIN", "RIFAMPICIN", "STREPTOMYCIN", "CEFTRIAXONE", "PENICILLIN", "ERYTHROMYCIN", "OTHER" ] + }, + "prescribedDrugText" : { + "type" : "string" + }, + "prophylaxisPrescribed" : { + "type" : "boolean" + }, "pseudonymized" : { "type" : "boolean" }, @@ -14241,7 +14416,7 @@ }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] }, "visitCount" : { "type" : "integer", @@ -14478,6 +14653,9 @@ "maxLength" : 3, "minLength" : 2 }, + "nutsCode" : { + "type" : "string" + }, "subcontinent" : { "$ref" : "#/components/schemas/SubcontinentReferenceDto" }, @@ -14520,6 +14698,9 @@ "isoCode" : { "type" : "string" }, + "nutsCode" : { + "type" : "string" + }, "subcontinent" : { "$ref" : "#/components/schemas/SubcontinentReferenceDto" }, @@ -15177,7 +15358,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "uniqueItems" : true }, @@ -15514,7 +15695,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -15550,7 +15731,7 @@ "properties" : { "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string" @@ -15637,7 +15818,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "eventCount" : { "type" : "integer", @@ -15678,7 +15859,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "notACaseCriteria" : { "$ref" : "#/components/schemas/ClassificationCriteriaDto" @@ -15716,6 +15897,9 @@ "type" : "integer", "format" : "int32" }, + "caseDefinitionText" : { + "type" : "string" + }, "caseFollowUpDuration" : { "type" : "integer", "format" : "int32" @@ -15733,7 +15917,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "eventParticipantFollowUpDuration" : { "type" : "integer", @@ -15752,6 +15936,17 @@ "followUpEnabled" : { "type" : "boolean" }, + "incubationPeriodEnabled" : { + "type" : "boolean" + }, + "maxIncubationPeriod" : { + "type" : "integer", + "format" : "int32" + }, + "minIncubationPeriod" : { + "type" : "integer", + "format" : "int32" + }, "primaryDisease" : { "type" : "boolean" }, @@ -15844,6 +16039,9 @@ "maxLength" : 255, "minLength" : 0 }, + "nutsCode" : { + "type" : "string" + }, "region" : { "$ref" : "#/components/schemas/RegionReferenceDto" }, @@ -15882,6 +16080,9 @@ "name" : { "type" : "string" }, + "nutsCode" : { + "type" : "string" + }, "population" : { "type" : "integer", "format" : "int32" @@ -15918,7 +16119,7 @@ "properties" : { "documentRelatedEntityType" : { "type" : "string", - "enum" : [ "CASE", "CONTACT", "ACTION", "EVENT", "TRAVEL_ENTRY" ] + "enum" : [ "CASE", "CONTACT", "ACTION", "EVENT", "TRAVEL_ENTRY", "SURVEY" ] }, "entityUuids" : { "type" : "array", @@ -15972,101 +16173,272 @@ }, "required" : [ "mimeType", "name", "size", "uploadingUser" ] }, - "EnvironmentDto" : { + "DrugSusceptibilityDto" : { "type" : "object", "properties" : { + "amikacinMic" : { + "type" : "number", + "format" : "float" + }, + "amikacinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "bedaquilineMic" : { + "type" : "number", + "format" : "float" + }, + "bedaquilineSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "capreomycinMic" : { + "type" : "number", + "format" : "float" + }, + "capreomycinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "ceftriaxoneMic" : { + "type" : "number", + "format" : "float" + }, + "ceftriaxoneSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, "changeDate" : { "type" : "string", "format" : "date-time" }, + "ciprofloxacinMic" : { + "type" : "number", + "format" : "float" + }, + "ciprofloxacinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, "creationDate" : { "type" : "string", "format" : "date-time" }, - "deleted" : { - "type" : "boolean" + "delamanidMic" : { + "type" : "number", + "format" : "float" }, - "deletionReason" : { + "delamanidSusceptibility" : { "type" : "string", - "enum" : [ "GDPR", "DELETION_REQUEST", "CREATED_WITH_NO_LEGAL_REASON", "TRANSFERRED_RESPONSIBILITY", "DUPLICATE_ENTRIES", "OTHER_REASON" ] + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, - "description" : { - "type" : "string", - "maxLength" : 1000000, - "minLength" : 0 + "erythromycinMic" : { + "type" : "number", + "format" : "float" }, - "environmentMedia" : { + "erythromycinSusceptibility" : { "type" : "string", - "enum" : [ "WATER", "SOIL_ROCK", "AIR", "BIOTA" ] + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, - "environmentName" : { + "ethambutolMic" : { + "type" : "number", + "format" : "float" + }, + "ethambutolSusceptibility" : { "type" : "string", - "maxLength" : 1000000, - "minLength" : 0 + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, - "externalId" : { + "gatifloxacinMic" : { + "type" : "number", + "format" : "float" + }, + "gatifloxacinSusceptibility" : { "type" : "string", - "maxLength" : 512, - "minLength" : 0 + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, "inJurisdiction" : { "type" : "boolean" }, - "infrastructureDetails" : { + "isoniazidMic" : { + "type" : "number", + "format" : "float" + }, + "isoniazidSusceptibility" : { "type" : "string", - "enum" : [ "SEPTIC_TANK", "LATRIN", "TOILET", "MANHOLE", "WELLS", "SURFACE_WATER", "OPEN_DRAIN", "OTHER", "UNKNOWN" ] + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, - "investigationStatus" : { + "kanamycinMic" : { + "type" : "number", + "format" : "float" + }, + "kanamycinSusceptibility" : { "type" : "string", - "enum" : [ "PENDING", "DONE", "DISCARDED" ] + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, - "location" : { - "$ref" : "#/components/schemas/LocationDto" + "levofloxacinMic" : { + "type" : "number", + "format" : "float" }, - "otherDeletionReason" : { + "levofloxacinSusceptibility" : { "type" : "string", - "maxLength" : 1000000, - "minLength" : 0 + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, - "otherInfrastructureDetails" : { + "moxifloxacinMic" : { + "type" : "number", + "format" : "float" + }, + "moxifloxacinSusceptibility" : { "type" : "string", - "maxLength" : 1000000, - "minLength" : 0 + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, - "otherWaterType" : { + "ofloxacinMic" : { + "type" : "number", + "format" : "float" + }, + "ofloxacinSusceptibility" : { "type" : "string", - "maxLength" : 1000000, - "minLength" : 0 + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, - "otherWaterUse" : { + "penicillinMic" : { + "type" : "number", + "format" : "float" + }, + "penicillinSusceptibility" : { "type" : "string", - "maxLength" : 1000000, - "minLength" : 0 + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, "pseudonymized" : { "type" : "boolean" }, - "reportDate" : { + "rifampicinMic" : { + "type" : "number", + "format" : "float" + }, + "rifampicinSusceptibility" : { "type" : "string", - "format" : "date-time" + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, - "reportingUser" : { - "$ref" : "#/components/schemas/UserReferenceDto" + "streptomycinMic" : { + "type" : "number", + "format" : "float" }, - "responsibleUser" : { - "$ref" : "#/components/schemas/UserReferenceDto" + "streptomycinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] }, "uuid" : { "type" : "string", "maxLength" : 36, "minLength" : 20, "pattern" : "^[0-9a-zA-Z-]*$" - }, - "waterType" : { + } + } + }, + "EnvironmentDto" : { + "type" : "object", + "properties" : { + "changeDate" : { "type" : "string", - "enum" : [ "WASTEWATER", "GROUNDWATER", "SURFACE_WATER", "PRECIPITATION", "OTHER", "UNKNOWN" ] + "format" : "date-time" }, - "waterUse" : { + "creationDate" : { + "type" : "string", + "format" : "date-time" + }, + "deleted" : { + "type" : "boolean" + }, + "deletionReason" : { + "type" : "string", + "enum" : [ "GDPR", "DELETION_REQUEST", "CREATED_WITH_NO_LEGAL_REASON", "TRANSFERRED_RESPONSIBILITY", "DUPLICATE_ENTRIES", "OTHER_REASON" ] + }, + "description" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, + "environmentMedia" : { + "type" : "string", + "enum" : [ "WATER", "SOIL_ROCK", "AIR", "BIOTA", "VECTORS" ] + }, + "environmentName" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, + "eventReferenceDtos" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/EventReferenceDto" + } + }, + "externalId" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, + "inJurisdiction" : { + "type" : "boolean" + }, + "infrastructureDetails" : { + "type" : "string", + "enum" : [ "SEPTIC_TANK", "LATRIN", "TOILET", "MANHOLE", "WELLS", "SURFACE_WATER", "OPEN_DRAIN", "OTHER", "UNKNOWN" ] + }, + "investigationStatus" : { + "type" : "string", + "enum" : [ "PENDING", "DONE", "DISCARDED" ] + }, + "location" : { + "$ref" : "#/components/schemas/LocationDto" + }, + "otherDeletionReason" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, + "otherInfrastructureDetails" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, + "otherWaterType" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, + "otherWaterUse" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, + "pseudonymized" : { + "type" : "boolean" + }, + "reportDate" : { + "type" : "string", + "format" : "date-time" + }, + "reportingUser" : { + "$ref" : "#/components/schemas/UserReferenceDto" + }, + "responsibleUser" : { + "$ref" : "#/components/schemas/UserReferenceDto" + }, + "uuid" : { + "type" : "string", + "maxLength" : 36, + "minLength" : 20, + "pattern" : "^[0-9a-zA-Z-]*$" + }, + "vectorType" : { + "type" : "string", + "enum" : [ "MOSQUITOS", "TICKS" ] + }, + "waterType" : { + "type" : "string", + "enum" : [ "WASTEWATER", "GROUNDWATER", "SURFACE_WATER", "PRECIPITATION", "OTHER", "UNKNOWN" ] + }, + "waterUse" : { "type" : "object", "additionalProperties" : { "type" : "boolean" @@ -16239,6 +16611,10 @@ "minLength" : 20, "pattern" : "^[0-9a-zA-Z-]*$" }, + "vectorType" : { + "type" : "string", + "enum" : [ "MOSQUITOS", "TICKS" ] + }, "weatherConditions" : { "type" : "object", "additionalProperties" : { @@ -16278,14 +16654,31 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "caseImportedStatus" : { + "type" : "string", + "enum" : [ "IMPORTED_CASE", "IMPORT_RELATED_CASE", "UNKNOWN_IMPORTATION_STATUS", "NOT_IMPORTED_CASE" ] + }, "changeDate" : { "type" : "string", "format" : "date-time" }, + "clusterRelated" : { + "type" : "boolean" + }, + "clusterType" : { + "type" : "string", + "enum" : [ "KINDERGARTEN_OR_CHILDCARE", "FAMILY", "MILITARY", "NOSOCOMIAL", "SCHOOL", "SPORTS_TEAM", "UNIVERSITY", "OTHER" ] + }, + "clusterTypeText" : { + "type" : "string" + }, "contactWithSourceCaseKnown" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "country" : { + "$ref" : "#/components/schemas/CountryReferenceDto" + }, "creationDate" : { "type" : "string", "format" : "date-time" @@ -16304,13 +16697,36 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "importedCase" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "inJurisdiction" : { "type" : "boolean" }, + "infectionSource" : { + "type" : "string", + "enum" : [ "FOOD", "ANIMAL", "NOT_APPLICABLE", "OTHER" ] + }, + "infectionSourceText" : { + "type" : "string" + }, "largeOutbreaksArea" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "modeOfTransmission" : { + "type" : "string", + "enum" : [ "ANIMAL_TO_HUMAN", "FOOD_OR_WATER", "PERSON_TO_PERSON", "RECREATIONAL_WATER", "HEALTHCARE_ASSOCIATED", "INJECTING_DRUG_USERS", "LAB_OCCUPATIONAL_EXPOSURE", "MOTHER_TO_CHILD", "SEXUAL", "TRANSFUSION_RECIPIENT", "ORGAN_RECIPIENT", "UNKNOWN", "OTHER" ] + }, + "modeOfTransmissionType" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, + "otherDetails" : { + "type" : "string" + }, "pseudonymized" : { "type" : "boolean" }, @@ -16373,7 +16789,7 @@ }, "eventDisease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "eventDiseaseDetails" : { "type" : "string" @@ -16476,7 +16892,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseVariant" : { "$ref" : "#/components/schemas/DiseaseVariant" @@ -16484,6 +16900,9 @@ "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" }, + "environment" : { + "$ref" : "#/components/schemas/EnvironmentReferenceDto" + }, "eventDateFrom" : { "type" : "string", "format" : "date-time" @@ -16593,7 +17012,7 @@ }, "typeOfPlace" : { "type" : "string", - "enum" : [ "FACILITY", "FACILITY_23_IFSG", "COMMUNITY_FACILITY", "FACILITY_36_IFSG", "FESTIVITIES", "HOME", "MEANS_OF_TRANSPORT", "PUBLIC_PLACE", "SCATTERED", "UNKNOWN", "OTHER" ] + "enum" : [ "FACILITY", "FACILITY_23_IFSG", "COMMUNITY_FACILITY", "FACILITY_36_IFSG", "FESTIVITIES", "HOME", "MEANS_OF_TRANSPORT", "PUBLIC_PLACE", "SCATTERED", "SCHOOL", "EDUCATION_AND_CHILDCARE", "NURSING_HOME", "ASYLUM_SEEKERS_SHELTER", "UNKNOWN", "OTHER" ] }, "userFilterIncluded" : { "type" : "boolean" @@ -16625,7 +17044,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string", @@ -16648,6 +17067,12 @@ "type" : "string", "format" : "date-time" }, + "environmentReferenceDtos" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/EnvironmentReferenceDto" + } + }, "epidemiologicalEvidence" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -16876,7 +17301,7 @@ }, "typeOfPlace" : { "type" : "string", - "enum" : [ "FACILITY", "FACILITY_23_IFSG", "COMMUNITY_FACILITY", "FACILITY_36_IFSG", "FESTIVITIES", "HOME", "MEANS_OF_TRANSPORT", "PUBLIC_PLACE", "SCATTERED", "UNKNOWN", "OTHER" ] + "enum" : [ "FACILITY", "FACILITY_23_IFSG", "COMMUNITY_FACILITY", "FACILITY_36_IFSG", "FESTIVITIES", "HOME", "MEANS_OF_TRANSPORT", "PUBLIC_PLACE", "SCATTERED", "SCHOOL", "EDUCATION_AND_CHILDCARE", "NURSING_HOME", "ASYLUM_SEEKERS_SHELTER", "UNKNOWN", "OTHER" ] }, "typeOfPlaceText" : { "type" : "string", @@ -17047,7 +17472,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string" @@ -17220,7 +17645,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "event" : { "$ref" : "#/components/schemas/EventReferenceDto" @@ -17239,7 +17664,7 @@ }, "pathogenTestResult" : { "type" : "string", - "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE" ] + "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE", "NOT_APPLICABLE" ] }, "person" : { "$ref" : "#/components/schemas/PersonReferenceDto" @@ -17254,7 +17679,7 @@ }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] } } }, @@ -17324,7 +17749,7 @@ }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] } }, "required" : [ "event", "person" ] @@ -17373,7 +17798,7 @@ }, "pathogenTestResult" : { "type" : "string", - "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE" ] + "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE", "NOT_APPLICABLE" ] }, "personUuid" : { "type" : "string" @@ -17397,7 +17822,7 @@ }, "vaccinationStatus" : { "type" : "string", - "enum" : [ "VACCINATED", "UNVACCINATED", "UNKNOWN" ] + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] } } }, @@ -17436,6 +17861,13 @@ "ExposureDto" : { "type" : "object", "properties" : { + "animalCategory" : { + "type" : "string", + "enum" : [ "DOMESTIC", "WILD" ] + }, + "animalCategoryDetails" : { + "type" : "string" + }, "animalCondition" : { "type" : "string", "enum" : [ "ALIVE", "DEAD", "PROCESSED", "UNKNOWN" ] @@ -17449,6 +17881,15 @@ "maxLength" : 1000000, "minLength" : 0 }, + "animalLocation" : { + "type" : "string", + "enum" : [ "ZOO", "FARM", "PARK", "FOREST", "OTHER" ] + }, + "animalLocationText" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, "animalMarket" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -17465,11 +17906,31 @@ "type" : "string", "format" : "date-time" }, + "childcareFacilityDetails" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, + "conditionOfAnimal" : { + "type" : "string", + "enum" : [ "ALIVE", "DEAD", "PROCESSED", "UNKNOWN" ] + }, "connectionNumber" : { "type" : "string", "maxLength" : 512, "minLength" : 0 }, + "contactFactorDetails" : { + "type" : "string" + }, + "contactFactors" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "DURATION_OF_EXPOSURE", "PROXIMITY_TO_SOURCE", "TYPE_OF_ACTIVITY", "POOR_VENTILATION", "PROXIMITY_AND_DURATION", "WIND_AND_AIRFLOW", "DENSITY_OF_PEOPLE", "SKIN_CONTACT", "BODY_FLUIDS", "BUTCHERING", "COOKING", "TOUCHING_CONTACT_WITH_FLUIDS", "SCRATCHES_BITES_LICKING", "SHARED_SURFACES", "OUTDOOR_ACTIVITIES", "STANDING_WATER_PROXIMITY", "HIGH_MOSQUITO_ACTIVITY_REGIONS", "UNPROTECTED_HOUSEHOLD", "MOSQUITO_ACTIVITY_TIME_OF_DAY", "CLOTHING_COVERAGE", "DURATION_OUTDOORS", "EXPOSED_SKIN", "DRINKING_CONTAMINATED_WATER", "ICE_AND_FOOD_PREPARATION", "SWALLOWING_WATER", "CONTACT_WITH_OPEN_WOUNDS", "EGG", "MEAT", "FISH_SEAFOOD", "DAIRY", "FRUIT", "RAW_VEGETABLES", "UNKNOWN", "OTHER" ] + }, + "uniqueItems" : true + }, "contactToBodyFluids" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -17500,6 +17961,10 @@ "maxLength" : 1000000, "minLength" : 0 }, + "domesticSwimming" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "eatingRawAnimalProducts" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -17508,19 +17973,40 @@ "type" : "string", "format" : "date-time" }, + "exposureCategory" : { + "type" : "string", + "enum" : [ "AIR_BORNE", "ANIMAL_CONTACT", "DIRECT_CONTACT", "FOMITE_TRANSMISSION", "FOOD_BORNE", "VECTOR_BORNE", "VERTICAL_TRANSMISSION", "WATER_BORNE" ] + }, + "exposureComment" : { + "type" : "string" + }, "exposureRole" : { "type" : "string", "enum" : [ "PASSENGER", "STAFF", "NURSING_STAFF", "MEDICAL_STAFF", "VISITOR", "GUEST", "CUSTOMER", "CONSERVATEE", "PATIENT", "EDUCATOR", "TRAINEE_TEACHER", "PUPIL", "STUDENT", "PARENT", "TEACHER", "UNKNOWN", "OTHER" ] }, + "exposureSetting" : { + "type" : "string", + "enum" : [ "INDOOR", "OUTDOOR", "PERSON_TO_PERSON", "MOSQUITO_BORNE", "TICK_BORNE", "DRINKING_WATER", "RECREATIONAL_WATER", "PREGNANCY_OR_DELIVERY", "OTHER", "UNKNOWN" ] + }, + "exposureSettingDetails" : { + "type" : "string" + }, + "exposureSubSettingDetails" : { + "type" : "string" + }, "exposureType" : { "type" : "string", - "enum" : [ "WORK", "TRAVEL", "SPORT", "VISIT", "GATHERING", "HABITATION", "PERSONAL_SERVICES", "BURIAL", "ANIMAL_CONTACT", "OTHER", "UNKNOWN" ] + "enum" : [ "WORK", "TRAVEL", "SPORT", "VISIT", "GATHERING", "HABITATION", "PERSONAL_SERVICES", "CHILDCARE_FACILITY", "BURIAL", "ANIMAL_CONTACT", "RECREATIONAL_WATER", "FOOD", "SEXUAL_CONTACT", "SYMPTOMATIC_CONTACT", "FLOOD_EXPOSURE", "OTHER", "UNKNOWN" ] }, "exposureTypeDetails" : { "type" : "string", "maxLength" : 1000000, "minLength" : 0 }, + "fomiteTransmissionLocation" : { + "type" : "string", + "enum" : [ "INSIDE_HOME", "OUTSIDE" ] + }, "gatheringDetails" : { "type" : "string", "maxLength" : 1000000, @@ -17554,6 +18040,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "internationalSwimming" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "largeAttendanceNumber" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -17605,6 +18095,17 @@ "type" : "string", "format" : "date-time" }, + "protectiveMeasureDetails" : { + "type" : "string" + }, + "protectiveMeasures" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "WEARING_MASK", "DISTANCE_1_5M", "FACE_TO_FACE_15MIN", "VENTILATION_HEPA", "HAND_HYGIENE", "AVOID_TOUCHING_FACE", "SAFE_SEX", "WEARING_PPE", "VACCINATION", "HAND_WASHING", "WOUND_WASHING", "INSECT_REPELLENT", "HERBS", "COILS", "SLEEPING_UNDER_BEDNET", "INDOOR_SPRAYING", "AIR_CONDITION", "PROTECTIVE_CLOTHING", "ENVIRONMENTAL_CONTROL", "TICK_PREVENTION", "REGULAR_CHECKS", "WATER_PURIFICATION", "SAFE_WATER_SOURCES", "AVOID_RAW_FOODS", "NO_SWIMMING_CONTAMINATED", "SHOWER_AFTER_SWIMMING", "WOUND_COVERAGE", "BATHING_CONTAMINATED_WATER", "WELL_COOKED", "COLD_HOT_CHAIN", "WASHED", "MEDICATION", "C_SECTION", "OTHER" ] + }, + "uniqueItems" : true + }, "protectiveMeasuresDetails" : { "type" : "string", "maxLength" : 1000000, @@ -17613,6 +18114,15 @@ "pseudonymized" : { "type" : "boolean" }, + "rawFoodContact" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "rawFoodContactText" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, "reportingUser" : { "$ref" : "#/components/schemas/UserReferenceDto" }, @@ -17625,6 +18135,11 @@ "maxLength" : 512, "minLength" : 0 }, + "sexualExposureText" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, "shortDistance" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -17633,18 +18148,53 @@ "type" : "string", "format" : "date-time" }, + "subSettings" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "CLOSED_POORLY_VENTILATED", "SHARED_HIGH_OCCUPANCY", "ENCLOSED_LIMITED_CIRCULATION", "VEHICLES", "HEALTHCARE_SETTINGS", "TEMPORARY_SHELTERS", "CROWDED_OUTDOOR_LIMITED_AIRFLOW", "CLOSE_PHYSICAL_CONTACT", "HIGH_TOUCH_ENVIRONMENTS", "SEXUAL_ACTIVITY", "STANDING_WATER_AREAS", "HIGH_MOSQUITO_ACTIVITY_REGIONS", "FORESTED_GRASSY_RURAL", "WILDLIFE_RESERVOIR_AREAS", "EATING_AT_HOME", "EATING_OUTSIDE", "UNKNOWN", "OTHER" ] + }, + "uniqueItems" : true + }, + "swimmingLocation" : { + "type" : "string", + "enum" : [ "PUBLIC", "PRIVATE", "LAKE", "RIVER", "OCEAN", "WATER_PARK", "OTHER" ] + }, + "swimmingLocationType" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, + "symptomaticIndividualText" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, + "travelAccommodation" : { + "type" : "string", + "enum" : [ "HOTEL", "CAMPING", "RENTED_APARTMENT_OR_HOUSE", "FRIENDS_OR_FAMILY", "OTHER" ] + }, + "travelAccommodationType" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, "typeOfAnimal" : { "type" : "string", - "enum" : [ "BAT", "POULTRY", "CAMEL", "CANIDAE", "CAT", "CATTLE", "DOG", "PRIMATE", "SNAKE", "SWINE", "RABBIT", "RODENT", "TICK", "FLEA", "OTHER" ] + "enum" : [ "BAT", "POULTRY", "CAMEL", "CANIDAE", "CAT", "CATTLE", "DOG", "PRIMATE", "SNAKE", "SWINE", "RABBIT", "RODENT", "TICK", "FLEA", "BIRDS", "GOAT", "HORSE", "SHEEP", "OTHER" ] }, "typeOfAnimalDetails" : { "type" : "string", "maxLength" : 1000000, "minLength" : 0 }, + "typeOfChildcareFacility" : { + "type" : "string", + "enum" : [ "NURSERY_CRECHE", "NURSERY_SCHOOL", "CHILDMINDER_CENTER", "CHILDCARE_CENTER", "SCHOOL", "OTHER" ] + }, "typeOfPlace" : { "type" : "string", - "enum" : [ "FACILITY", "FACILITY_23_IFSG", "COMMUNITY_FACILITY", "FACILITY_36_IFSG", "FESTIVITIES", "HOME", "MEANS_OF_TRANSPORT", "PUBLIC_PLACE", "SCATTERED", "UNKNOWN", "OTHER" ] + "enum" : [ "FACILITY", "FACILITY_23_IFSG", "COMMUNITY_FACILITY", "FACILITY_36_IFSG", "FESTIVITIES", "HOME", "MEANS_OF_TRANSPORT", "PUBLIC_PLACE", "SCATTERED", "SCHOOL", "EDUCATION_AND_CHILDCARE", "NURSING_HOME", "ASYLUM_SEEKERS_SHELTER", "UNKNOWN", "OTHER" ] }, "typeOfPlaceDetails" : { "type" : "string", @@ -17716,7 +18266,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseVariant" : { "$ref" : "#/components/schemas/DiseaseVariant" @@ -17757,27 +18307,64 @@ "ExternalMessageDto" : { "type" : "object", "properties" : { + "activitiesAsCase" : { + "type" : "string" + }, + "additionalPersonAddresses" : { + "type" : "string" + }, + "additionalPersonContactDetails" : { + "type" : "string" + }, + "admittedToHealthFacility" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "assignee" : { "$ref" : "#/components/schemas/UserReferenceDto" }, "automaticProcessingPossible" : { "type" : "boolean" }, + "caseClassification" : { + "type" : "string", + "enum" : [ "NOT_CLASSIFIED", "SUSPECT", "PROBABLE", "CONFIRMED", "CONFIRMED_NO_SYMPTOMS", "CONFIRMED_UNKNOWN_SYMPTOMS", "NO_CASE" ] + }, + "caseComments" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, "caseReportDate" : { "type" : "string", "format" : "date-time" }, + "caseSymptoms" : { + "$ref" : "#/components/schemas/SymptomsDto" + }, "changeDate" : { "type" : "string", "format" : "date-time" }, + "complianceWithTreatment" : { + "type" : "string", + "enum" : [ "NO_COMPLIANCE", "TREATMENT_COMPLETED", "TREATMENT_FAILED", "TREATMENT_NOT_COMPLETED", "UNKNOWN", "NOT_APPLICABLE" ] + }, "creationDate" : { "type" : "string", "format" : "date-time" }, + "deceasedDate" : { + "type" : "string", + "format" : "date-time" + }, + "diagnosticDate" : { + "type" : "string", + "format" : "date-time" + }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseVariant" : { "$ref" : "#/components/schemas/DiseaseVariant" @@ -17787,21 +18374,95 @@ "maxLength" : 512, "minLength" : 0 }, + "exposures" : { + "type" : "string" + }, "externalMessageDetails" : { "type" : "string", "maxLength" : 1000000, "minLength" : 0 }, - "inJurisdiction" : { - "type" : "boolean" + "hiv" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] }, - "messageDateTime" : { + "hivArt" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "hospitalizationAdmissionDate" : { "type" : "string", "format" : "date-time" }, + "hospitalizationDischargeDate" : { + "type" : "string", + "format" : "date-time" + }, + "hospitalizationFacilityDepartment" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, + "hospitalizationFacilityExternalId" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, + "hospitalizationFacilityName" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, + "inJurisdiction" : { + "type" : "boolean" + }, + "messageDateTime" : { + "type" : "string", + "format" : "date-time" + }, + "notifierAddress" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, + "notifierEmail" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, + "notifierFirstName" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, + "notifierLastName" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, + "notifierPhone" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, + "notifierRegistrationNumber" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, + "otherDiagnosticCriteria" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, "ownershipHandedOver" : { "type" : "boolean" }, + "personAdditionalDetails" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 + }, "personBirthDateDD" : { "type" : "integer", "format" : "int32" @@ -17840,6 +18501,31 @@ "maxLength" : 255, "minLength" : 0 }, + "personGuardianEmail" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, + "personGuardianFirstName" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, + "personGuardianLastName" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, + "personGuardianPhone" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, + "personGuardianRelationship" : { + "type" : "string", + "maxLength" : 255, + "minLength" : 0 + }, "personHouseNumber" : { "type" : "string", "maxLength" : 255, @@ -17855,6 +18541,11 @@ "maxLength" : 255, "minLength" : 0 }, + "personOccupation" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, "personPhone" : { "type" : "string", "maxLength" : 255, @@ -17882,9 +18573,17 @@ "maxLength" : 255, "minLength" : 0 }, + "previousTuberculosisTreatment" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "pseudonymized" : { "type" : "boolean" }, + "radiographyCompatibility" : { + "type" : "string", + "enum" : [ "COMPATIBLE_WITH_TB", "NOT_COMPATIBLE_WITH_TB" ] + }, "reportId" : { "type" : "string", "maxLength" : 512, @@ -17941,6 +18640,34 @@ "surveillanceReport" : { "$ref" : "#/components/schemas/SurveillanceReportReferenceDto" }, + "treatmentNotApplicable" : { + "type" : "boolean" + }, + "treatmentStarted" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "treatmentStartedDate" : { + "type" : "string", + "format" : "date-time" + }, + "tuberculosis" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "tuberculosisBeijingLineage" : { + "type" : "boolean" + }, + "tuberculosisDirectlyObservedTreatment" : { + "type" : "boolean" + }, + "tuberculosisInfectionYear" : { + "type" : "integer", + "format" : "int32" + }, + "tuberculosisMdrXdrTuberculosis" : { + "type" : "boolean" + }, "type" : { "type" : "string", "enum" : [ "LAB_MESSAGE", "PHYSICIANS_REPORT" ] @@ -17950,6 +18677,10 @@ "maxLength" : 36, "minLength" : 20, "pattern" : "^[0-9a-zA-Z-]*$" + }, + "vaccinationStatus" : { + "type" : "string", + "enum" : [ "VACCINATED", "UNVACCINATED", "VACCINATED_ONE_DOSE", "VACCINATED_TWO_DOSE", "RECOVERED", "OTHER", "UNKNOWN" ] } } }, @@ -17961,7 +18692,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseVariant" : { "$ref" : "#/components/schemas/DiseaseVariant" @@ -18023,7 +18754,7 @@ "properties" : { "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "personUuid" : { "type" : "string", @@ -18287,7 +19018,7 @@ "properties" : { "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -18299,7 +19030,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "AGGREGATE_REPORTING", "CAMPAIGNS", "CASE_SURVEILANCE", "CLINICAL_MANAGEMENT", "CONTACT_TRACING", "EVENT_SURVEILLANCE", "SAMPLES_LAB", "ADDITIONAL_TESTS", "TASK_MANAGEMENT", "WEEKLY_REPORTING", "IMMUNIZATION_MANAGEMENT", "TRAVEL_ENTRIES", "DASHBOARD_SURVEILLANCE", "DASHBOARD_CONTACTS", "DASHBOARD_CAMPAIGNS", "DASHBOARD_SAMPLES", "LIMITED_SYNCHRONIZATION", "ENVIRONMENT_MANAGEMENT", "SELF_REPORTING", "ASSIGN_TASKS_TO_HIGHER_LEVEL", "CASE_FOLLOWUP", "DOCUMENTS", "DOCUMENTS_MULTI_UPLOAD", "EVENT_GROUPS", "EVENT_HIERARCHIES", "EXTERNAL_MESSAGES", "MANUAL_EXTERNAL_MESSAGES", "NATIONAL_CASE_SHARING", "SURVEILLANCE_REPORTS", "SORMAS_TO_SORMAS_ACCEPT_REJECT", "SORMAS_TO_SORMAS_SHARE_CASES", "SORMAS_TO_SORMAS_SHARE_CONTACTS", "SORMAS_TO_SORMAS_SHARE_EVENTS", "SORMAS_TO_SORMAS_SHARE_EXTERNAL_MESSAGES", "IMMUNIZATION_STATUS_AUTOMATION", "PERSON_DUPLICATE_CUSTOM_SEARCH", "EDIT_INFRASTRUCTURE_DATA", "AUTOMATIC_ARCHIVING", "EDIT_ARCHIVED_ENTITIES", "EXTERNAL_EMAILS", "HIDE_JURISDICTION_FIELDS", "VIEW_TAB_CASES_HOSPITALIZATION", "VIEW_TAB_CASES_SYMPTOMS", "VIEW_TAB_CASES_EPIDEMIOLOGICAL_DATA", "VIEW_TAB_CASES_THERAPY", "VIEW_TAB_CASES_FOLLOW_UP", "VIEW_TAB_CASES_CLINICAL_COURSE", "VIEW_TAB_CONTACTS_EPIDEMIOLOGICAL_DATA", "VIEW_TAB_CONTACTS_FOLLOW_UP_VISITS", "GDPR_CONSENT_POPUP", "INFRASTRUCTURE_TYPE_AREA", "OUTBREAKS", "PERSON_MANAGEMENT", "AUTH_PROVIDER_TO_SORMAS_USER_SYNC", "LINE_LISTING", "EVENT_GROUPS_MODIFICATION_NOTIFICATIONS", "EVENT_PARTICIPANT_CASE_CONFIRMED_NOTIFICATIONS", "EVENT_PARTICIPANT_RELATED_TO_OTHER_EVENTS_NOTIFICATIONS", "TASK_NOTIFICATIONS", "OTHER_NOTIFICATIONS", "TASK_GENERATION_CASE_SURVEILLANCE", "TASK_GENERATION_CONTACT_TRACING", "TASK_GENERATION_EVENT_SURVEILLANCE", "TASK_GENERATION_GENERAL", "CASE_AND_CONTACT_BULK_ACTIONS" ] + "enum" : [ "AGGREGATE_REPORTING", "CAMPAIGNS", "CASE_SURVEILANCE", "CLINICAL_MANAGEMENT", "CONTACT_TRACING", "EVENT_SURVEILLANCE", "SAMPLES_LAB", "ADDITIONAL_TESTS", "TASK_MANAGEMENT", "WEEKLY_REPORTING", "IMMUNIZATION_MANAGEMENT", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_MANAGEMENT", "TRAVEL_ENTRIES", "DASHBOARD_SURVEILLANCE", "DASHBOARD_CONTACTS", "DASHBOARD_CAMPAIGNS", "DASHBOARD_SAMPLES", "LIMITED_SYNCHRONIZATION", "ENVIRONMENT_MANAGEMENT", "SELF_REPORTING", "SURVEYS", "ASSIGN_TASKS_TO_HIGHER_LEVEL", "CASE_FOLLOWUP", "DOCUMENTS", "DOCUMENTS_MULTI_UPLOAD", "EVENT_GROUPS", "EVENT_HIERARCHIES", "EXTERNAL_MESSAGES", "MANUAL_EXTERNAL_MESSAGES", "NATIONAL_CASE_SHARING", "SURVEILLANCE_REPORTS", "SORMAS_TO_SORMAS_ACCEPT_REJECT", "SORMAS_TO_SORMAS_SHARE_CASES", "SORMAS_TO_SORMAS_SHARE_CONTACTS", "SORMAS_TO_SORMAS_SHARE_EVENTS", "SORMAS_TO_SORMAS_SHARE_EXTERNAL_MESSAGES", "IMMUNIZATION_STATUS_AUTOMATION", "PERSON_DUPLICATE_CUSTOM_SEARCH", "EDIT_INFRASTRUCTURE_DATA", "AUTOMATIC_ARCHIVING", "EDIT_ARCHIVED_ENTITIES", "EXTERNAL_EMAILS", "HIDE_JURISDICTION_FIELDS", "VIEW_TAB_CASES_HOSPITALIZATION", "VIEW_TAB_CASES_SYMPTOMS", "VIEW_TAB_CASES_EPIDEMIOLOGICAL_DATA", "VIEW_TAB_CASES_THERAPY", "VIEW_TAB_CASES_FOLLOW_UP", "VIEW_TAB_CASES_CLINICAL_COURSE", "VIEW_TAB_CONTACTS_EPIDEMIOLOGICAL_DATA", "VIEW_TAB_CONTACTS_FOLLOW_UP_VISITS", "GDPR_CONSENT_POPUP", "INFRASTRUCTURE_TYPE_AREA", "OUTBREAKS", "PERSON_MANAGEMENT", "AUTH_PROVIDER_TO_SORMAS_USER_SYNC", "LINE_LISTING", "EVENT_GROUPS_MODIFICATION_NOTIFICATIONS", "EVENT_PARTICIPANT_CASE_CONFIRMED_NOTIFICATIONS", "EVENT_PARTICIPANT_RELATED_TO_OTHER_EVENTS_NOTIFICATIONS", "TASK_NOTIFICATIONS", "OTHER_NOTIFICATIONS", "TASK_GENERATION_CASE_SURVEILLANCE", "TASK_GENERATION_CONTACT_TRACING", "TASK_GENERATION_EVENT_SURVEILLANCE", "TASK_GENERATION_GENERAL", "CASE_AND_CONTACT_BULK_ACTIONS", "EPIPULSE_EXPORT" ] } }, "region" : { @@ -18323,7 +19054,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -18337,7 +19068,7 @@ }, "featureType" : { "type" : "string", - "enum" : [ "AGGREGATE_REPORTING", "CAMPAIGNS", "CASE_SURVEILANCE", "CLINICAL_MANAGEMENT", "CONTACT_TRACING", "EVENT_SURVEILLANCE", "SAMPLES_LAB", "ADDITIONAL_TESTS", "TASK_MANAGEMENT", "WEEKLY_REPORTING", "IMMUNIZATION_MANAGEMENT", "TRAVEL_ENTRIES", "DASHBOARD_SURVEILLANCE", "DASHBOARD_CONTACTS", "DASHBOARD_CAMPAIGNS", "DASHBOARD_SAMPLES", "LIMITED_SYNCHRONIZATION", "ENVIRONMENT_MANAGEMENT", "SELF_REPORTING", "ASSIGN_TASKS_TO_HIGHER_LEVEL", "CASE_FOLLOWUP", "DOCUMENTS", "DOCUMENTS_MULTI_UPLOAD", "EVENT_GROUPS", "EVENT_HIERARCHIES", "EXTERNAL_MESSAGES", "MANUAL_EXTERNAL_MESSAGES", "NATIONAL_CASE_SHARING", "SURVEILLANCE_REPORTS", "SORMAS_TO_SORMAS_ACCEPT_REJECT", "SORMAS_TO_SORMAS_SHARE_CASES", "SORMAS_TO_SORMAS_SHARE_CONTACTS", "SORMAS_TO_SORMAS_SHARE_EVENTS", "SORMAS_TO_SORMAS_SHARE_EXTERNAL_MESSAGES", "IMMUNIZATION_STATUS_AUTOMATION", "PERSON_DUPLICATE_CUSTOM_SEARCH", "EDIT_INFRASTRUCTURE_DATA", "AUTOMATIC_ARCHIVING", "EDIT_ARCHIVED_ENTITIES", "EXTERNAL_EMAILS", "HIDE_JURISDICTION_FIELDS", "VIEW_TAB_CASES_HOSPITALIZATION", "VIEW_TAB_CASES_SYMPTOMS", "VIEW_TAB_CASES_EPIDEMIOLOGICAL_DATA", "VIEW_TAB_CASES_THERAPY", "VIEW_TAB_CASES_FOLLOW_UP", "VIEW_TAB_CASES_CLINICAL_COURSE", "VIEW_TAB_CONTACTS_EPIDEMIOLOGICAL_DATA", "VIEW_TAB_CONTACTS_FOLLOW_UP_VISITS", "GDPR_CONSENT_POPUP", "INFRASTRUCTURE_TYPE_AREA", "OUTBREAKS", "PERSON_MANAGEMENT", "AUTH_PROVIDER_TO_SORMAS_USER_SYNC", "LINE_LISTING", "EVENT_GROUPS_MODIFICATION_NOTIFICATIONS", "EVENT_PARTICIPANT_CASE_CONFIRMED_NOTIFICATIONS", "EVENT_PARTICIPANT_RELATED_TO_OTHER_EVENTS_NOTIFICATIONS", "TASK_NOTIFICATIONS", "OTHER_NOTIFICATIONS", "TASK_GENERATION_CASE_SURVEILLANCE", "TASK_GENERATION_CONTACT_TRACING", "TASK_GENERATION_EVENT_SURVEILLANCE", "TASK_GENERATION_GENERAL", "CASE_AND_CONTACT_BULK_ACTIONS" ] + "enum" : [ "AGGREGATE_REPORTING", "CAMPAIGNS", "CASE_SURVEILANCE", "CLINICAL_MANAGEMENT", "CONTACT_TRACING", "EVENT_SURVEILLANCE", "SAMPLES_LAB", "ADDITIONAL_TESTS", "TASK_MANAGEMENT", "WEEKLY_REPORTING", "IMMUNIZATION_MANAGEMENT", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_MANAGEMENT", "TRAVEL_ENTRIES", "DASHBOARD_SURVEILLANCE", "DASHBOARD_CONTACTS", "DASHBOARD_CAMPAIGNS", "DASHBOARD_SAMPLES", "LIMITED_SYNCHRONIZATION", "ENVIRONMENT_MANAGEMENT", "SELF_REPORTING", "SURVEYS", "ASSIGN_TASKS_TO_HIGHER_LEVEL", "CASE_FOLLOWUP", "DOCUMENTS", "DOCUMENTS_MULTI_UPLOAD", "EVENT_GROUPS", "EVENT_HIERARCHIES", "EXTERNAL_MESSAGES", "MANUAL_EXTERNAL_MESSAGES", "NATIONAL_CASE_SHARING", "SURVEILLANCE_REPORTS", "SORMAS_TO_SORMAS_ACCEPT_REJECT", "SORMAS_TO_SORMAS_SHARE_CASES", "SORMAS_TO_SORMAS_SHARE_CONTACTS", "SORMAS_TO_SORMAS_SHARE_EVENTS", "SORMAS_TO_SORMAS_SHARE_EXTERNAL_MESSAGES", "IMMUNIZATION_STATUS_AUTOMATION", "PERSON_DUPLICATE_CUSTOM_SEARCH", "EDIT_INFRASTRUCTURE_DATA", "AUTOMATIC_ARCHIVING", "EDIT_ARCHIVED_ENTITIES", "EXTERNAL_EMAILS", "HIDE_JURISDICTION_FIELDS", "VIEW_TAB_CASES_HOSPITALIZATION", "VIEW_TAB_CASES_SYMPTOMS", "VIEW_TAB_CASES_EPIDEMIOLOGICAL_DATA", "VIEW_TAB_CASES_THERAPY", "VIEW_TAB_CASES_FOLLOW_UP", "VIEW_TAB_CASES_CLINICAL_COURSE", "VIEW_TAB_CONTACTS_EPIDEMIOLOGICAL_DATA", "VIEW_TAB_CONTACTS_FOLLOW_UP_VISITS", "GDPR_CONSENT_POPUP", "INFRASTRUCTURE_TYPE_AREA", "OUTBREAKS", "PERSON_MANAGEMENT", "AUTH_PROVIDER_TO_SORMAS_USER_SYNC", "LINE_LISTING", "EVENT_GROUPS_MODIFICATION_NOTIFICATIONS", "EVENT_PARTICIPANT_CASE_CONFIRMED_NOTIFICATIONS", "EVENT_PARTICIPANT_RELATED_TO_OTHER_EVENTS_NOTIFICATIONS", "TASK_NOTIFICATIONS", "OTHER_NOTIFICATIONS", "TASK_GENERATION_CASE_SURVEILLANCE", "TASK_GENERATION_CONTACT_TRACING", "TASK_GENERATION_EVENT_SURVEILLANCE", "TASK_GENERATION_GENERAL", "CASE_AND_CONTACT_BULK_ACTIONS", "EPIPULSE_EXPORT" ] }, "properties" : { "type" : "object", @@ -18361,7 +19092,7 @@ "properties" : { "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "districtName" : { "type" : "string" @@ -18435,6 +19166,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "complianceWithTreatment" : { + "type" : "string", + "enum" : [ "NO_COMPLIANCE", "TREATMENT_COMPLETED", "TREATMENT_FAILED", "TREATMENT_NOT_COMPLETED", "UNKNOWN", "NOT_APPLICABLE" ] + }, "congenitalSyphilis" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -18455,6 +19190,13 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "exposedToMosquitoBorneViruses" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "exposedToMosquitoBorneVirusesText" : { + "type" : "string" + }, "formerSmoker" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -18479,9 +19221,20 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "immunodeficiencyOtherThanHivText" : { + "type" : "string" + }, "inJurisdiction" : { "type" : "boolean" }, + "malaria" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "malariaInfectedYear" : { + "type" : "integer", + "format" : "int32" + }, "malignancyChemotherapy" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -18495,9 +19248,17 @@ "maxLength" : 4096, "minLength" : 0 }, + "previousTuberculosisTreatment" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "pseudonymized" : { "type" : "boolean" }, + "recurrentBronchiolitis" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "sickleCellDisease" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -18506,11 +19267,19 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "tuberculosisInfectionYear" : { + "type" : "integer", + "format" : "int32" + }, "uuid" : { "type" : "string", "maxLength" : 36, "minLength" : 20, "pattern" : "^[0-9a-zA-Z-]*$" + }, + "vaccinatedAgainstMosquitoBorneViruses" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] } } }, @@ -18533,6 +19302,10 @@ "type" : "string", "format" : "date-time" }, + "currentlyHospitalized" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "description" : { "type" : "string", "maxLength" : 4096, @@ -18542,6 +19315,10 @@ "type" : "string", "format" : "date-time" }, + "durationOfHospitalization" : { + "type" : "integer", + "format" : "int32" + }, "hospitalizationReason" : { "type" : "string", "enum" : [ "REPORTED_DISEASE", "ISOLATION", "OTHER", "UNKNOWN" ] @@ -18550,6 +19327,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "icuLengthOfStay" : { + "type" : "integer", + "format" : "int32" + }, "intensiveCareUnit" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -18579,12 +19360,20 @@ "maxLength" : 1000000, "minLength" : 0 }, + "oxygenPrescribed" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "previousHospitalizations" : { "type" : "array", "items" : { "$ref" : "#/components/schemas/PreviousHospitalizationDto" } }, + "stillHospitalized" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "uuid" : { "type" : "string", "maxLength" : 36, @@ -18617,7 +19406,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -18651,7 +19440,7 @@ }, "meansOfImmunization" : { "type" : "string", - "enum" : [ "VACCINATION", "RECOVERY", "VACCINATION_RECOVERY", "OTHER" ] + "enum" : [ "VACCINATION", "RECOVERY", "VACCINATION_RECOVERY", "MATERNAL_VACCINATION", "MONOCLONAL_ANTIBODY", "OTHER" ] }, "nameAddressPhoneEmailLike" : { "type" : "string" @@ -18709,7 +19498,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string", @@ -18748,13 +19537,17 @@ "inJurisdiction" : { "type" : "boolean" }, + "injectionFacility" : { + "type" : "string", + "enum" : [ "MATERNITY_WARD", "PAEDIATRIC_PRACTICE", "HOSPITAL" ] + }, "lastInfectionDate" : { "type" : "string", "format" : "date-time" }, "meansOfImmunization" : { "type" : "string", - "enum" : [ "VACCINATION", "RECOVERY", "VACCINATION_RECOVERY", "OTHER" ] + "enum" : [ "VACCINATION", "RECOVERY", "VACCINATION_RECOVERY", "MATERNAL_VACCINATION", "MONOCLONAL_ANTIBODY", "OTHER" ] }, "meansOfImmunizationDetails" : { "type" : "string", @@ -18858,7 +19651,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "type" : "string" @@ -18883,7 +19676,7 @@ }, "meansOfImmunization" : { "type" : "string", - "enum" : [ "VACCINATION", "RECOVERY", "VACCINATION_RECOVERY", "OTHER" ] + "enum" : [ "VACCINATION", "RECOVERY", "VACCINATION_RECOVERY", "MATERNAL_VACCINATION", "MONOCLONAL_ANTIBODY", "OTHER" ] }, "otherDeletionReason" : { "type" : "string" @@ -19305,7 +20098,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "healthFacilityLat" : { "type" : "number", @@ -19530,6 +20323,29 @@ } } }, + "NotifierReferenceDto" : { + "type" : "object", + "properties" : { + "caption" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "uuid" : { + "type" : "string", + "pattern" : "^[0-9a-zA-Z-]*$" + }, + "versionDate" : { + "type" : "string", + "format" : "date-time" + } + }, + "required" : [ "uuid" ] + }, "OccupationType" : { "type" : "object", "properties" : { @@ -19576,7 +20392,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "uniqueItems" : true }, @@ -19609,7 +20425,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -20504,6 +21320,11 @@ "PathogenTestDto" : { "type" : "object", "properties" : { + "antibodyTitre" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, "changeDate" : { "type" : "string", "format" : "date-time" @@ -20547,6 +21368,9 @@ "type" : "string", "enum" : [ "GDPR", "DELETION_REQUEST", "CREATED_WITH_NO_LEGAL_REASON", "TRANSFERRED_RESPONSIBILITY", "DUPLICATE_ENTRIES", "OTHER_REASON" ] }, + "drugSusceptibility" : { + "$ref" : "#/components/schemas/DrugSusceptibilityDto" + }, "environmentSample" : { "$ref" : "#/components/schemas/EnvironmentSampleReferenceDto" }, @@ -20563,9 +21387,20 @@ "fourFoldIncreaseAntibodyTiter" : { "type" : "boolean" }, + "genoType" : { + "type" : "string", + "enum" : [ "GENOTYPE_A", "GENOTYPE_B", "GENOTYPE_B2", "GENOTYPE_B3", "GENOTYPE_C1", "GENOTYPE_C2", "GENOTYPE_D1", "GENOTYPE_D10", "GENOTYPE_D11", "GENOTYPE_D2", "GENOTYPE_D3", "GENOTYPE_D4", "GENOTYPE_D5", "GENOTYPE_D6", "GENOTYPE_D7", "GENOTYPE_D8", "GENOTYPE_D9", "GENOTYPE_E", "GENOTYPE_F", "GENOTYPE_G1", "GENOTYPE_G2", "GENOTYPE_G3", "GENOTYPE_H1", "GENOTYPE_H2", "CRYPTOSPORIDIUM_HOMINIS", "CRYPTOSPORIDIUM_PARVUM", "CRYPTOSPORIDIUM_SPECIES", "OTHER", "UNKNOWN" ] + }, + "genoTypeText" : { + "type" : "string" + }, "inJurisdiction" : { "type" : "boolean" }, + "isoniazidResistant" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "lab" : { "$ref" : "#/components/schemas/FacilityReferenceDto" }, @@ -20582,10 +21417,16 @@ "maxLength" : 1000000, "minLength" : 0 }, + "patternProfile" : { + "type" : "string" + }, "pcrTestSpecification" : { "type" : "string", "enum" : [ "VARIANT_SPECIFIC", "N501Y_MUTATION_DETECTION" ] }, + "performedByReferenceLaboratory" : { + "type" : "boolean" + }, "preliminary" : { "type" : "boolean" }, @@ -20634,21 +21475,68 @@ "type" : "string", "format" : "date-time" }, + "resultDetails" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, + "retestRequested" : { + "type" : "boolean" + }, + "rifampicinResistant" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "rsvSubtype" : { + "type" : "string", + "enum" : [ "RSV_A", "RSV_B", "RSV_A_AND_B", "INDETERMINATE" ] + }, "sample" : { "$ref" : "#/components/schemas/SampleReferenceDto" }, + "seroGroupSpecification" : { + "type" : "string", + "enum" : [ "SEROGROUP_A", "SEROGROUP_B", "SEROGROUP_C", "SEROGROUP_W", "SEROGROUP_X", "SEROGROUP_Y", "SEROGROUP_Z", "SEROGROUP_29E", "NOT_GROUPABLE", "NOT_UNDER_SURVEILLANCE", "OTHER", "UNKNOWN" ] + }, + "seroGroupSpecificationText" : { + "type" : "string" + }, + "seroTypingMethod" : { + "type" : "string", + "enum" : [ "MULTIPLEX_PCR", "QUELLUNG_REACTION", "COAGGLUTINATION", "GEL_DIFFUSION", "PNEUMOTEST", "SLIDE_AGGLUTINATION", "OTHER" ] + }, + "seroTypingMethodText" : { + "type" : "string" + }, "serotype" : { + "type" : "string", + "enum" : [ "DENV_1", "DENV_2", "DENV_3", "DENV_4", "OTHER" ] + }, + "serotypeText" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, + "specie" : { + "type" : "string", + "enum" : [ "MYCOBATERIUM_AFRICANUM", "MYCOBATERIUM_BOVIS", "MYCOBATERIUM_TUBERCULOSIS", "OTHER_MTBC_MEMBER", "SPP", "FALCIPARUM", "VIVAX", "MALARIAE", "OVALE", "KNOWLESI", "CYNOMOLGI", "NOT_SPECIFIED", "COINFECTION", "OTHER", "UNKNOWN", "NOT_APPLICABLE" ] + }, + "specieText" : { "type" : "string", "maxLength" : 512, "minLength" : 0 }, + "strainCallStatus" : { + "type" : "string", + "enum" : [ "BEIJING", "NOBEIJING", "POSSBEIJING", "UNKNOWN" ] + }, "testDateTime" : { "type" : "string", "format" : "date-time" }, "testResult" : { "type" : "string", - "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE" ] + "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE", "NOT_APPLICABLE" ] }, "testResultText" : { "type" : "string", @@ -20658,9 +21546,13 @@ "testResultVerified" : { "type" : "boolean" }, + "testScale" : { + "type" : "string", + "enum" : [ "ONE_PLUS", "TWO_PLUS", "THREE_PLUS" ] + }, "testType" : { "type" : "string", - "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "OTHER" ] + "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "ENZYME_LINKED_IMMUNOSORBENT_ASSAY", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "IGRA", "TST", "BEIJINGGENOTYPING", "SPOLIGOTYPING", "MIRU_PATTERN_CODE", "ANTIBIOTIC_SUSCEPTIBILITY", "MULTILOCUS_SEQUENCE_TYPING", "SLIDE_AGGLUTINATION", "WHOLE_GENOME_SEQUENCING", "SEROGROUPING", "GENOTYPING", "NAAT", "THICK_BLOOD_SMEAR", "THIN_BLOOD_SMEAR", "Q_PCR", "LAMP", "OTHER_ANTIGEN_DETECTION_TEST", "OTHER_MOLECULAR_ASSAY", "OTHER_SEROLOGICAL_TEST", "OTHER" ] }, "testTypeText" : { "type" : "string", @@ -20669,7 +21561,7 @@ }, "testedDisease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "testedDiseaseDetails" : { "type" : "string", @@ -20692,6 +21584,34 @@ "maxLength" : 512, "minLength" : 0 }, + "tubeAgTb1" : { + "type" : "number", + "format" : "float" + }, + "tubeAgTb1GT10" : { + "type" : "boolean" + }, + "tubeAgTb2" : { + "type" : "number", + "format" : "float" + }, + "tubeAgTb2GT10" : { + "type" : "boolean" + }, + "tubeMitogene" : { + "type" : "number", + "format" : "float" + }, + "tubeMitogeneGT10" : { + "type" : "boolean" + }, + "tubeNil" : { + "type" : "number", + "format" : "float" + }, + "tubeNilGT10" : { + "type" : "boolean" + }, "typingId" : { "type" : "string", "maxLength" : 1000000, @@ -20707,7 +21627,7 @@ "type" : "boolean" } }, - "required" : [ "lab", "testResult", "testResultVerified", "testType" ] + "required" : [ "lab", "testResult", "testType" ] }, "PeriodDto" : { "type" : "object", @@ -20796,10 +21716,18 @@ "type" : "integer", "format" : "int32" }, + "birthdateFrom" : { + "type" : "string", + "format" : "date-time" + }, "birthdateMM" : { "type" : "integer", "format" : "int32" }, + "birthdateTo" : { + "type" : "string", + "format" : "date-time" + }, "birthdateYYYY" : { "type" : "integer", "format" : "int32" @@ -20810,6 +21738,9 @@ "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" }, + "includePartialMatch" : { + "type" : "boolean" + }, "nameAddressPhoneEmailLike" : { "type" : "string" }, @@ -20878,6 +21809,10 @@ "type" : "integer", "format" : "int32" }, + "birthWeightCategory" : { + "type" : "string", + "enum" : [ "NORMAL", "LOW_BIRTH_WEIGHT" ] + }, "birthdateDD" : { "type" : "integer", "format" : "int32" @@ -20914,7 +21849,7 @@ }, "causeOfDeathDisease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "changeDate" : { "type" : "string", @@ -20956,6 +21891,13 @@ "type" : "string", "readOnly" : true }, + "emancipated" : { + "type" : "boolean" + }, + "entryDate" : { + "type" : "string", + "format" : "date-time" + }, "externalId" : { "type" : "string", "maxLength" : 512, @@ -20980,12 +21922,19 @@ "type" : "integer", "format" : "int32" }, + "gestationalAgeCategory" : { + "type" : "string", + "enum" : [ "AT_TERM", "PREMATURE_BEFORE_32", "PREMATURE_32_TO_38" ] + }, "hasCovidApp" : { "type" : "boolean" }, "inJurisdiction" : { "type" : "boolean" }, + "incapacitated" : { + "type" : "boolean" + }, "internalToken" : { "type" : "string", "maxLength" : 1000000, @@ -20996,6 +21945,10 @@ "maxLength" : 512, "minLength" : 0 }, + "livingStatus" : { + "type" : "string", + "enum" : [ "MIGRANT", "FOREIGNER", "HOMELESS", "REGISTERED_AT_A_RESIDENCE" ] + }, "mothersMaidenName" : { "type" : "string", "maxLength" : 255, @@ -21006,6 +21959,10 @@ "maxLength" : 512, "minLength" : 0 }, + "multipleBirth" : { + "type" : "string", + "enum" : [ "SINGLE", "TWINS", "MULTIPLE" ] + }, "namesOfGuardians" : { "type" : "string", "maxLength" : 512, @@ -21094,6 +22051,13 @@ "maxLength" : 36, "minLength" : 20, "pattern" : "^[0-9a-zA-Z-]*$" + }, + "workPlace" : { + "type" : "string", + "enum" : [ "SCHOOL", "NURSERY", "UNKNOWN", "OTHER" ] + }, + "workPlaceText" : { + "type" : "string" } }, "required" : [ "firstName", "lastName", "sex" ] @@ -21213,6 +22177,9 @@ "type" : "integer", "format" : "int32" }, + "checkOnlyForNationalHealthId" : { + "type" : "boolean" + }, "firstName" : { "type" : "string" }, @@ -21593,7 +22560,7 @@ }, "typeOfDrug" : { "type" : "string", - "enum" : [ "ANTIMICROBIAL", "ANTIVIRAL", "OTHER" ] + "enum" : [ "ANTIMICROBIAL", "ANTIVIRAL", "ANTIBIOTIC", "OTHER" ] }, "uuid" : { "type" : "string", @@ -21665,7 +22632,7 @@ }, "typeOfDrug" : { "type" : "string", - "enum" : [ "ANTIMICROBIAL", "ANTIVIRAL", "OTHER" ] + "enum" : [ "ANTIMICROBIAL", "ANTIVIRAL", "ANTIBIOTIC", "OTHER" ] } } }, @@ -21719,6 +22686,11 @@ "healthFacility" : { "$ref" : "#/components/schemas/FacilityReferenceDto" }, + "healthFacilityDepartment" : { + "type" : "string", + "maxLength" : 512, + "minLength" : 0 + }, "healthFacilityDetails" : { "type" : "string", "maxLength" : 512, @@ -21728,6 +22700,10 @@ "type" : "string", "enum" : [ "REPORTED_DISEASE", "ISOLATION", "OTHER", "UNKNOWN" ] }, + "icuLengthOfStay" : { + "type" : "integer", + "format" : "int32" + }, "inJurisdiction" : { "type" : "boolean" }, @@ -21756,12 +22732,20 @@ "maxLength" : 1000000, "minLength" : 0 }, + "oxygenPrescribed" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "pseudonymized" : { "type" : "boolean" }, "region" : { "$ref" : "#/components/schemas/RegionReferenceDto" }, + "stillHospitalized" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "uuid" : { "type" : "string", "maxLength" : 36, @@ -21843,6 +22827,9 @@ "maxLength" : 255, "minLength" : 0 }, + "nutsCode" : { + "type" : "string" + }, "uuid" : { "type" : "string", "maxLength" : 36, @@ -21884,6 +22871,9 @@ "name" : { "type" : "string" }, + "nutsCode" : { + "type" : "string" + }, "population" : { "type" : "integer", "format" : "int32" @@ -21946,7 +22936,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -21965,7 +22955,7 @@ }, "pathogenTestResult" : { "type" : "string", - "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE" ] + "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE", "NOT_APPLICABLE" ] }, "received" : { "type" : "boolean" @@ -22072,7 +23062,7 @@ }, "pathogenTestResult" : { "type" : "string", - "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE" ] + "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE", "NOT_APPLICABLE" ] }, "pathogenTestingRequested" : { "type" : "boolean" @@ -22135,7 +23125,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "OTHER" ] + "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "ENZYME_LINKED_IMMUNOSORBENT_ASSAY", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "IGRA", "TST", "BEIJINGGENOTYPING", "SPOLIGOTYPING", "MIRU_PATTERN_CODE", "ANTIBIOTIC_SUSCEPTIBILITY", "MULTILOCUS_SEQUENCE_TYPING", "SLIDE_AGGLUTINATION", "WHOLE_GENOME_SEQUENCING", "SEROGROUPING", "GENOTYPING", "NAAT", "THICK_BLOOD_SMEAR", "THIN_BLOOD_SMEAR", "Q_PCR", "LAMP", "OTHER_ANTIGEN_DETECTION_TEST", "OTHER_MOLECULAR_ASSAY", "OTHER_SEROLOGICAL_TEST", "OTHER" ] }, "uniqueItems" : true }, @@ -22145,7 +23135,7 @@ }, "sampleMaterial" : { "type" : "string", - "enum" : [ "BLOOD", "SERA", "STOOL", "NASAL_SWAB", "THROAT_SWAB", "NP_SWAB", "RECTAL_SWAB", "CEREBROSPINAL_FLUID", "CRUST", "TISSUE", "URINE", "CORNEA_PM", "SALIVA", "URINE_PM", "NUCHAL_SKIN_BIOPSY", "SPUTUM", "ENDOTRACHEAL_ASPIRATE", "BRONCHOALVEOLAR_LAVAGE", "BRAIN_TISSUE", "ANTERIOR_NARES_SWAB", "OP_ASPIRATE", "NP_ASPIRATE", "PLEURAL_FLUID", "OTHER" ] + "enum" : [ "BLOOD", "DRY_BLOOD", "SERA", "STOOL", "THROAT_ASPIRATE", "NASAL_SWAB", "THROAT_SWAB", "NP_SWAB", "RECTAL_SWAB", "CEREBROSPINAL_FLUID", "CRUST", "TISSUE", "URINE", "CORNEA_PM", "SALIVA", "URINE_PM", "NUCHAL_SKIN_BIOPSY", "BIOPSY", "SPUTUM", "ENDOTRACHEAL_ASPIRATE", "BRONCHOALVEOLAR_LAVAGE", "BRAIN_TISSUE", "ANTERIOR_NARES_SWAB", "OP_ASPIRATE", "NP_ASPIRATE", "PLEURAL_FLUID", "NASOPHARYNGEAL_LAVAGE", "OROPHARYNGEAL_SWAB", "AMNIOTIC_FLUID", "CLINICAL_SAMPLE", "PERITONEAL_FLUID", "SYNOVIAL_FLUID", "EDTA_WHOLE_BLOOD", "INTESTINAL_FLUID", "DUODENUM_FLUID", "OTHER" ] }, "sampleMaterialText" : { "type" : "string", @@ -22222,7 +23212,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string" @@ -22255,7 +23245,7 @@ }, "pathogenTestResult" : { "type" : "string", - "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE" ] + "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE", "NOT_APPLICABLE" ] }, "pseudonymized" : { "type" : "boolean" @@ -22279,7 +23269,7 @@ }, "sampleMaterial" : { "type" : "string", - "enum" : [ "BLOOD", "SERA", "STOOL", "NASAL_SWAB", "THROAT_SWAB", "NP_SWAB", "RECTAL_SWAB", "CEREBROSPINAL_FLUID", "CRUST", "TISSUE", "URINE", "CORNEA_PM", "SALIVA", "URINE_PM", "NUCHAL_SKIN_BIOPSY", "SPUTUM", "ENDOTRACHEAL_ASPIRATE", "BRONCHOALVEOLAR_LAVAGE", "BRAIN_TISSUE", "ANTERIOR_NARES_SWAB", "OP_ASPIRATE", "NP_ASPIRATE", "PLEURAL_FLUID", "OTHER" ] + "enum" : [ "BLOOD", "DRY_BLOOD", "SERA", "STOOL", "THROAT_ASPIRATE", "NASAL_SWAB", "THROAT_SWAB", "NP_SWAB", "RECTAL_SWAB", "CEREBROSPINAL_FLUID", "CRUST", "TISSUE", "URINE", "CORNEA_PM", "SALIVA", "URINE_PM", "NUCHAL_SKIN_BIOPSY", "BIOPSY", "SPUTUM", "ENDOTRACHEAL_ASPIRATE", "BRONCHOALVEOLAR_LAVAGE", "BRAIN_TISSUE", "ANTERIOR_NARES_SWAB", "OP_ASPIRATE", "NP_ASPIRATE", "PLEURAL_FLUID", "NASOPHARYNGEAL_LAVAGE", "OROPHARYNGEAL_SWAB", "AMNIOTIC_FLUID", "CLINICAL_SAMPLE", "PERITONEAL_FLUID", "SYNOVIAL_FLUID", "EDTA_WHOLE_BLOOD", "INTESTINAL_FLUID", "DUODENUM_FLUID", "OTHER" ] }, "samplePurpose" : { "type" : "string", @@ -22305,7 +23295,7 @@ }, "typeOfLastTest" : { "type" : "string", - "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "OTHER" ] + "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "ENZYME_LINKED_IMMUNOSORBENT_ASSAY", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "IGRA", "TST", "BEIJINGGENOTYPING", "SPOLIGOTYPING", "MIRU_PATTERN_CODE", "ANTIBIOTIC_SUSCEPTIBILITY", "MULTILOCUS_SEQUENCE_TYPING", "SLIDE_AGGLUTINATION", "WHOLE_GENOME_SEQUENCING", "SEROGROUPING", "GENOTYPING", "NAAT", "THICK_BLOOD_SMEAR", "THIN_BLOOD_SMEAR", "Q_PCR", "LAMP", "OTHER_ANTIGEN_DETECTION_TEST", "OTHER_MOLECULAR_ASSAY", "OTHER_SEROLOGICAL_TEST", "OTHER" ] }, "uuid" : { "type" : "string", @@ -22379,7 +23369,7 @@ }, "sampleMaterial" : { "type" : "string", - "enum" : [ "BLOOD", "SERA", "STOOL", "NASAL_SWAB", "THROAT_SWAB", "NP_SWAB", "RECTAL_SWAB", "CEREBROSPINAL_FLUID", "CRUST", "TISSUE", "URINE", "CORNEA_PM", "SALIVA", "URINE_PM", "NUCHAL_SKIN_BIOPSY", "SPUTUM", "ENDOTRACHEAL_ASPIRATE", "BRONCHOALVEOLAR_LAVAGE", "BRAIN_TISSUE", "ANTERIOR_NARES_SWAB", "OP_ASPIRATE", "NP_ASPIRATE", "PLEURAL_FLUID", "OTHER" ] + "enum" : [ "BLOOD", "DRY_BLOOD", "SERA", "STOOL", "THROAT_ASPIRATE", "NASAL_SWAB", "THROAT_SWAB", "NP_SWAB", "RECTAL_SWAB", "CEREBROSPINAL_FLUID", "CRUST", "TISSUE", "URINE", "CORNEA_PM", "SALIVA", "URINE_PM", "NUCHAL_SKIN_BIOPSY", "BIOPSY", "SPUTUM", "ENDOTRACHEAL_ASPIRATE", "BRONCHOALVEOLAR_LAVAGE", "BRAIN_TISSUE", "ANTERIOR_NARES_SWAB", "OP_ASPIRATE", "NP_ASPIRATE", "PLEURAL_FLUID", "NASOPHARYNGEAL_LAVAGE", "OROPHARYNGEAL_SWAB", "AMNIOTIC_FLUID", "CLINICAL_SAMPLE", "PERITONEAL_FLUID", "SYNOVIAL_FLUID", "EDTA_WHOLE_BLOOD", "INTESTINAL_FLUID", "DUODENUM_FLUID", "OTHER" ] }, "sampleMaterialText" : { "type" : "string", @@ -22388,7 +23378,7 @@ }, "sampleOverallTestResult" : { "type" : "string", - "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE" ] + "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE", "NOT_APPLICABLE" ] }, "sampleReceivedDate" : { "type" : "string", @@ -22557,7 +23547,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string", @@ -22657,7 +23647,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string", @@ -22756,7 +23746,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string", @@ -23132,7 +24122,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -23223,7 +24213,7 @@ }, "reportingType" : { "type" : "string", - "enum" : [ "DOCTOR", "LABORATORY", "FORWARDING", "HOSPITAL_OR_STATIONARY_CARE", "COMMUNITY_FACILITY", "COMMUNITY_FACILITY_IFSG_ARTICLE_34", "OWN_DETERMINATION", "NOT_DETERMINABLE", "NOT_RAISED", "OTHER" ] + "enum" : [ "DOCTOR", "LABORATORY", "FORWARDING", "HOSPITAL_OR_STATIONARY_CARE", "COMMUNITY_FACILITY", "COMMUNITY_FACILITY_IFSG_ARTICLE_34", "OWN_DETERMINATION", "NOT_DETERMINABLE", "NOT_RAISED", "PHONE_NOTIFICATION", "OTHER" ] }, "reportingUser" : { "$ref" : "#/components/schemas/UserReferenceDto" @@ -23231,6 +24221,17 @@ "sormasToSormasOriginInfo" : { "$ref" : "#/components/schemas/SormasToSormasOriginInfoDto" }, + "treatmentNotApplicable" : { + "type" : "boolean" + }, + "treatmentStartDate" : { + "type" : "string", + "format" : "date-time" + }, + "treatmentStarted" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "uuid" : { "type" : "string", "maxLength" : 36, @@ -23253,6 +24254,19 @@ }, "required" : [ "uuid" ] }, + "SurveyReferenceDto" : { + "type" : "object", + "properties" : { + "caption" : { + "type" : "string" + }, + "uuid" : { + "type" : "string", + "pattern" : "^[0-9a-zA-Z-]*$" + } + }, + "required" : [ "uuid" ] + }, "SymptomsDto" : { "type" : "object", "properties" : { @@ -23264,6 +24278,18 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "acuteBleeding" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "acuteEncephalitis" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "acuteKidneyFailure" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "acuteRespiratoryDistressSyndrome" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23280,6 +24306,14 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "alteredLevelOfConsciousness" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "anemia" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "anorexiaAppetiteLoss" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23288,10 +24322,22 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "apnoea" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "arthritis" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "ascendingFlaccidParalysis" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "asymptomatic" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "backache" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23312,6 +24358,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "bloating" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "bloodCirculationProblems" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23352,6 +24402,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "cerebralMalaria" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "changeDate" : { "type" : "string", "format" : "date-time" @@ -23368,6 +24422,25 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "clammySkin" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "clinicalManifestation" : { + "type" : "string", + "enum" : [ "ASYMPTOMATIC", "NO_SIGN_OF_SEVERITY", "SIGN_OF_SEVERITY", "UNKNOWN", "OTHER" ] + }, + "clinicalManifestationText" : { + "type" : "string" + }, + "clinicalPresentationStatus" : { + "type" : "string", + "enum" : [ "ASYMPTOMATIC", "COMPATIBLE", "UNKNOWN" ] + }, + "coldSkin" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "coma" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23376,6 +24449,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "confusion" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "congenitalGlaucoma" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23405,6 +24482,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "convulsions" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "cough" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23425,6 +24506,14 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "coughingBouts" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "coughsProvokeVomiting" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "creationDate" : { "type" : "string", "format" : "date-time" @@ -23433,6 +24522,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "dateOfOnsetKnown" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "dehydration" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23445,6 +24538,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "diagnosis" : { + "type" : "string", + "enum" : [ "PULMONARY", "EXTRAPULMONARY" ] + }, "diarrhea" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23453,18 +24550,38 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "difficultyBreathingDuringMeals" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "digestedBloodVomit" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "disseminatedIntraVascularCoagulation" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "dizzinessStandingUp" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "durationOfSymptoms" : { + "type" : "integer", + "format" : "int32" + }, "dysphagia" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "eggyBurps" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "encephalitis" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "erraticBehaviour" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23525,6 +24642,10 @@ "type" : "integer", "format" : "int32" }, + "guillainBarreSyndrome" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "gumsBleeding" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23545,6 +24666,10 @@ "type" : "integer", "format" : "int32" }, + "hemorrhagicRash" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "hemorrhagicSyndrome" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23672,6 +24797,10 @@ "lesionsThorax" : { "type" : "boolean" }, + "lethargy" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "lossOfSmell" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23700,6 +24829,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "majorSite" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "BONES_JOINTS_OTHER_THAN_VERTEBRAE", "CNS_EXCEPT_MENINGES", "DISSEMINATED_FORM", "EXTRAPULMONARY_UNKNOWN_SITE", "EXTRA_THORACIC_LYMPH_NODES", "GENITO_URINARY", "INTRATHORACIC_LYMPH_NODES", "LUNG", "MENINGES", "PERITONEUM_DIGESTIVE_TRACT", "PLEURA", "UROGENITAL_SYSTEM", "VERTEBRAE", "UNKNOWN", "OTHER" ] + }, "malaise" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23708,10 +24841,18 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "meningitis" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "meningoencephalitis" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "metabolicAcidosis" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "microcephaly" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23720,6 +24861,10 @@ "type" : "integer", "format" : "int32" }, + "minorSite" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "BONES_JOINTS_OTHER_THAN_VERTEBRAE", "CNS_EXCEPT_MENINGES", "DISSEMINATED_FORM", "EXTRAPULMONARY_UNKNOWN_SITE", "EXTRA_THORACIC_LYMPH_NODES", "GENITO_URINARY", "INTRATHORACIC_LYMPH_NODES", "LUNG", "MENINGES", "PERITONEUM_DIGESTIVE_TRACT", "PLEURA", "UROGENITAL_SYSTEM", "VERTEBRAE", "UNKNOWN", "OTHER" ] + }, "musclePain" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23732,6 +24877,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "nocturnalCough" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "noseBleeding" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23744,6 +24893,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "offsetDate" : { + "type" : "string", + "format" : "date-time" + }, "onsetDate" : { "type" : "string", "format" : "date-time" @@ -23761,6 +24914,13 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "otherClinicalPresentation" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "otherClinicalPresentationText" : { + "type" : "string" + }, "otherComplications" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23779,6 +24939,19 @@ "maxLength" : 512, "minLength" : 0 }, + "otherMajorSiteDetails" : { + "type" : "string" + }, + "otherMinorSiteDetails" : { + "type" : "string" + }, + "otherNeurolocalSymptom" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "otherNeurolocalSymptomText" : { + "type" : "string" + }, "otherNonHemorrhagicSymptoms" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23792,6 +24965,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "overnightStayRequired" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "oxygenSaturationLower94" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23812,10 +24989,18 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "paradoxicalBreathing" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "paralysis" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "parentTimeOffWork" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "paresis" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23829,6 +25014,10 @@ "maxLength" : 512, "minLength" : 0 }, + "persistentVomiting" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "pharyngealErythema" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23841,10 +25030,18 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "plasmaLeakageSign" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "pneumoniaClinicalOrRadiologic" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "polydipsia" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "pseudonymized" : { "type" : "boolean" }, @@ -23868,18 +25065,34 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "reoccurrence" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "respiratoryDiseaseVentilation" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "respiratoryFatigue" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "respiratoryRate" : { "type" : "integer", "format" : "int32" }, + "restlessness" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "runnyNose" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "scantHemorrhage" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "seizures" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23888,6 +25101,18 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "septicaemia" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "severeAnemia" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "severeOrganImpairment" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "shivering" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23908,6 +25133,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "skinRashOnsetDate" : { + "type" : "string", + "format" : "date-time" + }, "skinUlcers" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23932,6 +25161,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "symptomCurrentStatus" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "symptomatic" : { "type" : "boolean" }, @@ -23940,6 +25173,10 @@ "maxLength" : 512, "minLength" : 0 }, + "syndromicFlu" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "temperature" : { "type" : "number", "format" : "float" @@ -23952,6 +25189,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "timeOffWorkDays" : { + "type" : "number", + "format" : "float" + }, "tremor" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23964,6 +25205,10 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, + "unknownSymptom" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, "uproariousness" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] @@ -23990,9 +25235,21 @@ "type" : "integer", "format" : "int32" }, + "weightLoss" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "weightLossAmount" : { + "type" : "number", + "format" : "float" + }, "wheezing" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] + }, + "whoopSound" : { + "type" : "string", + "enum" : [ "YES", "NO", "UNKNOWN" ] } } }, @@ -24249,7 +25506,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" @@ -24342,10 +25599,50 @@ "TestReportDto" : { "type" : "object", "properties" : { + "amikacinMic" : { + "type" : "number", + "format" : "float" + }, + "amikacinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "bedaquilineMic" : { + "type" : "number", + "format" : "float" + }, + "bedaquilineSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "capreomycinMic" : { + "type" : "number", + "format" : "float" + }, + "capreomycinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "ceftriaxoneMic" : { + "type" : "number", + "format" : "float" + }, + "ceftriaxoneSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, "changeDate" : { "type" : "string", "format" : "date-time" }, + "ciprofloxacinMic" : { + "type" : "number", + "format" : "float" + }, + "ciprofloxacinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, "cqValue" : { "type" : "number", "format" : "float" @@ -24382,6 +25679,30 @@ "type" : "string", "format" : "date-time" }, + "delamanidMic" : { + "type" : "number", + "format" : "float" + }, + "delamanidSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "erythromycinMic" : { + "type" : "number", + "format" : "float" + }, + "erythromycinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "ethambutolMic" : { + "type" : "number", + "format" : "float" + }, + "ethambutolSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, "externalId" : { "type" : "string", "maxLength" : 512, @@ -24392,6 +25713,66 @@ "maxLength" : 512, "minLength" : 0 }, + "gatifloxacinMic" : { + "type" : "number", + "format" : "float" + }, + "gatifloxacinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "genoType" : { + "type" : "string", + "enum" : [ "GENOTYPE_A", "GENOTYPE_B", "GENOTYPE_B2", "GENOTYPE_B3", "GENOTYPE_C1", "GENOTYPE_C2", "GENOTYPE_D1", "GENOTYPE_D10", "GENOTYPE_D11", "GENOTYPE_D2", "GENOTYPE_D3", "GENOTYPE_D4", "GENOTYPE_D5", "GENOTYPE_D6", "GENOTYPE_D7", "GENOTYPE_D8", "GENOTYPE_D9", "GENOTYPE_E", "GENOTYPE_F", "GENOTYPE_G1", "GENOTYPE_G2", "GENOTYPE_G3", "GENOTYPE_H1", "GENOTYPE_H2", "CRYPTOSPORIDIUM_HOMINIS", "CRYPTOSPORIDIUM_PARVUM", "CRYPTOSPORIDIUM_SPECIES", "OTHER", "UNKNOWN" ] + }, + "isoniazidMic" : { + "type" : "number", + "format" : "float" + }, + "isoniazidSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "kanamycinMic" : { + "type" : "number", + "format" : "float" + }, + "kanamycinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "levofloxacinMic" : { + "type" : "number", + "format" : "float" + }, + "levofloxacinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "moxifloxacinMic" : { + "type" : "number", + "format" : "float" + }, + "moxifloxacinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "ofloxacinMic" : { + "type" : "number", + "format" : "float" + }, + "ofloxacinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "penicillinMic" : { + "type" : "number", + "format" : "float" + }, + "penicillinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, "preliminary" : { "type" : "boolean" }, @@ -24433,9 +25814,54 @@ "maxLength" : 1000000, "minLength" : 0 }, + "rifampicinMic" : { + "type" : "number", + "format" : "float" + }, + "rifampicinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, + "rsvSubtype" : { + "type" : "string", + "enum" : [ "RSV_A", "RSV_B", "RSV_A_AND_B", "INDETERMINATE" ] + }, "sampleReport" : { "$ref" : "#/components/schemas/SampleReportReferenceDto" }, + "seroGroupSpecification" : { + "type" : "string", + "enum" : [ "SEROGROUP_A", "SEROGROUP_B", "SEROGROUP_C", "SEROGROUP_W", "SEROGROUP_X", "SEROGROUP_Y", "SEROGROUP_Z", "SEROGROUP_29E", "NOT_GROUPABLE", "NOT_UNDER_SURVEILLANCE", "OTHER", "UNKNOWN" ] + }, + "seroGroupSpecificationText" : { + "type" : "string" + }, + "seroTypingMethod" : { + "type" : "string", + "enum" : [ "MULTIPLEX_PCR", "QUELLUNG_REACTION", "COAGGLUTINATION", "GEL_DIFFUSION", "PNEUMOTEST", "SLIDE_AGGLUTINATION", "OTHER" ] + }, + "seroTypingMethodText" : { + "type" : "string" + }, + "serotype" : { + "type" : "string" + }, + "specie" : { + "type" : "string", + "enum" : [ "MYCOBATERIUM_AFRICANUM", "MYCOBATERIUM_BOVIS", "MYCOBATERIUM_TUBERCULOSIS", "OTHER_MTBC_MEMBER", "SPP", "FALCIPARUM", "VIVAX", "MALARIAE", "OVALE", "KNOWLESI", "CYNOMOLGI", "NOT_SPECIFIED", "COINFECTION", "OTHER", "UNKNOWN", "NOT_APPLICABLE" ] + }, + "strainCallStatus" : { + "type" : "string", + "enum" : [ "BEIJING", "NOBEIJING", "POSSBEIJING", "UNKNOWN" ] + }, + "streptomycinMic" : { + "type" : "number", + "format" : "float" + }, + "streptomycinSusceptibility" : { + "type" : "string", + "enum" : [ "NOT_APPLICABLE", "RESISTANT", "SUSCEPTIBLE", "INTERMEDIATE", "UNKNOWN" ] + }, "testDateTime" : { "type" : "string", "format" : "date-time" @@ -24467,7 +25893,7 @@ }, "testResult" : { "type" : "string", - "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE" ] + "enum" : [ "INDETERMINATE", "PENDING", "NEGATIVE", "POSITIVE", "NOT_DONE", "NOT_APPLICABLE" ] }, "testResultText" : { "type" : "string", @@ -24479,7 +25905,12 @@ }, "testType" : { "type" : "string", - "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "OTHER" ] + "enum" : [ "ANTIBODY_DETECTION", "ANTIGEN_DETECTION", "RAPID_ANTIGEN_DETECTION", "RAPID_TEST", "CULTURE", "HISTOPATHOLOGY", "ISOLATION", "IGM_SERUM_ANTIBODY", "IGG_SERUM_ANTIBODY", "IGA_SERUM_ANTIBODY", "INCUBATION_TIME", "INDIRECT_FLUORESCENT_ANTIBODY", "DIRECT_FLUORESCENT_ANTIBODY", "MICROSCOPY", "NEUTRALIZING_ANTIBODIES", "ENZYME_LINKED_IMMUNOSORBENT_ASSAY", "PCR_RT_PCR", "GRAM_STAIN", "LATEX_AGGLUTINATION", "CQ_VALUE_DETECTION", "SEQUENCING", "DNA_MICROARRAY", "TMA", "IGRA", "TST", "BEIJINGGENOTYPING", "SPOLIGOTYPING", "MIRU_PATTERN_CODE", "ANTIBIOTIC_SUSCEPTIBILITY", "MULTILOCUS_SEQUENCE_TYPING", "SLIDE_AGGLUTINATION", "WHOLE_GENOME_SEQUENCING", "SEROGROUPING", "GENOTYPING", "NAAT", "THICK_BLOOD_SMEAR", "THIN_BLOOD_SMEAR", "Q_PCR", "LAMP", "OTHER_ANTIGEN_DETECTION_TEST", "OTHER_MOLECULAR_ASSAY", "OTHER_SEROLOGICAL_TEST", "OTHER" ] + }, + "testTypeDetails" : { + "type" : "string", + "maxLength" : 1000000, + "minLength" : 0 }, "testedDiseaseVariant" : { "type" : "string", @@ -24491,6 +25922,34 @@ "maxLength" : 255, "minLength" : 0 }, + "tubeAgTb1" : { + "type" : "number", + "format" : "float" + }, + "tubeAgTb1GT10" : { + "type" : "boolean" + }, + "tubeAgTb2" : { + "type" : "number", + "format" : "float" + }, + "tubeAgTb2GT10" : { + "type" : "boolean" + }, + "tubeMitogene" : { + "type" : "number", + "format" : "float" + }, + "tubeMitogeneGT10" : { + "type" : "boolean" + }, + "tubeNil" : { + "type" : "number", + "format" : "float" + }, + "tubeNilGT10" : { + "type" : "boolean" + }, "typingId" : { "type" : "string", "maxLength" : 1000000, @@ -24508,6 +25967,9 @@ "TherapyDto" : { "type" : "object", "properties" : { + "beijingLineage" : { + "type" : "boolean" + }, "changeDate" : { "type" : "string", "format" : "date-time" @@ -24516,6 +25978,12 @@ "type" : "string", "format" : "date-time" }, + "directlyObservedTreatment" : { + "type" : "boolean" + }, + "mdrXdrTuberculosis" : { + "type" : "boolean" + }, "uuid" : { "type" : "string", "maxLength" : 36, @@ -24627,7 +26095,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "diseaseDetails" : { "type" : "string", @@ -24927,7 +26395,7 @@ }, "typeOfDrug" : { "type" : "string", - "enum" : [ "ANTIMICROBIAL", "ANTIVIRAL", "OTHER" ] + "enum" : [ "ANTIMICROBIAL", "ANTIVIRAL", "ANTIBIOTIC", "OTHER" ] }, "uuid" : { "type" : "string", @@ -24993,7 +26461,7 @@ }, "typeOfDrug" : { "type" : "string", - "enum" : [ "ANTIMICROBIAL", "ANTIVIRAL", "OTHER" ] + "enum" : [ "ANTIMICROBIAL", "ANTIVIRAL", "ANTIBIOTIC", "OTHER" ] } } }, @@ -25046,6 +26514,9 @@ "district" : { "$ref" : "#/components/schemas/DistrictReferenceDto" }, + "externalId" : { + "type" : "string" + }, "firstName" : { "type" : "string", "maxLength" : 512, @@ -25066,7 +26537,7 @@ }, "language" : { "type" : "string", - "enum" : [ "EN", "EN_NG", "EN_GH", "EN_AF", "FR", "FR_CH", "FR_TN", "DE", "DE_CH", "ES_BO", "ES_EC", "ES_CU", "IT", "IT_CH", "FI", "PS", "FA", "CZ", "UR_PK" ] + "enum" : [ "EN", "EN_NG", "EN_GM", "EN_LR", "EN_GH", "EN_KE", "EN_AF", "FR", "FR_CH", "FR_TN", "DE", "DE_CH", "PT_CV", "ES_BO", "ES_EC", "ES_CU", "IT", "IT_CH", "FI", "PS", "FA", "CZ", "UR_PK" ] }, "lastName" : { "type" : "string", @@ -25077,7 +26548,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "uniqueItems" : true }, @@ -25226,7 +26697,7 @@ "type" : "array", "items" : { "type" : "string", - "enum" : [ "CASE_VIEW", "CASE_CREATE", "CASE_EDIT", "CASE_DELETE", "CASE_ARCHIVE", "CASE_VIEW_ARCHIVED", "CASE_IMPORT", "CASE_EXPORT", "CASE_INVESTIGATE", "CASE_CLASSIFY", "CASE_CHANGE_DISEASE", "CASE_CHANGE_EPID_NUMBER", "CASE_TRANSFER", "CASE_REFER_FROM_POE", "CASE_MERGE", "CASE_SHARE", "CASE_RESPONSIBLE", "GRANT_SPECIAL_CASE_ACCESS", "IMMUNIZATION_VIEW", "IMMUNIZATION_CREATE", "IMMUNIZATION_EDIT", "IMMUNIZATION_DELETE", "IMMUNIZATION_ARCHIVE", "IMMUNIZATION_VIEW_ARCHIVED", "PERSON_VIEW", "PERSON_EDIT", "PERSON_DELETE", "PERSON_EXPORT", "PERSON_CONTACT_DETAILS_DELETE", "PERSON_MERGE", "SAMPLE_VIEW", "SAMPLE_CREATE", "SAMPLE_EDIT", "SAMPLE_DELETE", "SAMPLE_EXPORT", "SAMPLE_TRANSFER", "SAMPLE_EDIT_NOT_OWNED", "PATHOGEN_TEST_CREATE", "PATHOGEN_TEST_EDIT", "PATHOGEN_TEST_DELETE", "ADDITIONAL_TEST_VIEW", "ADDITIONAL_TEST_CREATE", "ADDITIONAL_TEST_EDIT", "ADDITIONAL_TEST_DELETE", "CONTACT_VIEW", "CONTACT_CREATE", "CONTACT_EDIT", "CONTACT_DELETE", "CONTACT_ARCHIVE", "CONTACT_VIEW_ARCHIVED", "CONTACT_IMPORT", "CONTACT_EXPORT", "CONTACT_CONVERT", "CONTACT_REASSIGN_CASE", "CONTACT_MERGE", "CONTACT_RESPONSIBLE", "VISIT_CREATE", "VISIT_EDIT", "VISIT_DELETE", "VISIT_EXPORT", "TASK_VIEW", "TASK_CREATE", "TASK_EDIT", "TASK_DELETE", "TASK_EXPORT", "TASK_ASSIGN", "TASK_ARCHIVE", "TASK_VIEW_ARCHIVED", "ACTION_CREATE", "ACTION_DELETE", "ACTION_EDIT", "EVENT_VIEW", "EVENT_CREATE", "EVENT_EDIT", "EVENT_DELETE", "EVENT_ARCHIVE", "EVENT_VIEW_ARCHIVED", "EVENT_IMPORT", "EVENT_EXPORT", "EVENT_RESPONSIBLE", "EVENTPARTICIPANT_VIEW", "EVENTPARTICIPANT_CREATE", "EVENTPARTICIPANT_EDIT", "EVENTPARTICIPANT_DELETE", "EVENTPARTICIPANT_ARCHIVE", "EVENTPARTICIPANT_VIEW_ARCHIVED", "EVENTPARTICIPANT_IMPORT", "EVENTGROUP_CREATE", "EVENTGROUP_EDIT", "EVENTGROUP_ARCHIVE", "EVENTGROUP_VIEW_ARCHIVED", "EVENTGROUP_DELETE", "EVENTGROUP_LINK", "USER_VIEW", "USER_CREATE", "USER_EDIT", "USER_ROLE_VIEW", "USER_ROLE_EDIT", "USER_ROLE_DELETE", "STATISTICS_ACCESS", "STATISTICS_EXPORT", "INFRASTRUCTURE_VIEW", "INFRASTRUCTURE_CREATE", "INFRASTRUCTURE_EDIT", "INFRASTRUCTURE_IMPORT", "INFRASTRUCTURE_ARCHIVE", "INFRASTRUCTURE_VIEW_ARCHIVED", "INFRASTRUCTURE_EXPORT", "POPULATION_MANAGE", "DASHBOARD_SURVEILLANCE_VIEW", "DASHBOARD_CONTACT_VIEW", "DASHBOARD_CONTACT_VIEW_TRANSMISSION_CHAINS", "DASHBOARD_CAMPAIGNS_VIEW", "DASHBOARD_SAMPLES_VIEW", "CASE_CLINICIAN_VIEW", "THERAPY_VIEW", "PRESCRIPTION_CREATE", "PRESCRIPTION_EDIT", "PRESCRIPTION_DELETE", "TREATMENT_CREATE", "TREATMENT_EDIT", "TREATMENT_DELETE", "CLINICAL_COURSE_VIEW", "CLINICAL_COURSE_EDIT", "CLINICAL_VISIT_CREATE", "CLINICAL_VISIT_EDIT", "CLINICAL_VISIT_DELETE", "PORT_HEALTH_INFO_VIEW", "PORT_HEALTH_INFO_EDIT", "WEEKLYREPORT_VIEW", "WEEKLYREPORT_CREATE", "AGGREGATE_REPORT_VIEW", "AGGREGATE_REPORT_EDIT", "AGGREGATE_REPORT_EXPORT", "SEE_PERSONAL_DATA_IN_JURISDICTION", "SEE_PERSONAL_DATA_OUTSIDE_JURISDICTION", "SEE_SENSITIVE_DATA_IN_JURISDICTION", "SEE_SENSITIVE_DATA_OUTSIDE_JURISDICTION", "CAMPAIGN_VIEW", "CAMPAIGN_EDIT", "CAMPAIGN_ARCHIVE", "CAMPAIGN_VIEW_ARCHIVED", "CAMPAIGN_DELETE", "CAMPAIGN_FORM_DATA_VIEW", "CAMPAIGN_FORM_DATA_EDIT", "CAMPAIGN_FORM_DATA_ARCHIVE", "CAMPAIGN_FORM_DATA_DELETE", "CAMPAIGN_FORM_DATA_VIEW_ARCHIVED", "CAMPAIGN_FORM_DATA_EXPORT", "TRAVEL_ENTRY_MANAGEMENT_ACCESS", "TRAVEL_ENTRY_VIEW", "TRAVEL_ENTRY_CREATE", "TRAVEL_ENTRY_EDIT", "TRAVEL_ENTRY_ARCHIVE", "TRAVEL_ENTRY_VIEW_ARCHIVED", "TRAVEL_ENTRY_DELETE", "ENVIRONMENT_VIEW", "ENVIRONMENT_CREATE", "ENVIRONMENT_EDIT", "ENVIRONMENT_ARCHIVE", "ENVIRONMENT_VIEW_ARCHIVED", "ENVIRONMENT_DELETE", "ENVIRONMENT_IMPORT", "ENVIRONMENT_EXPORT", "ENVIRONMENT_SAMPLE_VIEW", "ENVIRONMENT_SAMPLE_CREATE", "ENVIRONMENT_SAMPLE_EDIT", "ENVIRONMENT_SAMPLE_EDIT_DISPATCH", "ENVIRONMENT_SAMPLE_EDIT_RECEIVAL", "ENVIRONMENT_SAMPLE_DELETE", "ENVIRONMENT_SAMPLE_IMPORT", "ENVIRONMENT_SAMPLE_EXPORT", "ENVIRONMENT_PATHOGEN_TEST_CREATE", "ENVIRONMENT_PATHOGEN_TEST_EDIT", "ENVIRONMENT_PATHOGEN_TEST_DELETE", "SELF_REPORT_VIEW", "SELF_REPORT_CREATE", "SELF_REPORT_EDIT", "SELF_REPORT_DELETE", "SELF_REPORT_ARCHIVE", "SELF_REPORT_PROCESS", "SELF_REPORT_IMPORT", "SELF_REPORT_EXPORT", "DOCUMENT_VIEW", "DOCUMENT_UPLOAD", "DOCUMENT_DELETE", "PERFORM_BULK_OPERATIONS", "PERFORM_BULK_OPERATIONS_PSEUDONYM", "QUARANTINE_ORDER_CREATE", "SORMAS_REST", "SORMAS_UI", "DATABASE_EXPORT_ACCESS", "EXPORT_DATA_PROTECTION_DATA", "BAG_EXPORT", "SEND_MANUAL_EXTERNAL_MESSAGES", "MANAGE_EXTERNAL_SYMPTOM_JOURNAL", "EXTERNAL_VISITS", "SORMAS_TO_SORMAS_CLIENT", "SORMAS_TO_SORMAS_SHARE", "SORMAS_TO_SORMAS_PROCESS", "EXTERNAL_SURVEILLANCE_SHARE", "EXTERNAL_SURVEILLANCE_DELETE", "EXTERNAL_MESSAGE_VIEW", "EXTERNAL_MESSAGE_PROCESS", "EXTERNAL_MESSAGE_PUSH", "EXTERNAL_MESSAGE_DELETE", "OUTBREAK_VIEW", "OUTBREAK_EDIT", "MANAGE_PUBLIC_EXPORT_CONFIGURATION", "DOCUMENT_TEMPLATE_MANAGEMENT", "LINE_LISTING_CONFIGURE", "DEV_MODE", "EMAIL_TEMPLATE_MANAGEMENT", "EXTERNAL_EMAIL_SEND", "EXTERNAL_EMAIL_ATTACH_DOCUMENTS", "CUSTOMIZABLE_ENUM_MANAGEMENT" ] + "enum" : [ "CASE_VIEW", "CASE_CREATE", "CASE_EDIT", "CASE_DELETE", "CASE_ARCHIVE", "CASE_VIEW_ARCHIVED", "CASE_IMPORT", "CASE_EXPORT", "CASE_INVESTIGATE", "CASE_CLASSIFY", "CASE_CHANGE_DISEASE", "CASE_CHANGE_EPID_NUMBER", "CASE_TRANSFER", "CASE_REFER_FROM_POE", "CASE_MERGE", "CASE_SHARE", "CASE_RESPONSIBLE", "GRANT_SPECIAL_CASE_ACCESS", "IMMUNIZATION_VIEW", "IMMUNIZATION_CREATE", "IMMUNIZATION_EDIT", "IMMUNIZATION_DELETE", "IMMUNIZATION_ARCHIVE", "IMMUNIZATION_VIEW_ARCHIVED", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_CREATE", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EDIT", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE", "ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT", "PERSON_VIEW", "PERSON_EDIT", "PERSON_DELETE", "PERSON_EXPORT", "PERSON_CONTACT_DETAILS_DELETE", "PERSON_MERGE", "SAMPLE_VIEW", "SAMPLE_CREATE", "SAMPLE_EDIT", "SAMPLE_DELETE", "SAMPLE_EXPORT", "SAMPLE_TRANSFER", "SAMPLE_EDIT_NOT_OWNED", "PATHOGEN_TEST_CREATE", "PATHOGEN_TEST_EDIT", "PATHOGEN_TEST_DELETE", "ADDITIONAL_TEST_VIEW", "ADDITIONAL_TEST_CREATE", "ADDITIONAL_TEST_EDIT", "ADDITIONAL_TEST_DELETE", "CONTACT_VIEW", "CONTACT_CREATE", "CONTACT_EDIT", "CONTACT_DELETE", "CONTACT_ARCHIVE", "CONTACT_VIEW_ARCHIVED", "CONTACT_IMPORT", "CONTACT_EXPORT", "CONTACT_CONVERT", "CONTACT_REASSIGN_CASE", "CONTACT_MERGE", "CONTACT_RESPONSIBLE", "VISIT_CREATE", "VISIT_EDIT", "VISIT_DELETE", "VISIT_EXPORT", "TASK_VIEW", "TASK_CREATE", "TASK_EDIT", "TASK_DELETE", "TASK_EXPORT", "TASK_ASSIGN", "TASK_ARCHIVE", "TASK_VIEW_ARCHIVED", "ACTION_CREATE", "ACTION_DELETE", "ACTION_EDIT", "EVENT_VIEW", "EVENT_CREATE", "EVENT_EDIT", "EVENT_DELETE", "EVENT_ARCHIVE", "EVENT_VIEW_ARCHIVED", "EVENT_IMPORT", "EVENT_EXPORT", "EVENT_RESPONSIBLE", "EVENTPARTICIPANT_VIEW", "EVENTPARTICIPANT_CREATE", "EVENTPARTICIPANT_EDIT", "EVENTPARTICIPANT_DELETE", "EVENTPARTICIPANT_ARCHIVE", "EVENTPARTICIPANT_VIEW_ARCHIVED", "EVENTPARTICIPANT_IMPORT", "EVENTGROUP_CREATE", "EVENTGROUP_EDIT", "EVENTGROUP_ARCHIVE", "EVENTGROUP_VIEW_ARCHIVED", "EVENTGROUP_DELETE", "EVENTGROUP_LINK", "USER_VIEW", "USER_CREATE", "USER_EDIT", "USER_ROLE_VIEW", "USER_ROLE_EDIT", "USER_ROLE_DELETE", "STATISTICS_ACCESS", "STATISTICS_EXPORT", "INFRASTRUCTURE_VIEW", "INFRASTRUCTURE_CREATE", "INFRASTRUCTURE_EDIT", "INFRASTRUCTURE_IMPORT", "INFRASTRUCTURE_ARCHIVE", "INFRASTRUCTURE_VIEW_ARCHIVED", "INFRASTRUCTURE_EXPORT", "POPULATION_MANAGE", "DASHBOARD_SURVEILLANCE_VIEW", "DASHBOARD_CONTACT_VIEW", "DASHBOARD_CONTACT_VIEW_TRANSMISSION_CHAINS", "DASHBOARD_CAMPAIGNS_VIEW", "DASHBOARD_SAMPLES_VIEW", "DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW", "CASE_CLINICIAN_VIEW", "THERAPY_VIEW", "PRESCRIPTION_CREATE", "PRESCRIPTION_EDIT", "PRESCRIPTION_DELETE", "TREATMENT_CREATE", "TREATMENT_EDIT", "TREATMENT_DELETE", "CLINICAL_COURSE_VIEW", "CLINICAL_COURSE_EDIT", "CLINICAL_VISIT_CREATE", "CLINICAL_VISIT_EDIT", "CLINICAL_VISIT_DELETE", "PORT_HEALTH_INFO_VIEW", "PORT_HEALTH_INFO_EDIT", "WEEKLYREPORT_VIEW", "WEEKLYREPORT_CREATE", "AGGREGATE_REPORT_VIEW", "AGGREGATE_REPORT_EDIT", "AGGREGATE_REPORT_EXPORT", "SEE_PERSONAL_DATA_IN_JURISDICTION", "SEE_PERSONAL_DATA_OUTSIDE_JURISDICTION", "SEE_SENSITIVE_DATA_IN_JURISDICTION", "SEE_SENSITIVE_DATA_OUTSIDE_JURISDICTION", "CAMPAIGN_VIEW", "CAMPAIGN_EDIT", "CAMPAIGN_ARCHIVE", "CAMPAIGN_VIEW_ARCHIVED", "CAMPAIGN_DELETE", "CAMPAIGN_FORM_DATA_VIEW", "CAMPAIGN_FORM_DATA_EDIT", "CAMPAIGN_FORM_DATA_ARCHIVE", "CAMPAIGN_FORM_DATA_DELETE", "CAMPAIGN_FORM_DATA_VIEW_ARCHIVED", "CAMPAIGN_FORM_DATA_EXPORT", "TRAVEL_ENTRY_MANAGEMENT_ACCESS", "TRAVEL_ENTRY_VIEW", "TRAVEL_ENTRY_CREATE", "TRAVEL_ENTRY_EDIT", "TRAVEL_ENTRY_ARCHIVE", "TRAVEL_ENTRY_VIEW_ARCHIVED", "TRAVEL_ENTRY_DELETE", "ENVIRONMENT_VIEW", "ENVIRONMENT_CREATE", "ENVIRONMENT_EDIT", "ENVIRONMENT_ARCHIVE", "ENVIRONMENT_VIEW_ARCHIVED", "ENVIRONMENT_DELETE", "ENVIRONMENT_IMPORT", "ENVIRONMENT_EXPORT", "ENVIRONMENT_LINK", "ENVIRONMENT_SAMPLE_VIEW", "ENVIRONMENT_SAMPLE_CREATE", "ENVIRONMENT_SAMPLE_EDIT", "ENVIRONMENT_SAMPLE_EDIT_DISPATCH", "ENVIRONMENT_SAMPLE_EDIT_RECEIVAL", "ENVIRONMENT_SAMPLE_DELETE", "ENVIRONMENT_SAMPLE_IMPORT", "ENVIRONMENT_SAMPLE_EXPORT", "ENVIRONMENT_PATHOGEN_TEST_CREATE", "ENVIRONMENT_PATHOGEN_TEST_EDIT", "ENVIRONMENT_PATHOGEN_TEST_DELETE", "SELF_REPORT_VIEW", "SELF_REPORT_CREATE", "SELF_REPORT_EDIT", "SELF_REPORT_DELETE", "SELF_REPORT_ARCHIVE", "SELF_REPORT_PROCESS", "SELF_REPORT_IMPORT", "SELF_REPORT_EXPORT", "DOCUMENT_VIEW", "DOCUMENT_UPLOAD", "DOCUMENT_DELETE", "PERFORM_BULK_OPERATIONS", "PERFORM_BULK_OPERATIONS_PSEUDONYM", "QUARANTINE_ORDER_CREATE", "SORMAS_REST", "SORMAS_UI", "DATABASE_EXPORT_ACCESS", "EXPORT_DATA_PROTECTION_DATA", "BAG_EXPORT", "SEND_MANUAL_EXTERNAL_MESSAGES", "MANAGE_EXTERNAL_SYMPTOM_JOURNAL", "EXTERNAL_VISITS", "SORMAS_TO_SORMAS_CLIENT", "SORMAS_TO_SORMAS_SHARE", "SORMAS_TO_SORMAS_PROCESS", "EXTERNAL_SURVEILLANCE_SHARE", "EXTERNAL_SURVEILLANCE_DELETE", "EXTERNAL_MESSAGE_PUSH", "EXTERNAL_MESSAGE_ACCESS", "EXTERNAL_MESSAGE_LABORATORY_VIEW", "EXTERNAL_MESSAGE_DOCTOR_DECLARATION_VIEW", "EXTERNAL_MESSAGE_LABORATORY_PROCESS", "EXTERNAL_MESSAGE_DOCTOR_DECLARATION_PROCESS", "EXTERNAL_MESSAGE_LABORATORY_DELETE", "EXTERNAL_MESSAGE_DOCTOR_DECLARATION_DELETE", "SURVEY_VIEW", "SURVEY_CREATE", "SURVEY_EDIT", "SURVEY_DELETE", "SURVEY_TOKEN_VIEW", "SURVEY_TOKEN_CREATE", "SURVEY_TOKEN_EDIT", "SURVEY_TOKEN_DELETE", "SURVEY_TOKEN_IMPORT", "OUTBREAK_VIEW", "OUTBREAK_EDIT", "MANAGE_PUBLIC_EXPORT_CONFIGURATION", "DOCUMENT_TEMPLATE_MANAGEMENT", "LINE_LISTING_CONFIGURE", "DEV_MODE", "EMAIL_TEMPLATE_MANAGEMENT", "EXTERNAL_EMAIL_SEND", "EXTERNAL_EMAIL_ATTACH_DOCUMENTS", "CUSTOMIZABLE_ENUM_MANAGEMENT", "SYSTEM_CONFIGURATION", "DISEASE_MANAGEMENT", "EPIPULSE_EXPORT_VIEW", "EPIPULSE_EXPORT_CREATE", "EPIPULSE_EXPORT_DOWNLOAD", "EPIPULSE_EXPORT_DELETE" ] }, "uniqueItems" : true }, @@ -25335,11 +26806,11 @@ }, "vaccineManufacturer" : { "type" : "string", - "enum" : [ "BIONTECH_PFIZER", "MODERNA", "ASTRA_ZENECA", "JOHNSON_JOHNSON", "NOVAVAX", "SANOFI_GSK", "VALNEVA", "UNKNOWN", "OTHER" ] + "enum" : [ "BIONTECH_PFIZER", "PFIZER", "BAVARIAN_NORDIC", "MODERNA", "ASTRA_ZENECA", "JOHNSON_JOHNSON", "KM_BIOLOGICS", "NOVAVAX", "SANOFI_GSK", "SANOFI_PASTEUR_BIOLOGICS", "VALNEVA", "MERCK", "SANOFI_PASTEUR", "TAKEDA", "GSK", "OXFORD", "UNKNOWN", "OTHER" ] }, "vaccineName" : { "type" : "string", - "enum" : [ "COMIRNATY", "MRNA_BIVALENT_BA_1_BIONTECH_PFIZER", "MRNA_BIVALENT_BA_4_5_BIONTECH_PFIZER", "MRNA_1273", "MRNA_BIVALENT_BA_1_MODERNA", "MRNA_BIVALENT_BA_4_5_MODERNA", "VALNEVA", "NVX_COV_2373", "NUVAXOVID", "OXFORD_ASTRA_ZENECA", "AD26_COV2_S", "SANOFI_GSK", "UNKNOWN", "OTHER" ] + "enum" : [ "COMIRNATY", "MRNA_BIVALENT_BA_1_BIONTECH_PFIZER", "MRNA_BIVALENT_BA_4_5_BIONTECH_PFIZER", "MRNA_1273", "MRNA_BIVALENT_BA_1_MODERNA", "MRNA_BIVALENT_BA_4_5_MODERNA", "VALNEVA", "NVX_COV_2373", "NUVAXOVID", "OXFORD_ASTRA_ZENECA", "AD26_COV2_S", "SANOFI_GSK", "MENABCWY", "ACAM2000", "LC_16", "PREVENAR_13_PFIZER", "VAXNEUVANCE_MERCK", "PREVNAR_20_PFIZER", "PNEUMOVAX_23_MERCK", "MVA_BN", "DENGVAXIA", "QDENGA", "RTS_S_AS01", "R21_MATRIX_M", "UNKNOWN", "OTHER" ] }, "vaccineType" : { "type" : "string", @@ -25381,7 +26852,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "inJurisdiction" : { "type" : "boolean" @@ -25446,7 +26917,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "id" : { "type" : "integer", @@ -25577,7 +27048,7 @@ }, "disease" : { "type" : "string", - "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "OTHER", "UNDEFINED" ] + "enum" : [ "AFP", "CHOLERA", "CONGENITAL_RUBELLA", "CSM", "DENGUE", "EVD", "GUINEA_WORM", "LASSA", "MEASLES", "MONKEYPOX", "NEW_INFLUENZA", "PLAGUE", "POLIO", "UNSPECIFIED_VHF", "WEST_NILE_FEVER", "YELLOW_FEVER", "RABIES", "ANTHRAX", "CORONAVIRUS", "PNEUMONIA", "MALARIA", "TYPHOID_FEVER", "ACUTE_VIRAL_HEPATITIS", "NON_NEONATAL_TETANUS", "HIV", "SCHISTOSOMIASIS", "SOIL_TRANSMITTED_HELMINTHS", "TRYPANOSOMIASIS", "DIARRHEA_DEHYDRATION", "DIARRHEA_BLOOD", "SNAKE_BITE", "RUBELLA", "TUBERCULOSIS", "LATENT_TUBERCULOSIS", "LEPROSY", "LYMPHATIC_FILARIASIS", "BURULI_ULCER", "PERTUSSIS", "NEONATAL_TETANUS", "ONCHOCERCIASIS", "DIPHTERIA", "TRACHOMA", "YAWS_ENDEMIC_SYPHILIS", "MATERNAL_DEATHS", "PERINATAL_DEATHS", "INFLUENZA", "INFLUENZA_A", "INFLUENZA_B", "H_METAPNEUMOVIRUS", "RESPIRATORY_SYNCYTIAL_VIRUS", "PARAINFLUENZA_1_4", "ADENOVIRUS", "RHINOVIRUS", "ENTEROVIRUS", "M_PNEUMONIAE", "C_PNEUMONIAE", "ARI", "CHIKUNGUNYA", "POST_IMMUNIZATION_ADVERSE_EVENTS_MILD", "POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE", "FHA", "INVASIVE_PNEUMOCOCCAL_INFECTION", "INVASIVE_MENINGOCOCCAL_INFECTION", "GIARDIASIS", "CRYPTOSPORIDIOSIS", "OTHER", "UNDEFINED" ] }, "numberOfCases" : { "type" : "integer", diff --git a/sormas-rest/swagger.yaml b/sormas-rest/swagger.yaml index 412f6671307..7401e28ffbf 100644 --- a/sormas-rest/swagger.yaml +++ b/sormas-rest/swagger.yaml @@ -10,7 +10,7 @@ info: name: GPL v3 url: https://www.gnu.org/licenses/gpl-3.0.html title: SORMAS REST API - version: 1.98.0-SNAPSHOT + version: 1.104.0-SNAPSHOT servers: - url: /sormas-rest paths: @@ -2427,6 +2427,7 @@ paths: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -2438,6 +2439,7 @@ paths: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -2453,6 +2455,10 @@ paths: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED description: default response @@ -4099,6 +4105,7 @@ paths: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -4110,6 +4117,7 @@ paths: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -4125,6 +4133,10 @@ paths: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED requestBody: @@ -4424,6 +4436,7 @@ paths: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -4435,6 +4448,7 @@ paths: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -4450,6 +4464,10 @@ paths: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED responses: @@ -7041,6 +7059,12 @@ paths: - IMMUNIZATION_DELETE - IMMUNIZATION_ARCHIVE - IMMUNIZATION_VIEW_ARCHIVED + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_CREATE + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EDIT + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT - PERSON_VIEW - PERSON_EDIT - PERSON_DELETE @@ -7131,6 +7155,7 @@ paths: - DASHBOARD_CONTACT_VIEW_TRANSMISSION_CHAINS - DASHBOARD_CAMPAIGNS_VIEW - DASHBOARD_SAMPLES_VIEW + - DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW - CASE_CLINICIAN_VIEW - THERAPY_VIEW - PRESCRIPTION_CREATE @@ -7181,6 +7206,7 @@ paths: - ENVIRONMENT_DELETE - ENVIRONMENT_IMPORT - ENVIRONMENT_EXPORT + - ENVIRONMENT_LINK - ENVIRONMENT_SAMPLE_VIEW - ENVIRONMENT_SAMPLE_CREATE - ENVIRONMENT_SAMPLE_EDIT @@ -7219,10 +7245,23 @@ paths: - SORMAS_TO_SORMAS_PROCESS - EXTERNAL_SURVEILLANCE_SHARE - EXTERNAL_SURVEILLANCE_DELETE - - EXTERNAL_MESSAGE_VIEW - - EXTERNAL_MESSAGE_PROCESS - EXTERNAL_MESSAGE_PUSH - - EXTERNAL_MESSAGE_DELETE + - EXTERNAL_MESSAGE_ACCESS + - EXTERNAL_MESSAGE_LABORATORY_VIEW + - EXTERNAL_MESSAGE_DOCTOR_DECLARATION_VIEW + - EXTERNAL_MESSAGE_LABORATORY_PROCESS + - EXTERNAL_MESSAGE_DOCTOR_DECLARATION_PROCESS + - EXTERNAL_MESSAGE_LABORATORY_DELETE + - EXTERNAL_MESSAGE_DOCTOR_DECLARATION_DELETE + - SURVEY_VIEW + - SURVEY_CREATE + - SURVEY_EDIT + - SURVEY_DELETE + - SURVEY_TOKEN_VIEW + - SURVEY_TOKEN_CREATE + - SURVEY_TOKEN_EDIT + - SURVEY_TOKEN_DELETE + - SURVEY_TOKEN_IMPORT - OUTBREAK_VIEW - OUTBREAK_EDIT - MANAGE_PUBLIC_EXPORT_CONFIGURATION @@ -7233,6 +7272,12 @@ paths: - EXTERNAL_EMAIL_SEND - EXTERNAL_EMAIL_ATTACH_DOCUMENTS - CUSTOMIZABLE_ENUM_MANAGEMENT + - SYSTEM_CONFIGURATION + - DISEASE_MANAGEMENT + - EPIPULSE_EXPORT_VIEW + - EPIPULSE_EXPORT_CREATE + - EPIPULSE_EXPORT_DOWNLOAD + - EPIPULSE_EXPORT_DELETE description: default response tags: - User Controller @@ -7278,6 +7323,12 @@ paths: - IMMUNIZATION_DELETE - IMMUNIZATION_ARCHIVE - IMMUNIZATION_VIEW_ARCHIVED + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_CREATE + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EDIT + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT - PERSON_VIEW - PERSON_EDIT - PERSON_DELETE @@ -7368,6 +7419,7 @@ paths: - DASHBOARD_CONTACT_VIEW_TRANSMISSION_CHAINS - DASHBOARD_CAMPAIGNS_VIEW - DASHBOARD_SAMPLES_VIEW + - DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW - CASE_CLINICIAN_VIEW - THERAPY_VIEW - PRESCRIPTION_CREATE @@ -7418,6 +7470,7 @@ paths: - ENVIRONMENT_DELETE - ENVIRONMENT_IMPORT - ENVIRONMENT_EXPORT + - ENVIRONMENT_LINK - ENVIRONMENT_SAMPLE_VIEW - ENVIRONMENT_SAMPLE_CREATE - ENVIRONMENT_SAMPLE_EDIT @@ -7456,10 +7509,23 @@ paths: - SORMAS_TO_SORMAS_PROCESS - EXTERNAL_SURVEILLANCE_SHARE - EXTERNAL_SURVEILLANCE_DELETE - - EXTERNAL_MESSAGE_VIEW - - EXTERNAL_MESSAGE_PROCESS - EXTERNAL_MESSAGE_PUSH - - EXTERNAL_MESSAGE_DELETE + - EXTERNAL_MESSAGE_ACCESS + - EXTERNAL_MESSAGE_LABORATORY_VIEW + - EXTERNAL_MESSAGE_DOCTOR_DECLARATION_VIEW + - EXTERNAL_MESSAGE_LABORATORY_PROCESS + - EXTERNAL_MESSAGE_DOCTOR_DECLARATION_PROCESS + - EXTERNAL_MESSAGE_LABORATORY_DELETE + - EXTERNAL_MESSAGE_DOCTOR_DECLARATION_DELETE + - SURVEY_VIEW + - SURVEY_CREATE + - SURVEY_EDIT + - SURVEY_DELETE + - SURVEY_TOKEN_VIEW + - SURVEY_TOKEN_CREATE + - SURVEY_TOKEN_EDIT + - SURVEY_TOKEN_DELETE + - SURVEY_TOKEN_IMPORT - OUTBREAK_VIEW - OUTBREAK_EDIT - MANAGE_PUBLIC_EXPORT_CONFIGURATION @@ -7470,6 +7536,12 @@ paths: - EXTERNAL_EMAIL_SEND - EXTERNAL_EMAIL_ATTACH_DOCUMENTS - CUSTOMIZABLE_ENUM_MANAGEMENT + - SYSTEM_CONFIGURATION + - DISEASE_MANAGEMENT + - EPIPULSE_EXPORT_VIEW + - EPIPULSE_EXPORT_CREATE + - EPIPULSE_EXPORT_DOWNLOAD + - EPIPULSE_EXPORT_DELETE description: default response tags: - User Controller @@ -8164,6 +8236,10 @@ components: - MEANS_OF_TRANSPORT - PUBLIC_PLACE - SCATTERED + - SCHOOL + - EDUCATION_AND_CHILDCARE + - NURSING_HOME + - ASYLUM_SEEKERS_SHELTER - UNKNOWN - OTHER typeOfPlaceDetails: @@ -8348,6 +8424,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -8359,6 +8436,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -8374,6 +8452,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED districtId: @@ -8470,6 +8552,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -8481,6 +8564,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -8496,6 +8580,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -8566,6 +8654,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -8577,6 +8666,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -8592,6 +8682,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -8887,9 +8981,15 @@ components: birthdateDD: type: integer format: int32 + birthdateFrom: + type: string + format: date-time birthdateMM: type: integer format: int32 + birthdateTo: + type: string + format: date-time birthdateYYYY: type: integer format: int32 @@ -8966,6 +9066,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -8977,6 +9078,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -8992,6 +9094,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseVariant: @@ -9102,6 +9208,8 @@ components: $ref: '#/components/schemas/FacilityReferenceDto' includeCasesFromOtherJurisdictions: type: boolean + includePartialMatch: + type: boolean investigationStatus: type: string enum: @@ -9206,6 +9314,19 @@ components: type: string surveillanceOfficer: $ref: '#/components/schemas/UserReferenceDto' + survey: + $ref: '#/components/schemas/SurveyReferenceDto' + surveyAssignedFrom: + type: string + format: date-time + surveyAssignedTo: + type: string + format: date-time + surveyResponseStatus: + type: string + enum: + - RECEIVED + - NOT_RECEIVED symptomJournalStatus: type: string enum: @@ -9219,6 +9340,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN withExtendedQuarantine: type: boolean @@ -9347,6 +9472,8 @@ components: - DENGUE_FEVER - DENGUE_HEMORRHAGIC_FEVER - DENUGE_SHOCK_SYNDROME + department: + type: string disease: type: string enum: @@ -9383,6 +9510,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -9394,6 +9522,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -9409,6 +9538,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -9618,6 +9751,8 @@ components: type: boolean notACaseReasonPhysicianInformation: type: boolean + notifier: + $ref: '#/components/schemas/NotifierReferenceDto' notifyingClinic: type: string enum: @@ -9638,6 +9773,8 @@ components: type: string maxLength: 1000000 minLength: 0 + otherDiagnosticCriteria: + type: string outcome: type: string enum: @@ -9668,6 +9805,8 @@ components: minLength: 0 portHealthInfo: $ref: '#/components/schemas/PortHealthInfoDto' + postMortem: + type: boolean postpartum: type: string enum: @@ -9785,6 +9924,11 @@ components: enum: - FURIOUS_RABIES - PARALYTIC_RABIES + radiographyCompatibility: + type: string + enum: + - COMPATIBLE_WITH_TB + - NOT_COMPATIBLE_WITH_TB reInfection: type: string enum: @@ -9877,6 +10021,17 @@ components: $ref: '#/components/schemas/SymptomsDto' therapy: $ref: '#/components/schemas/TherapyDto' + treatmentNotApplicable: + type: boolean + treatmentStartDate: + type: string + format: date-time + treatmentStarted: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN trimester: type: string enum: @@ -9894,7 +10049,15 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN + vaccinationStatusDetails: + type: string + maxLength: 512 + minLength: 0 wasInQuarantineBeforeIsolation: type: string enum: @@ -9913,9 +10076,15 @@ components: birthdateDD: type: integer format: int32 + birthdateFrom: + type: string + format: date-time birthdateMM: type: integer format: int32 + birthdateTo: + type: string + format: date-time birthdateYYYY: type: integer format: int32 @@ -9992,6 +10161,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -10003,6 +10173,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -10018,6 +10189,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseVariant: @@ -10128,6 +10303,8 @@ components: $ref: '#/components/schemas/FacilityReferenceDto' includeCasesFromOtherJurisdictions: type: boolean + includePartialMatch: + type: boolean interval: type: integer format: int32 @@ -10238,6 +10415,19 @@ components: type: string surveillanceOfficer: $ref: '#/components/schemas/UserReferenceDto' + survey: + $ref: '#/components/schemas/SurveyReferenceDto' + surveyAssignedFrom: + type: string + format: date-time + surveyAssignedTo: + type: string + format: date-time + surveyResponseStatus: + type: string + enum: + - RECEIVED + - NOT_RECEIVED symptomJournalStatus: type: string enum: @@ -10251,6 +10441,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN withExtendedQuarantine: type: boolean @@ -10301,6 +10495,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -10312,6 +10507,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -10327,6 +10523,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED firstName: @@ -10437,6 +10637,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -10448,6 +10649,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -10463,6 +10665,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -10526,6 +10732,8 @@ components: missedVisitsCount: type: integer format: int32 + nationalHealthId: + type: string otherDeletionReason: type: string outcome: @@ -10628,6 +10836,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN visitCount: type: integer @@ -10702,6 +10914,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -10713,6 +10926,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -10728,6 +10942,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -10771,6 +10989,8 @@ components: missedVisitsCount: type: integer format: int32 + nationalHealthId: + type: string otherDeletionReason: type: string outcome: @@ -10849,6 +11069,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN visitCount: type: integer @@ -10950,6 +11174,7 @@ components: enum: - ANTIBODY_DETECTION - ANTIGEN_DETECTION + - RAPID_ANTIGEN_DETECTION - RAPID_TEST - CULTURE - HISTOPATHOLOGY @@ -10962,6 +11187,7 @@ components: - DIRECT_FLUORESCENT_ANTIBODY - MICROSCOPY - NEUTRALIZING_ANTIBODIES + - ENZYME_LINKED_IMMUNOSORBENT_ASSAY - PCR_RT_PCR - GRAM_STAIN - LATEX_AGGLUTINATION @@ -10969,6 +11195,25 @@ components: - SEQUENCING - DNA_MICROARRAY - TMA + - IGRA + - TST + - BEIJINGGENOTYPING + - SPOLIGOTYPING + - MIRU_PATTERN_CODE + - ANTIBIOTIC_SUSCEPTIBILITY + - MULTILOCUS_SEQUENCE_TYPING + - SLIDE_AGGLUTINATION + - WHOLE_GENOME_SEQUENCING + - SEROGROUPING + - GENOTYPING + - NAAT + - THICK_BLOOD_SMEAR + - THIN_BLOOD_SMEAR + - Q_PCR + - LAMP + - OTHER_ANTIGEN_DETECTION_TEST + - OTHER_MOLECULAR_ASSAY + - OTHER_SEROLOGICAL_TEST - OTHER ClassificationPathogenTestNegativeResultCriteriaDto: type: object @@ -10991,6 +11236,7 @@ components: enum: - ANTIBODY_DETECTION - ANTIGEN_DETECTION + - RAPID_ANTIGEN_DETECTION - RAPID_TEST - CULTURE - HISTOPATHOLOGY @@ -11003,6 +11249,7 @@ components: - DIRECT_FLUORESCENT_ANTIBODY - MICROSCOPY - NEUTRALIZING_ANTIBODIES + - ENZYME_LINKED_IMMUNOSORBENT_ASSAY - PCR_RT_PCR - GRAM_STAIN - LATEX_AGGLUTINATION @@ -11010,6 +11257,25 @@ components: - SEQUENCING - DNA_MICROARRAY - TMA + - IGRA + - TST + - BEIJINGGENOTYPING + - SPOLIGOTYPING + - MIRU_PATTERN_CODE + - ANTIBIOTIC_SUSCEPTIBILITY + - MULTILOCUS_SEQUENCE_TYPING + - SLIDE_AGGLUTINATION + - WHOLE_GENOME_SEQUENCING + - SEROGROUPING + - GENOTYPING + - NAAT + - THICK_BLOOD_SMEAR + - THIN_BLOOD_SMEAR + - Q_PCR + - LAMP + - OTHER_ANTIGEN_DETECTION_TEST + - OTHER_MOLECULAR_ASSAY + - OTHER_SEROLOGICAL_TEST - OTHER sampleTestTypes: type: array @@ -11018,6 +11284,7 @@ components: enum: - ANTIBODY_DETECTION - ANTIGEN_DETECTION + - RAPID_ANTIGEN_DETECTION - RAPID_TEST - CULTURE - HISTOPATHOLOGY @@ -11030,6 +11297,7 @@ components: - DIRECT_FLUORESCENT_ANTIBODY - MICROSCOPY - NEUTRALIZING_ANTIBODIES + - ENZYME_LINKED_IMMUNOSORBENT_ASSAY - PCR_RT_PCR - GRAM_STAIN - LATEX_AGGLUTINATION @@ -11037,6 +11305,25 @@ components: - SEQUENCING - DNA_MICROARRAY - TMA + - IGRA + - TST + - BEIJINGGENOTYPING + - SPOLIGOTYPING + - MIRU_PATTERN_CODE + - ANTIBIOTIC_SUSCEPTIBILITY + - MULTILOCUS_SEQUENCE_TYPING + - SLIDE_AGGLUTINATION + - WHOLE_GENOME_SEQUENCING + - SEROGROUPING + - GENOTYPING + - NAAT + - THICK_BLOOD_SMEAR + - THIN_BLOOD_SMEAR + - Q_PCR + - LAMP + - OTHER_ANTIGEN_DETECTION_TEST + - OTHER_MOLECULAR_ASSAY + - OTHER_SEROLOGICAL_TEST - OTHER writeOnly: true ClassificationPersonAgeBetweenYearsCriteriaDto: @@ -11159,6 +11446,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -11170,6 +11458,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -11185,6 +11474,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED inJurisdiction: @@ -11284,6 +11577,8 @@ components: type: string maxLength: 255 minLength: 0 + nutsCode: + type: string region: $ref: '#/components/schemas/RegionReferenceDto' uuid: @@ -11309,9 +11604,15 @@ components: birthdateDD: type: integer format: int32 + birthdateFrom: + type: string + format: date-time birthdateMM: type: integer format: int32 + birthdateTo: + type: string + format: date-time birthdateYYYY: type: integer format: int32 @@ -11407,6 +11708,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -11418,6 +11720,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -11433,6 +11736,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseVariant: @@ -11474,6 +11781,8 @@ components: format: date-time includeContactsFromOtherJurisdictions: type: boolean + includePartialMatch: + type: boolean lastContactDateFrom: type: string format: date-time @@ -11494,6 +11803,31 @@ components: $ref: '#/components/schemas/PersonReferenceDto' personLike: type: string + prescribedDrug: + type: string + enum: + - AMIKACIN + - BEDAQUILINE + - CAPREOMYCIN + - CIPROFLOXACIN + - DELAMANID + - ETHAMBUTOL + - GATIFLOXACIN + - ISONIAZID + - KANAMYCIN + - LEVOFLOXACIN + - MOXIFLOXACIN + - OFLOXACIN + - RIFAMPICIN + - STREPTOMYCIN + - CEFTRIAXONE + - PENICILLIN + - ERYTHROMYCIN + - OTHER + prescribedDrugText: + type: string + prophylaxisPrescribed: + type: boolean quarantineFrom: type: string format: date-time @@ -11570,6 +11904,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN withCase: type: boolean @@ -11658,6 +11996,7 @@ components: - AEROSOL - MEDICAL_DISTANT - MEDICAL_LIMITED + uniqueItems: true contactProximityDetails: type: string maxLength: 512 @@ -11722,6 +12061,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -11733,6 +12073,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -11748,6 +12089,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -11806,6 +12151,8 @@ components: $ref: '#/components/schemas/HealthConditionsDto' highPriority: type: boolean + immuneGlobulinProposed: + type: boolean immunosuppressiveTherapyBasicDisease: type: string enum: @@ -11837,6 +12184,29 @@ components: type: boolean person: $ref: '#/components/schemas/PersonReferenceDto' + prescribedDrug: + type: string + enum: + - AMIKACIN + - BEDAQUILINE + - CAPREOMYCIN + - CIPROFLOXACIN + - DELAMANID + - ETHAMBUTOL + - GATIFLOXACIN + - ISONIAZID + - KANAMYCIN + - LEVOFLOXACIN + - MOXIFLOXACIN + - OFLOXACIN + - RIFAMPICIN + - STREPTOMYCIN + - CEFTRIAXONE + - PENICILLIN + - ERYTHROMYCIN + - OTHER + prescribedDrugText: + type: string previousQuarantineTo: type: string format: date-time @@ -11852,6 +12222,8 @@ components: prohibitionToWorkUntil: type: string format: date-time + prophylaxisPrescribed: + type: boolean pseudonymized: type: boolean quarantine: @@ -11983,11 +12355,23 @@ components: maxLength: 36 minLength: 20 pattern: "^[0-9a-zA-Z-]*$" + vaccinationDoseOneDate: + type: string + format: date-time + vaccinationDoseTwoDate: + type: string + format: date-time + vaccinationProposed: + type: boolean vaccinationStatus: type: string enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN required: - disease @@ -12062,6 +12446,7 @@ components: - AEROSOL - MEDICAL_DISTANT - MEDICAL_LIMITED + uniqueItems: true contactStatus: type: string enum: @@ -12113,6 +12498,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -12124,6 +12510,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -12139,6 +12526,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -12169,6 +12560,9 @@ components: format: date-time houseNumber: type: string + id: + type: integer + format: int64 inJurisdiction: type: boolean internalToken: @@ -12185,6 +12579,8 @@ components: missedVisitsCount: type: integer format: int32 + nationalHealthId: + type: string otherDeletionReason: type: string personUuid: @@ -12193,6 +12589,31 @@ components: type: string postalCode: type: string + prescribedDrug: + type: string + enum: + - AMIKACIN + - BEDAQUILINE + - CAPREOMYCIN + - CIPROFLOXACIN + - DELAMANID + - ETHAMBUTOL + - GATIFLOXACIN + - ISONIAZID + - KANAMYCIN + - LEVOFLOXACIN + - MOXIFLOXACIN + - OFLOXACIN + - RIFAMPICIN + - STREPTOMYCIN + - CEFTRIAXONE + - PENICILLIN + - ERYTHROMYCIN + - OTHER + prescribedDrugText: + type: string + prophylaxisPrescribed: + type: boolean pseudonymized: type: boolean regionName: @@ -12237,6 +12658,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN visitCount: type: integer @@ -12304,6 +12729,7 @@ components: - AEROSOL - MEDICAL_DISTANT - MEDICAL_LIMITED + uniqueItems: true contactStatus: type: string enum: @@ -12355,6 +12781,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -12366,6 +12793,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -12381,6 +12809,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -12406,6 +12838,9 @@ components: followUpUntil: type: string format: date-time + id: + type: integer + format: int64 inJurisdiction: type: boolean internalToken: @@ -12418,10 +12853,37 @@ components: missedVisitsCount: type: integer format: int32 + nationalHealthId: + type: string otherDeletionReason: type: string personUuid: type: string + prescribedDrug: + type: string + enum: + - AMIKACIN + - BEDAQUILINE + - CAPREOMYCIN + - CIPROFLOXACIN + - DELAMANID + - ETHAMBUTOL + - GATIFLOXACIN + - ISONIAZID + - KANAMYCIN + - LEVOFLOXACIN + - MOXIFLOXACIN + - OFLOXACIN + - RIFAMPICIN + - STREPTOMYCIN + - CEFTRIAXONE + - PENICILLIN + - ERYTHROMYCIN + - OTHER + prescribedDrugText: + type: string + prophylaxisPrescribed: + type: boolean pseudonymized: type: boolean regionName: @@ -12447,6 +12909,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN visitCount: type: integer @@ -12626,6 +13092,8 @@ components: type: string maxLength: 3 minLength: 2 + nutsCode: + type: string subcontinent: $ref: '#/components/schemas/SubcontinentReferenceDto' unoCode: @@ -12656,6 +13124,8 @@ components: type: string isoCode: type: string + nutsCode: + type: string subcontinent: $ref: '#/components/schemas/SubcontinentReferenceDto' unoCode: @@ -13128,6 +13598,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -13139,6 +13610,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -13154,6 +13626,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED uniqueItems: true @@ -13438,6 +13914,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -13449,6 +13926,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -13464,6 +13942,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -13527,6 +14009,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -13538,6 +14021,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -13553,6 +14037,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -13661,6 +14149,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -13672,6 +14161,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -13687,6 +14177,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED eventCount: @@ -13751,6 +14245,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -13762,6 +14257,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -13777,6 +14273,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED notACaseCriteria: @@ -13804,6 +14304,8 @@ components: automaticSampleAssignmentThreshold: type: integer format: int32 + caseDefinitionText: + type: string caseFollowUpDuration: type: integer format: int32 @@ -13851,6 +14353,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -13862,6 +14365,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -13877,6 +14381,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED eventParticipantFollowUpDuration: @@ -13891,6 +14399,14 @@ components: format: int32 followUpEnabled: type: boolean + incubationPeriodEnabled: + type: boolean + maxIncubationPeriod: + type: integer + format: int32 + minIncubationPeriod: + type: integer + format: int32 primaryDisease: type: boolean uuid: @@ -13960,6 +14476,8 @@ components: type: string maxLength: 255 minLength: 0 + nutsCode: + type: string region: $ref: '#/components/schemas/RegionReferenceDto' uuid: @@ -13987,6 +14505,8 @@ components: format: float name: type: string + nutsCode: + type: string population: type: integer format: int32 @@ -14020,6 +14540,7 @@ components: - ACTION - EVENT - TRAVEL_ENTRY + - SURVEY entityUuids: type: array items: @@ -14063,6 +14584,211 @@ components: - name - size - uploadingUser + DrugSusceptibilityDto: + type: object + properties: + amikacinMic: + type: number + format: float + amikacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + bedaquilineMic: + type: number + format: float + bedaquilineSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + capreomycinMic: + type: number + format: float + capreomycinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + ceftriaxoneMic: + type: number + format: float + ceftriaxoneSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + changeDate: + type: string + format: date-time + ciprofloxacinMic: + type: number + format: float + ciprofloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + creationDate: + type: string + format: date-time + delamanidMic: + type: number + format: float + delamanidSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + erythromycinMic: + type: number + format: float + erythromycinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + ethambutolMic: + type: number + format: float + ethambutolSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + gatifloxacinMic: + type: number + format: float + gatifloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + inJurisdiction: + type: boolean + isoniazidMic: + type: number + format: float + isoniazidSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + kanamycinMic: + type: number + format: float + kanamycinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + levofloxacinMic: + type: number + format: float + levofloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + moxifloxacinMic: + type: number + format: float + moxifloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + ofloxacinMic: + type: number + format: float + ofloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + penicillinMic: + type: number + format: float + penicillinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + pseudonymized: + type: boolean + rifampicinMic: + type: number + format: float + rifampicinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + streptomycinMic: + type: number + format: float + streptomycinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + uuid: + type: string + maxLength: 36 + minLength: 20 + pattern: "^[0-9a-zA-Z-]*$" EnvironmentDto: type: object properties: @@ -14094,10 +14820,15 @@ components: - SOIL_ROCK - AIR - BIOTA + - VECTORS environmentName: type: string maxLength: 1000000 minLength: 0 + eventReferenceDtos: + type: array + items: + $ref: '#/components/schemas/EventReferenceDto' externalId: type: string maxLength: 512 @@ -14154,6 +14885,11 @@ components: maxLength: 36 minLength: 20 pattern: "^[0-9a-zA-Z-]*$" + vectorType: + type: string + enum: + - MOSQUITOS + - TICKS waterType: type: string enum: @@ -14314,6 +15050,11 @@ components: maxLength: 36 minLength: 20 pattern: "^[0-9a-zA-Z-]*$" + vectorType: + type: string + enum: + - MOSQUITOS + - TICKS weatherConditions: type: object additionalProperties: @@ -14354,15 +15095,39 @@ components: - "YES" - "NO" - UNKNOWN + caseImportedStatus: + type: string + enum: + - IMPORTED_CASE + - IMPORT_RELATED_CASE + - UNKNOWN_IMPORTATION_STATUS + - NOT_IMPORTED_CASE changeDate: type: string format: date-time + clusterRelated: + type: boolean + clusterType: + type: string + enum: + - KINDERGARTEN_OR_CHILDCARE + - FAMILY + - MILITARY + - NOSOCOMIAL + - SCHOOL + - SPORTS_TEAM + - UNIVERSITY + - OTHER + clusterTypeText: + type: string contactWithSourceCaseKnown: type: string enum: - "YES" - "NO" - UNKNOWN + country: + $ref: '#/components/schemas/CountryReferenceDto' creationDate: type: string format: date-time @@ -14382,21 +15147,58 @@ components: - "YES" - "NO" - UNKNOWN - inJurisdiction: - type: boolean - largeOutbreaksArea: + importedCase: type: string enum: - "YES" - "NO" - UNKNOWN - pseudonymized: + inJurisdiction: type: boolean - uuid: + infectionSource: type: string - maxLength: 36 - minLength: 20 - pattern: "^[0-9a-zA-Z-]*$" + enum: + - FOOD + - ANIMAL + - NOT_APPLICABLE + - OTHER + infectionSourceText: + type: string + largeOutbreaksArea: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + modeOfTransmission: + type: string + enum: + - ANIMAL_TO_HUMAN + - FOOD_OR_WATER + - PERSON_TO_PERSON + - RECREATIONAL_WATER + - HEALTHCARE_ASSOCIATED + - INJECTING_DRUG_USERS + - LAB_OCCUPATIONAL_EXPOSURE + - MOTHER_TO_CHILD + - SEXUAL + - TRANSFUSION_RECIPIENT + - ORGAN_RECIPIENT + - UNKNOWN + - OTHER + modeOfTransmissionType: + type: string + maxLength: 512 + minLength: 0 + otherDetails: + type: string + pseudonymized: + type: boolean + uuid: + type: string + maxLength: 36 + minLength: 20 + pattern: "^[0-9a-zA-Z-]*$" EpiWeek: type: object properties: @@ -14481,6 +15283,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -14492,6 +15295,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -14507,6 +15311,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED eventDiseaseDetails: @@ -14645,6 +15453,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -14656,6 +15465,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -14671,12 +15481,18 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseVariant: $ref: '#/components/schemas/DiseaseVariant' district: $ref: '#/components/schemas/DistrictReferenceDto' + environment: + $ref: '#/components/schemas/EnvironmentReferenceDto' eventDateFrom: type: string format: date-time @@ -14856,6 +15672,10 @@ components: - MEANS_OF_TRANSPORT - PUBLIC_PLACE - SCATTERED + - SCHOOL + - EDUCATION_AND_CHILDCARE + - NURSING_HOME + - ASYLUM_SEEKERS_SHELTER - UNKNOWN - OTHER userFilterIncluded: @@ -14920,6 +15740,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -14931,6 +15752,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -14946,6 +15768,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -14970,6 +15796,10 @@ components: endDate: type: string format: date-time + environmentReferenceDtos: + type: array + items: + $ref: '#/components/schemas/EnvironmentReferenceDto' epidemiologicalEvidence: type: string enum: @@ -15224,6 +16054,10 @@ components: - MEANS_OF_TRANSPORT - PUBLIC_PLACE - SCATTERED + - SCHOOL + - EDUCATION_AND_CHILDCARE + - NURSING_HOME + - ASYLUM_SEEKERS_SHELTER - UNKNOWN - OTHER typeOfPlaceText: @@ -15406,6 +16240,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -15417,6 +16252,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -15432,6 +16268,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -15616,6 +16456,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -15627,6 +16468,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -15642,6 +16484,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED event: @@ -15662,6 +16508,7 @@ components: - NEGATIVE - POSITIVE - NOT_DONE + - NOT_APPLICABLE person: $ref: '#/components/schemas/PersonReferenceDto' relevanceStatus: @@ -15679,6 +16526,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN EventParticipantDto: type: object @@ -15738,6 +16589,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN required: - event @@ -15786,6 +16641,7 @@ components: - NEGATIVE - POSITIVE - NOT_DONE + - NOT_APPLICABLE personUuid: type: string pseudonymized: @@ -15810,6 +16666,10 @@ components: enum: - VACCINATED - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER - UNKNOWN EventParticipantReferenceDto: type: object @@ -15838,6 +16698,13 @@ components: ExposureDto: type: object properties: + animalCategory: + type: string + enum: + - DOMESTIC + - WILD + animalCategoryDetails: + type: string animalCondition: type: string enum: @@ -15857,6 +16724,18 @@ components: type: string maxLength: 1000000 minLength: 0 + animalLocation: + type: string + enum: + - ZOO + - FARM + - PARK + - FOREST + - OTHER + animalLocationText: + type: string + maxLength: 1000000 + minLength: 0 animalMarket: type: string enum: @@ -15878,10 +16757,63 @@ components: changeDate: type: string format: date-time + childcareFacilityDetails: + type: string + maxLength: 1000000 + minLength: 0 + conditionOfAnimal: + type: string + enum: + - ALIVE + - DEAD + - PROCESSED + - UNKNOWN connectionNumber: type: string maxLength: 512 minLength: 0 + contactFactorDetails: + type: string + contactFactors: + type: array + items: + type: string + enum: + - DURATION_OF_EXPOSURE + - PROXIMITY_TO_SOURCE + - TYPE_OF_ACTIVITY + - POOR_VENTILATION + - PROXIMITY_AND_DURATION + - WIND_AND_AIRFLOW + - DENSITY_OF_PEOPLE + - SKIN_CONTACT + - BODY_FLUIDS + - BUTCHERING + - COOKING + - TOUCHING_CONTACT_WITH_FLUIDS + - SCRATCHES_BITES_LICKING + - SHARED_SURFACES + - OUTDOOR_ACTIVITIES + - STANDING_WATER_PROXIMITY + - HIGH_MOSQUITO_ACTIVITY_REGIONS + - UNPROTECTED_HOUSEHOLD + - MOSQUITO_ACTIVITY_TIME_OF_DAY + - CLOTHING_COVERAGE + - DURATION_OUTDOORS + - EXPOSED_SKIN + - DRINKING_CONTAMINATED_WATER + - ICE_AND_FOOD_PREPARATION + - SWALLOWING_WATER + - CONTACT_WITH_OPEN_WOUNDS + - EGG + - MEAT + - FISH_SEAFOOD + - DAIRY + - FRUIT + - RAW_VEGETABLES + - UNKNOWN + - OTHER + uniqueItems: true contactToBodyFluids: type: string enum: @@ -15911,6 +16843,12 @@ components: type: string maxLength: 1000000 minLength: 0 + domesticSwimming: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN eatingRawAnimalProducts: type: string enum: @@ -15920,6 +16858,19 @@ components: endDate: type: string format: date-time + exposureCategory: + type: string + enum: + - AIR_BORNE + - ANIMAL_CONTACT + - DIRECT_CONTACT + - FOMITE_TRANSMISSION + - FOOD_BORNE + - VECTOR_BORNE + - VERTICAL_TRANSMISSION + - WATER_BORNE + exposureComment: + type: string exposureRole: type: string enum: @@ -15940,6 +16891,23 @@ components: - TEACHER - UNKNOWN - OTHER + exposureSetting: + type: string + enum: + - INDOOR + - OUTDOOR + - PERSON_TO_PERSON + - MOSQUITO_BORNE + - TICK_BORNE + - DRINKING_WATER + - RECREATIONAL_WATER + - PREGNANCY_OR_DELIVERY + - OTHER + - UNKNOWN + exposureSettingDetails: + type: string + exposureSubSettingDetails: + type: string exposureType: type: string enum: @@ -15950,14 +16918,25 @@ components: - GATHERING - HABITATION - PERSONAL_SERVICES + - CHILDCARE_FACILITY - BURIAL - ANIMAL_CONTACT + - RECREATIONAL_WATER + - FOOD + - SEXUAL_CONTACT + - SYMPTOMATIC_CONTACT + - FLOOD_EXPOSURE - OTHER - UNKNOWN exposureTypeDetails: type: string maxLength: 1000000 minLength: 0 + fomiteTransmissionLocation: + type: string + enum: + - INSIDE_HOME + - OUTSIDE gatheringDetails: type: string maxLength: 1000000 @@ -16003,6 +16982,12 @@ components: - "YES" - "NO" - UNKNOWN + internationalSwimming: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN largeAttendanceNumber: type: string enum: @@ -16071,12 +17056,64 @@ components: prophylaxisDate: type: string format: date-time + protectiveMeasureDetails: + type: string + protectiveMeasures: + type: array + items: + type: string + enum: + - WEARING_MASK + - DISTANCE_1_5M + - FACE_TO_FACE_15MIN + - VENTILATION_HEPA + - HAND_HYGIENE + - AVOID_TOUCHING_FACE + - SAFE_SEX + - WEARING_PPE + - VACCINATION + - HAND_WASHING + - WOUND_WASHING + - INSECT_REPELLENT + - HERBS + - COILS + - SLEEPING_UNDER_BEDNET + - INDOOR_SPRAYING + - AIR_CONDITION + - PROTECTIVE_CLOTHING + - ENVIRONMENTAL_CONTROL + - TICK_PREVENTION + - REGULAR_CHECKS + - WATER_PURIFICATION + - SAFE_WATER_SOURCES + - AVOID_RAW_FOODS + - NO_SWIMMING_CONTAMINATED + - SHOWER_AFTER_SWIMMING + - WOUND_COVERAGE + - BATHING_CONTAMINATED_WATER + - WELL_COOKED + - COLD_HOT_CHAIN + - WASHED + - MEDICATION + - C_SECTION + - OTHER + uniqueItems: true protectiveMeasuresDetails: type: string maxLength: 1000000 minLength: 0 pseudonymized: type: boolean + rawFoodContact: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + rawFoodContactText: + type: string + maxLength: 1000000 + minLength: 0 reportingUser: $ref: '#/components/schemas/UserReferenceDto' riskArea: @@ -16089,6 +17126,10 @@ components: type: string maxLength: 512 minLength: 0 + sexualExposureText: + type: string + maxLength: 1000000 + minLength: 0 shortDistance: type: string enum: @@ -16098,6 +17139,60 @@ components: startDate: type: string format: date-time + subSettings: + type: array + items: + type: string + enum: + - CLOSED_POORLY_VENTILATED + - SHARED_HIGH_OCCUPANCY + - ENCLOSED_LIMITED_CIRCULATION + - VEHICLES + - HEALTHCARE_SETTINGS + - TEMPORARY_SHELTERS + - CROWDED_OUTDOOR_LIMITED_AIRFLOW + - CLOSE_PHYSICAL_CONTACT + - HIGH_TOUCH_ENVIRONMENTS + - SEXUAL_ACTIVITY + - STANDING_WATER_AREAS + - HIGH_MOSQUITO_ACTIVITY_REGIONS + - FORESTED_GRASSY_RURAL + - WILDLIFE_RESERVOIR_AREAS + - EATING_AT_HOME + - EATING_OUTSIDE + - UNKNOWN + - OTHER + uniqueItems: true + swimmingLocation: + type: string + enum: + - PUBLIC + - PRIVATE + - LAKE + - RIVER + - OCEAN + - WATER_PARK + - OTHER + swimmingLocationType: + type: string + maxLength: 512 + minLength: 0 + symptomaticIndividualText: + type: string + maxLength: 1000000 + minLength: 0 + travelAccommodation: + type: string + enum: + - HOTEL + - CAMPING + - RENTED_APARTMENT_OR_HOUSE + - FRIENDS_OR_FAMILY + - OTHER + travelAccommodationType: + type: string + maxLength: 512 + minLength: 0 typeOfAnimal: type: string enum: @@ -16115,11 +17210,24 @@ components: - RODENT - TICK - FLEA + - BIRDS + - GOAT + - HORSE + - SHEEP - OTHER typeOfAnimalDetails: type: string maxLength: 1000000 minLength: 0 + typeOfChildcareFacility: + type: string + enum: + - NURSERY_CRECHE + - NURSERY_SCHOOL + - CHILDMINDER_CENTER + - CHILDCARE_CENTER + - SCHOOL + - OTHER typeOfPlace: type: string enum: @@ -16132,6 +17240,10 @@ components: - MEANS_OF_TRANSPORT - PUBLIC_PLACE - SCATTERED + - SCHOOL + - EDUCATION_AND_CHILDCARE + - NURSING_HOME + - ASYLUM_SEEKERS_SHELTER - UNKNOWN - OTHER typeOfPlaceDetails: @@ -16239,6 +17351,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -16250,6 +17363,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -16265,6 +17379,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseVariant: @@ -16300,19 +17418,62 @@ components: ExternalMessageDto: type: object properties: + activitiesAsCase: + type: string + additionalPersonAddresses: + type: string + additionalPersonContactDetails: + type: string + admittedToHealthFacility: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN assignee: $ref: '#/components/schemas/UserReferenceDto' automaticProcessingPossible: type: boolean + caseClassification: + type: string + enum: + - NOT_CLASSIFIED + - SUSPECT + - PROBABLE + - CONFIRMED + - CONFIRMED_NO_SYMPTOMS + - CONFIRMED_UNKNOWN_SYMPTOMS + - NO_CASE + caseComments: + type: string + maxLength: 1000000 + minLength: 0 caseReportDate: type: string format: date-time + caseSymptoms: + $ref: '#/components/schemas/SymptomsDto' changeDate: type: string format: date-time + complianceWithTreatment: + type: string + enum: + - NO_COMPLIANCE + - TREATMENT_COMPLETED + - TREATMENT_FAILED + - TREATMENT_NOT_COMPLETED + - UNKNOWN + - NOT_APPLICABLE creationDate: type: string format: date-time + deceasedDate: + type: string + format: date-time + diagnosticDate: + type: string + format: date-time disease: type: string enum: @@ -16349,6 +17510,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -16360,6 +17522,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -16375,6 +17538,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseVariant: @@ -16383,17 +17550,81 @@ components: type: string maxLength: 512 minLength: 0 + exposures: + type: string externalMessageDetails: type: string maxLength: 1000000 minLength: 0 + hiv: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + hivArt: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + hospitalizationAdmissionDate: + type: string + format: date-time + hospitalizationDischargeDate: + type: string + format: date-time + hospitalizationFacilityDepartment: + type: string + maxLength: 512 + minLength: 0 + hospitalizationFacilityExternalId: + type: string + maxLength: 512 + minLength: 0 + hospitalizationFacilityName: + type: string + maxLength: 512 + minLength: 0 inJurisdiction: type: boolean messageDateTime: type: string format: date-time + notifierAddress: + type: string + maxLength: 1000000 + minLength: 0 + notifierEmail: + type: string + maxLength: 255 + minLength: 0 + notifierFirstName: + type: string + maxLength: 255 + minLength: 0 + notifierLastName: + type: string + maxLength: 255 + minLength: 0 + notifierPhone: + type: string + maxLength: 255 + minLength: 0 + notifierRegistrationNumber: + type: string + maxLength: 255 + minLength: 0 + otherDiagnosticCriteria: + type: string + maxLength: 512 + minLength: 0 ownershipHandedOver: type: boolean + personAdditionalDetails: + type: string + maxLength: 1000000 + minLength: 0 personBirthDateDD: type: integer format: int32 @@ -16423,6 +17654,26 @@ components: type: string maxLength: 255 minLength: 0 + personGuardianEmail: + type: string + maxLength: 255 + minLength: 0 + personGuardianFirstName: + type: string + maxLength: 255 + minLength: 0 + personGuardianLastName: + type: string + maxLength: 255 + minLength: 0 + personGuardianPhone: + type: string + maxLength: 255 + minLength: 0 + personGuardianRelationship: + type: string + maxLength: 255 + minLength: 0 personHouseNumber: type: string maxLength: 255 @@ -16435,6 +17686,10 @@ components: type: string maxLength: 255 minLength: 0 + personOccupation: + type: string + maxLength: 512 + minLength: 0 personPhone: type: string maxLength: 255 @@ -16468,8 +17723,19 @@ components: type: string maxLength: 255 minLength: 0 + previousTuberculosisTreatment: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN pseudonymized: type: boolean + radiographyCompatibility: + type: string + enum: + - COMPATIBLE_WITH_TB + - NOT_COMPATIBLE_WITH_TB reportId: type: string maxLength: 512 @@ -16515,16 +17781,52 @@ components: - UNCLEAR surveillanceReport: $ref: '#/components/schemas/SurveillanceReportReferenceDto' - type: + treatmentNotApplicable: + type: boolean + treatmentStarted: type: string enum: - - LAB_MESSAGE - - PHYSICIANS_REPORT - uuid: + - "YES" + - "NO" + - UNKNOWN + treatmentStartedDate: + type: string + format: date-time + tuberculosis: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + tuberculosisBeijingLineage: + type: boolean + tuberculosisDirectlyObservedTreatment: + type: boolean + tuberculosisInfectionYear: + type: integer + format: int32 + tuberculosisMdrXdrTuberculosis: + type: boolean + type: + type: string + enum: + - LAB_MESSAGE + - PHYSICIANS_REPORT + uuid: type: string maxLength: 36 minLength: 20 pattern: "^[0-9a-zA-Z-]*$" + vaccinationStatus: + type: string + enum: + - VACCINATED + - UNVACCINATED + - VACCINATED_ONE_DOSE + - VACCINATED_TWO_DOSE + - RECOVERED + - OTHER + - UNKNOWN ExternalMessageIndexDto: type: object properties: @@ -16566,6 +17868,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -16577,6 +17880,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -16592,6 +17896,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseVariant: @@ -16678,6 +17986,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -16689,6 +17998,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -16704,6 +18014,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED personUuid: @@ -17138,6 +18452,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -17149,6 +18464,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -17164,6 +18480,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -17186,6 +18506,7 @@ components: - TASK_MANAGEMENT - WEEKLY_REPORTING - IMMUNIZATION_MANAGEMENT + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_MANAGEMENT - TRAVEL_ENTRIES - DASHBOARD_SURVEILLANCE - DASHBOARD_CONTACTS @@ -17194,6 +18515,7 @@ components: - LIMITED_SYNCHRONIZATION - ENVIRONMENT_MANAGEMENT - SELF_REPORTING + - SURVEYS - ASSIGN_TASKS_TO_HIGHER_LEVEL - CASE_FOLLOWUP - DOCUMENTS @@ -17240,6 +18562,7 @@ components: - TASK_GENERATION_EVENT_SURVEILLANCE - TASK_GENERATION_GENERAL - CASE_AND_CONTACT_BULK_ACTIONS + - EPIPULSE_EXPORT region: $ref: '#/components/schemas/RegionReferenceDto' searchText: @@ -17289,6 +18612,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -17300,6 +18624,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -17315,6 +18640,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -17338,6 +18667,7 @@ components: - TASK_MANAGEMENT - WEEKLY_REPORTING - IMMUNIZATION_MANAGEMENT + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_MANAGEMENT - TRAVEL_ENTRIES - DASHBOARD_SURVEILLANCE - DASHBOARD_CONTACTS @@ -17346,6 +18676,7 @@ components: - LIMITED_SYNCHRONIZATION - ENVIRONMENT_MANAGEMENT - SELF_REPORTING + - SURVEYS - ASSIGN_TASKS_TO_HIGHER_LEVEL - CASE_FOLLOWUP - DOCUMENTS @@ -17392,6 +18723,7 @@ components: - TASK_GENERATION_EVENT_SURVEILLANCE - TASK_GENERATION_GENERAL - CASE_AND_CONTACT_BULK_ACTIONS + - EPIPULSE_EXPORT properties: type: object additionalProperties: @@ -17442,6 +18774,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -17453,6 +18786,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -17468,6 +18802,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED districtName: @@ -17548,6 +18886,15 @@ components: - "YES" - "NO" - UNKNOWN + complianceWithTreatment: + type: string + enum: + - NO_COMPLIANCE + - TREATMENT_COMPLETED + - TREATMENT_FAILED + - TREATMENT_NOT_COMPLETED + - UNKNOWN + - NOT_APPLICABLE congenitalSyphilis: type: string enum: @@ -17575,6 +18922,14 @@ components: - "YES" - "NO" - UNKNOWN + exposedToMosquitoBorneViruses: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + exposedToMosquitoBorneVirusesText: + type: string formerSmoker: type: string enum: @@ -17611,8 +18966,19 @@ components: - "YES" - "NO" - UNKNOWN + immunodeficiencyOtherThanHivText: + type: string inJurisdiction: type: boolean + malaria: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + malariaInfectedYear: + type: integer + format: int32 malignancyChemotherapy: type: string enum: @@ -17629,8 +18995,20 @@ components: type: string maxLength: 4096 minLength: 0 + previousTuberculosisTreatment: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN pseudonymized: type: boolean + recurrentBronchiolitis: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN sickleCellDisease: type: string enum: @@ -17643,11 +19021,20 @@ components: - "YES" - "NO" - UNKNOWN + tuberculosisInfectionYear: + type: integer + format: int32 uuid: type: string maxLength: 36 minLength: 20 pattern: "^[0-9a-zA-Z-]*$" + vaccinatedAgainstMosquitoBorneViruses: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN HospitalizationDto: type: object properties: @@ -17666,6 +19053,12 @@ components: creationDate: type: string format: date-time + currentlyHospitalized: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN description: type: string maxLength: 4096 @@ -17673,6 +19066,9 @@ components: dischargeDate: type: string format: date-time + durationOfHospitalization: + type: integer + format: int32 hospitalizationReason: type: string enum: @@ -17686,6 +19082,9 @@ components: - "YES" - "NO" - UNKNOWN + icuLengthOfStay: + type: integer + format: int32 intensiveCareUnit: type: string enum: @@ -17717,10 +19116,22 @@ components: type: string maxLength: 1000000 minLength: 0 + oxygenPrescribed: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN previousHospitalizations: type: array items: $ref: '#/components/schemas/PreviousHospitalizationDto' + stillHospitalized: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN uuid: type: string maxLength: 36 @@ -17781,6 +19192,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -17792,6 +19204,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -17807,6 +19220,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -17920,6 +19337,8 @@ components: - VACCINATION - RECOVERY - VACCINATION_RECOVERY + - MATERNAL_VACCINATION + - MONOCLONAL_ANTIBODY - OTHER nameAddressPhoneEmailLike: type: string @@ -18005,6 +19424,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -18016,6 +19436,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -18031,6 +19452,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -18129,6 +19554,12 @@ components: - EXPIRED inJurisdiction: type: boolean + injectionFacility: + type: string + enum: + - MATERNITY_WARD + - PAEDIATRIC_PRACTICE + - HOSPITAL lastInfectionDate: type: string format: date-time @@ -18138,6 +19569,8 @@ components: - VACCINATION - RECOVERY - VACCINATION_RECOVERY + - MATERNAL_VACCINATION + - MONOCLONAL_ANTIBODY - OTHER meansOfImmunizationDetails: type: string @@ -18262,6 +19695,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -18273,6 +19707,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -18288,6 +19723,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -18319,6 +19758,8 @@ components: - VACCINATION - RECOVERY - VACCINATION_RECOVERY + - MATERNAL_VACCINATION + - MONOCLONAL_ANTIBODY - OTHER otherDeletionReason: type: string @@ -18748,6 +20189,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -18759,6 +20201,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -18774,6 +20217,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED healthFacilityLat: @@ -18963,6 +20410,23 @@ components: maxLength: 36 minLength: 20 pattern: "^[0-9a-zA-Z-]*$" + NotifierReferenceDto: + type: object + properties: + caption: + type: string + firstName: + type: string + lastName: + type: string + uuid: + type: string + pattern: "^[0-9a-zA-Z-]*$" + versionDate: + type: string + format: date-time + required: + - uuid OccupationType: type: object properties: @@ -19031,6 +20495,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -19042,6 +20507,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -19057,6 +20523,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED uniqueItems: true @@ -19115,6 +20585,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -19126,6 +20597,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -19141,6 +20613,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -19761,6 +21237,10 @@ components: PathogenTestDto: type: object properties: + antibodyTitre: + type: string + maxLength: 512 + minLength: 0 changeDate: type: string format: date-time @@ -19799,6 +21279,8 @@ components: - TRANSFERRED_RESPONSIBILITY - DUPLICATE_ENTRIES - OTHER_REASON + drugSusceptibility: + $ref: '#/components/schemas/DrugSusceptibilityDto' environmentSample: $ref: '#/components/schemas/EnvironmentSampleReferenceDto' externalId: @@ -19811,8 +21293,48 @@ components: minLength: 0 fourFoldIncreaseAntibodyTiter: type: boolean + genoType: + type: string + enum: + - GENOTYPE_A + - GENOTYPE_B + - GENOTYPE_B2 + - GENOTYPE_B3 + - GENOTYPE_C1 + - GENOTYPE_C2 + - GENOTYPE_D1 + - GENOTYPE_D10 + - GENOTYPE_D11 + - GENOTYPE_D2 + - GENOTYPE_D3 + - GENOTYPE_D4 + - GENOTYPE_D5 + - GENOTYPE_D6 + - GENOTYPE_D7 + - GENOTYPE_D8 + - GENOTYPE_D9 + - GENOTYPE_E + - GENOTYPE_F + - GENOTYPE_G1 + - GENOTYPE_G2 + - GENOTYPE_G3 + - GENOTYPE_H1 + - GENOTYPE_H2 + - CRYPTOSPORIDIUM_HOMINIS + - CRYPTOSPORIDIUM_PARVUM + - CRYPTOSPORIDIUM_SPECIES + - OTHER + - UNKNOWN + genoTypeText: + type: string inJurisdiction: type: boolean + isoniazidResistant: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN lab: $ref: '#/components/schemas/FacilityReferenceDto' labDetails: @@ -19825,11 +21347,15 @@ components: type: string maxLength: 1000000 minLength: 0 + patternProfile: + type: string pcrTestSpecification: type: string enum: - VARIANT_SPECIFIC - N501Y_MUTATION_DETECTION + performedByReferenceLaboratory: + type: boolean preliminary: type: boolean prescriberAddress: @@ -19867,12 +21393,98 @@ components: reportDate: type: string format: date-time + resultDetails: + type: string + maxLength: 512 + minLength: 0 + retestRequested: + type: boolean + rifampicinResistant: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + rsvSubtype: + type: string + enum: + - RSV_A + - RSV_B + - RSV_A_AND_B + - INDETERMINATE sample: $ref: '#/components/schemas/SampleReferenceDto' + seroGroupSpecification: + type: string + enum: + - SEROGROUP_A + - SEROGROUP_B + - SEROGROUP_C + - SEROGROUP_W + - SEROGROUP_X + - SEROGROUP_Y + - SEROGROUP_Z + - SEROGROUP_29E + - NOT_GROUPABLE + - NOT_UNDER_SURVEILLANCE + - OTHER + - UNKNOWN + seroGroupSpecificationText: + type: string + seroTypingMethod: + type: string + enum: + - MULTIPLEX_PCR + - QUELLUNG_REACTION + - COAGGLUTINATION + - GEL_DIFFUSION + - PNEUMOTEST + - SLIDE_AGGLUTINATION + - OTHER + seroTypingMethodText: + type: string serotype: + type: string + enum: + - DENV_1 + - DENV_2 + - DENV_3 + - DENV_4 + - OTHER + serotypeText: + type: string + maxLength: 512 + minLength: 0 + specie: + type: string + enum: + - MYCOBATERIUM_AFRICANUM + - MYCOBATERIUM_BOVIS + - MYCOBATERIUM_TUBERCULOSIS + - OTHER_MTBC_MEMBER + - SPP + - FALCIPARUM + - VIVAX + - MALARIAE + - OVALE + - KNOWLESI + - CYNOMOLGI + - NOT_SPECIFIED + - COINFECTION + - OTHER + - UNKNOWN + - NOT_APPLICABLE + specieText: type: string maxLength: 512 minLength: 0 + strainCallStatus: + type: string + enum: + - BEIJING + - NOBEIJING + - POSSBEIJING + - UNKNOWN testDateTime: type: string format: date-time @@ -19884,17 +21496,25 @@ components: - NEGATIVE - POSITIVE - NOT_DONE + - NOT_APPLICABLE testResultText: type: string maxLength: 4096 minLength: 0 testResultVerified: type: boolean + testScale: + type: string + enum: + - ONE_PLUS + - TWO_PLUS + - THREE_PLUS testType: type: string enum: - ANTIBODY_DETECTION - ANTIGEN_DETECTION + - RAPID_ANTIGEN_DETECTION - RAPID_TEST - CULTURE - HISTOPATHOLOGY @@ -19907,6 +21527,7 @@ components: - DIRECT_FLUORESCENT_ANTIBODY - MICROSCOPY - NEUTRALIZING_ANTIBODIES + - ENZYME_LINKED_IMMUNOSORBENT_ASSAY - PCR_RT_PCR - GRAM_STAIN - LATEX_AGGLUTINATION @@ -19914,6 +21535,25 @@ components: - SEQUENCING - DNA_MICROARRAY - TMA + - IGRA + - TST + - BEIJINGGENOTYPING + - SPOLIGOTYPING + - MIRU_PATTERN_CODE + - ANTIBIOTIC_SUSCEPTIBILITY + - MULTILOCUS_SEQUENCE_TYPING + - SLIDE_AGGLUTINATION + - WHOLE_GENOME_SEQUENCING + - SEROGROUPING + - GENOTYPING + - NAAT + - THICK_BLOOD_SMEAR + - THIN_BLOOD_SMEAR + - Q_PCR + - LAMP + - OTHER_ANTIGEN_DETECTION_TEST + - OTHER_MOLECULAR_ASSAY + - OTHER_SEROLOGICAL_TEST - OTHER testTypeText: type: string @@ -19955,6 +21595,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -19966,6 +21607,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -19981,6 +21623,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED testedDiseaseDetails: @@ -19999,6 +21645,26 @@ components: type: string maxLength: 512 minLength: 0 + tubeAgTb1: + type: number + format: float + tubeAgTb1GT10: + type: boolean + tubeAgTb2: + type: number + format: float + tubeAgTb2GT10: + type: boolean + tubeMitogene: + type: number + format: float + tubeMitogeneGT10: + type: boolean + tubeNil: + type: number + format: float + tubeNilGT10: + type: boolean typingId: type: string maxLength: 1000000 @@ -20013,7 +21679,6 @@ components: required: - lab - testResult - - testResultVerified - testType PeriodDto: type: object @@ -20087,9 +21752,15 @@ components: birthdateDD: type: integer format: int32 + birthdateFrom: + type: string + format: date-time birthdateMM: type: integer format: int32 + birthdateTo: + type: string + format: date-time birthdateYYYY: type: integer format: int32 @@ -20097,6 +21768,8 @@ components: $ref: '#/components/schemas/CommunityReferenceDto' district: $ref: '#/components/schemas/DistrictReferenceDto' + includePartialMatch: + type: boolean nameAddressPhoneEmailLike: type: string personAssociation: @@ -20163,6 +21836,11 @@ components: birthWeight: type: integer format: int32 + birthWeightCategory: + type: string + enum: + - NORMAL + - LOW_BIRTH_WEIGHT birthdateDD: type: integer format: int32 @@ -20229,6 +21907,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -20240,6 +21919,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -20255,6 +21935,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED changeDate: @@ -20296,6 +21980,11 @@ components: emailAddress: type: string readOnly: true + emancipated: + type: boolean + entryDate: + type: string + format: date-time externalId: type: string maxLength: 512 @@ -20315,10 +22004,18 @@ components: gestationAgeAtBirth: type: integer format: int32 + gestationalAgeCategory: + type: string + enum: + - AT_TERM + - PREMATURE_BEFORE_32 + - PREMATURE_32_TO_38 hasCovidApp: type: boolean inJurisdiction: type: boolean + incapacitated: + type: boolean internalToken: type: string maxLength: 1000000 @@ -20327,6 +22024,13 @@ components: type: string maxLength: 512 minLength: 0 + livingStatus: + type: string + enum: + - MIGRANT + - FOREIGNER + - HOMELESS + - REGISTERED_AT_A_RESIDENCE mothersMaidenName: type: string maxLength: 255 @@ -20335,6 +22039,12 @@ components: type: string maxLength: 512 minLength: 0 + multipleBirth: + type: string + enum: + - SINGLE + - TWINS + - MULTIPLE namesOfGuardians: type: string maxLength: 512 @@ -20481,6 +22191,15 @@ components: maxLength: 36 minLength: 20 pattern: "^[0-9a-zA-Z-]*$" + workPlace: + type: string + enum: + - SCHOOL + - NURSERY + - UNKNOWN + - OTHER + workPlaceText: + type: string required: - firstName - lastName @@ -20570,6 +22289,8 @@ components: birthdateYYYY: type: integer format: int32 + checkOnlyForNationalHealthId: + type: boolean firstName: type: string lastName: @@ -20911,6 +22632,7 @@ components: enum: - ANTIMICROBIAL - ANTIVIRAL + - ANTIBIOTIC - OTHER uuid: type: string @@ -20969,6 +22691,7 @@ components: enum: - ANTIMICROBIAL - ANTIVIRAL + - ANTIBIOTIC - OTHER PrescriptionReferenceDto: type: object @@ -21011,6 +22734,10 @@ components: $ref: '#/components/schemas/DistrictReferenceDto' healthFacility: $ref: '#/components/schemas/FacilityReferenceDto' + healthFacilityDepartment: + type: string + maxLength: 512 + minLength: 0 healthFacilityDetails: type: string maxLength: 512 @@ -21022,6 +22749,9 @@ components: - ISOLATION - OTHER - UNKNOWN + icuLengthOfStay: + type: integer + format: int32 inJurisdiction: type: boolean intensiveCareUnit: @@ -21049,10 +22779,22 @@ components: type: string maxLength: 1000000 minLength: 0 + oxygenPrescribed: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN pseudonymized: type: boolean region: $ref: '#/components/schemas/RegionReferenceDto' + stillHospitalized: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN uuid: type: string maxLength: 36 @@ -21116,6 +22858,8 @@ components: type: string maxLength: 255 minLength: 0 + nutsCode: + type: string uuid: type: string maxLength: 36 @@ -21145,6 +22889,8 @@ components: format: float name: type: string + nutsCode: + type: string population: type: integer format: int32 @@ -21233,6 +22979,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -21244,6 +22991,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -21259,6 +23007,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -21279,6 +23031,7 @@ components: - NEGATIVE - POSITIVE - NOT_DONE + - NOT_APPLICABLE received: type: boolean referred: @@ -21379,6 +23132,7 @@ components: - NEGATIVE - POSITIVE - NOT_DONE + - NOT_APPLICABLE pathogenTestingRequested: type: boolean pseudonymized: @@ -21444,6 +23198,7 @@ components: enum: - ANTIBODY_DETECTION - ANTIGEN_DETECTION + - RAPID_ANTIGEN_DETECTION - RAPID_TEST - CULTURE - HISTOPATHOLOGY @@ -21456,6 +23211,7 @@ components: - DIRECT_FLUORESCENT_ANTIBODY - MICROSCOPY - NEUTRALIZING_ANTIBODIES + - ENZYME_LINKED_IMMUNOSORBENT_ASSAY - PCR_RT_PCR - GRAM_STAIN - LATEX_AGGLUTINATION @@ -21463,6 +23219,25 @@ components: - SEQUENCING - DNA_MICROARRAY - TMA + - IGRA + - TST + - BEIJINGGENOTYPING + - SPOLIGOTYPING + - MIRU_PATTERN_CODE + - ANTIBIOTIC_SUSCEPTIBILITY + - MULTILOCUS_SEQUENCE_TYPING + - SLIDE_AGGLUTINATION + - WHOLE_GENOME_SEQUENCING + - SEROGROUPING + - GENOTYPING + - NAAT + - THICK_BLOOD_SMEAR + - THIN_BLOOD_SMEAR + - Q_PCR + - LAMP + - OTHER_ANTIGEN_DETECTION_TEST + - OTHER_MOLECULAR_ASSAY + - OTHER_SEROLOGICAL_TEST - OTHER uniqueItems: true sampleDateTime: @@ -21472,8 +23247,10 @@ components: type: string enum: - BLOOD + - DRY_BLOOD - SERA - STOOL + - THROAT_ASPIRATE - NASAL_SWAB - THROAT_SWAB - NP_SWAB @@ -21486,6 +23263,7 @@ components: - SALIVA - URINE_PM - NUCHAL_SKIN_BIOPSY + - BIOPSY - SPUTUM - ENDOTRACHEAL_ASPIRATE - BRONCHOALVEOLAR_LAVAGE @@ -21494,6 +23272,15 @@ components: - OP_ASPIRATE - NP_ASPIRATE - PLEURAL_FLUID + - NASOPHARYNGEAL_LAVAGE + - OROPHARYNGEAL_SWAB + - AMNIOTIC_FLUID + - CLINICAL_SAMPLE + - PERITONEAL_FLUID + - SYNOVIAL_FLUID + - EDTA_WHOLE_BLOOD + - INTESTINAL_FLUID + - DUODENUM_FLUID - OTHER sampleMaterialText: type: string @@ -21619,6 +23406,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -21630,6 +23418,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -21645,6 +23434,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -21675,6 +23468,7 @@ components: - NEGATIVE - POSITIVE - NOT_DONE + - NOT_APPLICABLE pseudonymized: type: boolean received: @@ -21693,8 +23487,10 @@ components: type: string enum: - BLOOD + - DRY_BLOOD - SERA - STOOL + - THROAT_ASPIRATE - NASAL_SWAB - THROAT_SWAB - NP_SWAB @@ -21707,6 +23503,7 @@ components: - SALIVA - URINE_PM - NUCHAL_SKIN_BIOPSY + - BIOPSY - SPUTUM - ENDOTRACHEAL_ASPIRATE - BRONCHOALVEOLAR_LAVAGE @@ -21715,6 +23512,15 @@ components: - OP_ASPIRATE - NP_ASPIRATE - PLEURAL_FLUID + - NASOPHARYNGEAL_LAVAGE + - OROPHARYNGEAL_SWAB + - AMNIOTIC_FLUID + - CLINICAL_SAMPLE + - PERITONEAL_FLUID + - SYNOVIAL_FLUID + - EDTA_WHOLE_BLOOD + - INTESTINAL_FLUID + - DUODENUM_FLUID - OTHER samplePurpose: type: string @@ -21755,6 +23561,7 @@ components: enum: - ANTIBODY_DETECTION - ANTIGEN_DETECTION + - RAPID_ANTIGEN_DETECTION - RAPID_TEST - CULTURE - HISTOPATHOLOGY @@ -21767,6 +23574,7 @@ components: - DIRECT_FLUORESCENT_ANTIBODY - MICROSCOPY - NEUTRALIZING_ANTIBODIES + - ENZYME_LINKED_IMMUNOSORBENT_ASSAY - PCR_RT_PCR - GRAM_STAIN - LATEX_AGGLUTINATION @@ -21774,6 +23582,25 @@ components: - SEQUENCING - DNA_MICROARRAY - TMA + - IGRA + - TST + - BEIJINGGENOTYPING + - SPOLIGOTYPING + - MIRU_PATTERN_CODE + - ANTIBIOTIC_SUSCEPTIBILITY + - MULTILOCUS_SEQUENCE_TYPING + - SLIDE_AGGLUTINATION + - WHOLE_GENOME_SEQUENCING + - SEROGROUPING + - GENOTYPING + - NAAT + - THICK_BLOOD_SMEAR + - THIN_BLOOD_SMEAR + - Q_PCR + - LAMP + - OTHER_ANTIGEN_DETECTION_TEST + - OTHER_MOLECULAR_ASSAY + - OTHER_SEROLOGICAL_TEST - OTHER uuid: type: string @@ -21829,8 +23656,10 @@ components: type: string enum: - BLOOD + - DRY_BLOOD - SERA - STOOL + - THROAT_ASPIRATE - NASAL_SWAB - THROAT_SWAB - NP_SWAB @@ -21843,6 +23672,7 @@ components: - SALIVA - URINE_PM - NUCHAL_SKIN_BIOPSY + - BIOPSY - SPUTUM - ENDOTRACHEAL_ASPIRATE - BRONCHOALVEOLAR_LAVAGE @@ -21851,6 +23681,15 @@ components: - OP_ASPIRATE - NP_ASPIRATE - PLEURAL_FLUID + - NASOPHARYNGEAL_LAVAGE + - OROPHARYNGEAL_SWAB + - AMNIOTIC_FLUID + - CLINICAL_SAMPLE + - PERITONEAL_FLUID + - SYNOVIAL_FLUID + - EDTA_WHOLE_BLOOD + - INTESTINAL_FLUID + - DUODENUM_FLUID - OTHER sampleMaterialText: type: string @@ -21864,6 +23703,7 @@ components: - NEGATIVE - POSITIVE - NOT_DONE + - NOT_APPLICABLE sampleReceivedDate: type: string format: date-time @@ -22049,6 +23889,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -22060,6 +23901,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -22075,6 +23917,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -22261,6 +24107,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -22272,6 +24119,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -22287,6 +24135,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -22393,6 +24245,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -22404,6 +24257,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -22419,6 +24273,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -22745,6 +24603,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -22756,6 +24615,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -22771,6 +24631,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -22913,11 +24777,23 @@ components: - OWN_DETERMINATION - NOT_DETERMINABLE - NOT_RAISED + - PHONE_NOTIFICATION - OTHER reportingUser: $ref: '#/components/schemas/UserReferenceDto' sormasToSormasOriginInfo: $ref: '#/components/schemas/SormasToSormasOriginInfoDto' + treatmentNotApplicable: + type: boolean + treatmentStartDate: + type: string + format: date-time + treatmentStarted: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN uuid: type: string maxLength: 36 @@ -22936,6 +24812,16 @@ components: pattern: "^[0-9a-zA-Z-]*$" required: - uuid + SurveyReferenceDto: + type: object + properties: + caption: + type: string + uuid: + type: string + pattern: "^[0-9a-zA-Z-]*$" + required: + - uuid SymptomsDto: type: object properties: @@ -22951,115 +24837,169 @@ components: - "YES" - "NO" - UNKNOWN - acuteRespiratoryDistressSyndrome: + acuteBleeding: type: string enum: - "YES" - "NO" - UNKNOWN - aerophobia: + acuteEncephalitis: type: string enum: - "YES" - "NO" - UNKNOWN - agitation: + acuteKidneyFailure: type: string enum: - "YES" - "NO" - UNKNOWN - alteredConsciousness: + acuteRespiratoryDistressSyndrome: type: string enum: - "YES" - "NO" - UNKNOWN - anorexiaAppetiteLoss: + aerophobia: type: string enum: - "YES" - "NO" - UNKNOWN - anxietyStates: + agitation: type: string enum: - "YES" - "NO" - UNKNOWN - ascendingFlaccidParalysis: + alteredConsciousness: type: string enum: - "YES" - "NO" - UNKNOWN - backache: + alteredLevelOfConsciousness: type: string enum: - "YES" - "NO" - UNKNOWN - bedridden: + anemia: type: string enum: - "YES" - "NO" - UNKNOWN - bilateralCataracts: + anorexiaAppetiteLoss: type: string enum: - "YES" - "NO" - UNKNOWN - blackeningDeathOfTissue: + anxietyStates: type: string enum: - "YES" - "NO" - UNKNOWN - bleedingVagina: + apnoea: type: string enum: - "YES" - "NO" - UNKNOWN - bloodCirculationProblems: + arthritis: type: string enum: - "YES" - "NO" - UNKNOWN - bloodInStool: + ascendingFlaccidParalysis: type: string enum: - "YES" - "NO" - UNKNOWN - bloodPressureDiastolic: - type: integer - format: int32 - bloodPressureSystolic: - type: integer - format: int32 - bloodUrine: + asymptomatic: type: string enum: - "YES" - "NO" - UNKNOWN - bloodyBlackStool: + backache: type: string enum: - "YES" - "NO" - UNKNOWN - blueLips: + bedridden: type: string enum: - "YES" - "NO" - UNKNOWN - breathlessness: + bilateralCataracts: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + blackeningDeathOfTissue: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + bleedingVagina: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + bloating: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + bloodCirculationProblems: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + bloodInStool: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + bloodPressureDiastolic: + type: integer + format: int32 + bloodPressureSystolic: + type: integer + format: int32 + bloodUrine: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + bloodyBlackStool: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + blueLips: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + breathlessness: type: string enum: - "YES" @@ -23077,6 +25017,12 @@ components: - "YES" - "NO" - UNKNOWN + cerebralMalaria: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN changeDate: type: string format: date-time @@ -23098,6 +25044,34 @@ components: - "YES" - "NO" - UNKNOWN + clammySkin: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + clinicalManifestation: + type: string + enum: + - ASYMPTOMATIC + - NO_SIGN_OF_SEVERITY + - SIGN_OF_SEVERITY + - UNKNOWN + - OTHER + clinicalManifestationText: + type: string + clinicalPresentationStatus: + type: string + enum: + - ASYMPTOMATIC + - COMPATIBLE + - UNKNOWN + coldSkin: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN coma: type: string enum: @@ -23110,6 +25084,12 @@ components: - "YES" - "NO" - UNKNOWN + confusion: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN congenitalGlaucoma: type: string enum: @@ -23151,6 +25131,12 @@ components: - "YES" - "NO" - UNKNOWN + convulsions: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN cough: type: string enum: @@ -23181,6 +25167,18 @@ components: - "YES" - "NO" - UNKNOWN + coughingBouts: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + coughsProvokeVomiting: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN creationDate: type: string format: date-time @@ -23190,6 +25188,12 @@ components: - "YES" - "NO" - UNKNOWN + dateOfOnsetKnown: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN dehydration: type: string enum: @@ -23208,6 +25212,11 @@ components: - "YES" - "NO" - UNKNOWN + diagnosis: + type: string + enum: + - PULMONARY + - EXTRAPULMONARY diarrhea: type: string enum: @@ -23220,24 +25229,51 @@ components: - "YES" - "NO" - UNKNOWN + difficultyBreathingDuringMeals: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN digestedBloodVomit: type: string enum: - "YES" - "NO" - UNKNOWN + disseminatedIntraVascularCoagulation: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN dizzinessStandingUp: type: string enum: - "YES" - "NO" - UNKNOWN + durationOfSymptoms: + type: integer + format: int32 dysphagia: type: string enum: - "YES" - "NO" - UNKNOWN + eggyBurps: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + encephalitis: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN erraticBehaviour: type: string enum: @@ -23325,6 +25361,12 @@ components: glasgowComaScale: type: integer format: int32 + guillainBarreSyndrome: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN gumsBleeding: type: string enum: @@ -23349,6 +25391,12 @@ components: height: type: integer format: int32 + hemorrhagicRash: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN hemorrhagicSyndrome: type: string enum: @@ -23514,6 +25562,12 @@ components: - UNKNOWN lesionsThorax: type: boolean + lethargy: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN lossOfSmell: type: string enum: @@ -23556,6 +25610,25 @@ components: - "YES" - "NO" - UNKNOWN + majorSite: + type: string + enum: + - NOT_APPLICABLE + - BONES_JOINTS_OTHER_THAN_VERTEBRAE + - CNS_EXCEPT_MENINGES + - DISSEMINATED_FORM + - EXTRAPULMONARY_UNKNOWN_SITE + - EXTRA_THORACIC_LYMPH_NODES + - GENITO_URINARY + - INTRATHORACIC_LYMPH_NODES + - LUNG + - MENINGES + - PERITONEUM_DIGESTIVE_TRACT + - PLEURA + - UROGENITAL_SYSTEM + - VERTEBRAE + - UNKNOWN + - OTHER malaise: type: string enum: @@ -23568,12 +25641,24 @@ components: - "YES" - "NO" - UNKNOWN + meningitis: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN meningoencephalitis: type: string enum: - "YES" - "NO" - UNKNOWN + metabolicAcidosis: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN microcephaly: type: string enum: @@ -23583,6 +25668,25 @@ components: midUpperArmCircumference: type: integer format: int32 + minorSite: + type: string + enum: + - NOT_APPLICABLE + - BONES_JOINTS_OTHER_THAN_VERTEBRAE + - CNS_EXCEPT_MENINGES + - DISSEMINATED_FORM + - EXTRAPULMONARY_UNKNOWN_SITE + - EXTRA_THORACIC_LYMPH_NODES + - GENITO_URINARY + - INTRATHORACIC_LYMPH_NODES + - LUNG + - MENINGES + - PERITONEUM_DIGESTIVE_TRACT + - PLEURA + - UROGENITAL_SYSTEM + - VERTEBRAE + - UNKNOWN + - OTHER musclePain: type: string enum: @@ -23601,6 +25705,12 @@ components: - "YES" - "NO" - UNKNOWN + nocturnalCough: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN noseBleeding: type: string enum: @@ -23619,6 +25729,9 @@ components: - "YES" - "NO" - UNKNOWN + offsetDate: + type: string + format: date-time onsetDate: type: string format: date-time @@ -23638,6 +25751,14 @@ components: - "YES" - "NO" - UNKNOWN + otherClinicalPresentation: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + otherClinicalPresentationText: + type: string otherComplications: type: string enum: @@ -23658,6 +25779,18 @@ components: type: string maxLength: 512 minLength: 0 + otherMajorSiteDetails: + type: string + otherMinorSiteDetails: + type: string + otherNeurolocalSymptom: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + otherNeurolocalSymptomText: + type: string otherNonHemorrhagicSymptoms: type: string enum: @@ -23674,6 +25807,12 @@ components: - "YES" - "NO" - UNKNOWN + overnightStayRequired: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN oxygenSaturationLower94: type: string enum: @@ -23704,12 +25843,24 @@ components: - "YES" - "NO" - UNKNOWN + paradoxicalBreathing: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN paralysis: type: string enum: - "YES" - "NO" - UNKNOWN + parentTimeOffWork: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN paresis: type: string enum: @@ -23726,6 +25877,12 @@ components: type: string maxLength: 512 minLength: 0 + persistentVomiting: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN pharyngealErythema: type: string enum: @@ -23744,12 +25901,24 @@ components: - "YES" - "NO" - UNKNOWN + plasmaLeakageSign: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN pneumoniaClinicalOrRadiologic: type: string enum: - "YES" - "NO" - UNKNOWN + polydipsia: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN pseudonymized: type: boolean purpuricRash: @@ -23782,21 +25951,45 @@ components: - "YES" - "NO" - UNKNOWN + reoccurrence: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN respiratoryDiseaseVentilation: type: string enum: - "YES" - "NO" - UNKNOWN + respiratoryFatigue: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN respiratoryRate: type: integer format: int32 + restlessness: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN runnyNose: type: string enum: - "YES" - "NO" - UNKNOWN + scantHemorrhage: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN seizures: type: string enum: @@ -23809,6 +26002,24 @@ components: - "YES" - "NO" - UNKNOWN + septicaemia: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + severeAnemia: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + severeOrganImpairment: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN shivering: type: string enum: @@ -23839,6 +26050,9 @@ components: - "YES" - "NO" - UNKNOWN + skinRashOnsetDate: + type: string + format: date-time skinUlcers: type: string enum: @@ -23875,12 +26089,24 @@ components: - "YES" - "NO" - UNKNOWN + symptomCurrentStatus: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN symptomatic: type: boolean symptomsComments: type: string maxLength: 512 minLength: 0 + syndromicFlu: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN temperature: type: number format: float @@ -23897,6 +26123,9 @@ components: - "YES" - "NO" - UNKNOWN + timeOffWorkDays: + type: number + format: float tremor: type: string enum: @@ -23915,6 +26144,12 @@ components: - "YES" - "NO" - UNKNOWN + unknownSymptom: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN uproariousness: type: string enum: @@ -23947,12 +26182,27 @@ components: weight: type: integer format: int32 + weightLoss: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN + weightLossAmount: + type: number + format: float wheezing: type: string enum: - "YES" - "NO" - UNKNOWN + whoopSound: + type: string + enum: + - "YES" + - "NO" + - UNKNOWN TaskContextIndexCriteria: type: object properties: @@ -24266,6 +26516,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -24277,6 +26528,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -24292,6 +26544,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED district: @@ -24399,9 +26655,64 @@ components: TestReportDto: type: object properties: + amikacinMic: + type: number + format: float + amikacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + bedaquilineMic: + type: number + format: float + bedaquilineSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + capreomycinMic: + type: number + format: float + capreomycinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + ceftriaxoneMic: + type: number + format: float + ceftriaxoneSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN changeDate: type: string format: date-time + ciprofloxacinMic: + type: number + format: float + ciprofloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN cqValue: type: number format: float @@ -24429,6 +26740,39 @@ components: dateOfResult: type: string format: date-time + delamanidMic: + type: number + format: float + delamanidSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + erythromycinMic: + type: number + format: float + erythromycinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + ethambutolMic: + type: number + format: float + ethambutolSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN externalId: type: string maxLength: 512 @@ -24437,6 +26781,115 @@ components: type: string maxLength: 512 minLength: 0 + gatifloxacinMic: + type: number + format: float + gatifloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + genoType: + type: string + enum: + - GENOTYPE_A + - GENOTYPE_B + - GENOTYPE_B2 + - GENOTYPE_B3 + - GENOTYPE_C1 + - GENOTYPE_C2 + - GENOTYPE_D1 + - GENOTYPE_D10 + - GENOTYPE_D11 + - GENOTYPE_D2 + - GENOTYPE_D3 + - GENOTYPE_D4 + - GENOTYPE_D5 + - GENOTYPE_D6 + - GENOTYPE_D7 + - GENOTYPE_D8 + - GENOTYPE_D9 + - GENOTYPE_E + - GENOTYPE_F + - GENOTYPE_G1 + - GENOTYPE_G2 + - GENOTYPE_G3 + - GENOTYPE_H1 + - GENOTYPE_H2 + - CRYPTOSPORIDIUM_HOMINIS + - CRYPTOSPORIDIUM_PARVUM + - CRYPTOSPORIDIUM_SPECIES + - OTHER + - UNKNOWN + isoniazidMic: + type: number + format: float + isoniazidSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + kanamycinMic: + type: number + format: float + kanamycinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + levofloxacinMic: + type: number + format: float + levofloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + moxifloxacinMic: + type: number + format: float + moxifloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + ofloxacinMic: + type: number + format: float + ofloxacinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + penicillinMic: + type: number + format: float + penicillinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN preliminary: type: boolean prescriberAddress: @@ -24469,8 +26922,94 @@ components: type: string maxLength: 1000000 minLength: 0 + rifampicinMic: + type: number + format: float + rifampicinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN + rsvSubtype: + type: string + enum: + - RSV_A + - RSV_B + - RSV_A_AND_B + - INDETERMINATE sampleReport: $ref: '#/components/schemas/SampleReportReferenceDto' + seroGroupSpecification: + type: string + enum: + - SEROGROUP_A + - SEROGROUP_B + - SEROGROUP_C + - SEROGROUP_W + - SEROGROUP_X + - SEROGROUP_Y + - SEROGROUP_Z + - SEROGROUP_29E + - NOT_GROUPABLE + - NOT_UNDER_SURVEILLANCE + - OTHER + - UNKNOWN + seroGroupSpecificationText: + type: string + seroTypingMethod: + type: string + enum: + - MULTIPLEX_PCR + - QUELLUNG_REACTION + - COAGGLUTINATION + - GEL_DIFFUSION + - PNEUMOTEST + - SLIDE_AGGLUTINATION + - OTHER + seroTypingMethodText: + type: string + serotype: + type: string + specie: + type: string + enum: + - MYCOBATERIUM_AFRICANUM + - MYCOBATERIUM_BOVIS + - MYCOBATERIUM_TUBERCULOSIS + - OTHER_MTBC_MEMBER + - SPP + - FALCIPARUM + - VIVAX + - MALARIAE + - OVALE + - KNOWLESI + - CYNOMOLGI + - NOT_SPECIFIED + - COINFECTION + - OTHER + - UNKNOWN + - NOT_APPLICABLE + strainCallStatus: + type: string + enum: + - BEIJING + - NOBEIJING + - POSSBEIJING + - UNKNOWN + streptomycinMic: + type: number + format: float + streptomycinSusceptibility: + type: string + enum: + - NOT_APPLICABLE + - RESISTANT + - SUSCEPTIBLE + - INTERMEDIATE + - UNKNOWN testDateTime: type: string format: date-time @@ -24503,6 +27042,7 @@ components: - NEGATIVE - POSITIVE - NOT_DONE + - NOT_APPLICABLE testResultText: type: string maxLength: 1000000 @@ -24514,6 +27054,7 @@ components: enum: - ANTIBODY_DETECTION - ANTIGEN_DETECTION + - RAPID_ANTIGEN_DETECTION - RAPID_TEST - CULTURE - HISTOPATHOLOGY @@ -24526,6 +27067,7 @@ components: - DIRECT_FLUORESCENT_ANTIBODY - MICROSCOPY - NEUTRALIZING_ANTIBODIES + - ENZYME_LINKED_IMMUNOSORBENT_ASSAY - PCR_RT_PCR - GRAM_STAIN - LATEX_AGGLUTINATION @@ -24533,7 +27075,30 @@ components: - SEQUENCING - DNA_MICROARRAY - TMA + - IGRA + - TST + - BEIJINGGENOTYPING + - SPOLIGOTYPING + - MIRU_PATTERN_CODE + - ANTIBIOTIC_SUSCEPTIBILITY + - MULTILOCUS_SEQUENCE_TYPING + - SLIDE_AGGLUTINATION + - WHOLE_GENOME_SEQUENCING + - SEROGROUPING + - GENOTYPING + - NAAT + - THICK_BLOOD_SMEAR + - THIN_BLOOD_SMEAR + - Q_PCR + - LAMP + - OTHER_ANTIGEN_DETECTION_TEST + - OTHER_MOLECULAR_ASSAY + - OTHER_SEROLOGICAL_TEST - OTHER + testTypeDetails: + type: string + maxLength: 1000000 + minLength: 0 testedDiseaseVariant: type: string maxLength: 255 @@ -24542,6 +27107,26 @@ components: type: string maxLength: 255 minLength: 0 + tubeAgTb1: + type: number + format: float + tubeAgTb1GT10: + type: boolean + tubeAgTb2: + type: number + format: float + tubeAgTb2GT10: + type: boolean + tubeMitogene: + type: number + format: float + tubeMitogeneGT10: + type: boolean + tubeNil: + type: number + format: float + tubeNilGT10: + type: boolean typingId: type: string maxLength: 1000000 @@ -24556,12 +27141,18 @@ components: TherapyDto: type: object properties: + beijingLineage: + type: boolean changeDate: type: string format: date-time creationDate: type: string format: date-time + directlyObservedTreatment: + type: boolean + mdrXdrTuberculosis: + type: boolean uuid: type: string maxLength: 36 @@ -24688,6 +27279,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -24699,6 +27291,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -24714,6 +27307,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED diseaseDetails: @@ -24980,6 +27577,7 @@ components: enum: - ANTIMICROBIAL - ANTIVIRAL + - ANTIBIOTIC - OTHER uuid: type: string @@ -25034,6 +27632,7 @@ components: enum: - ANTIMICROBIAL - ANTIVIRAL + - ANTIBIOTIC - OTHER UserCriteria: type: object @@ -25069,6 +27668,8 @@ components: format: date-time district: $ref: '#/components/schemas/DistrictReferenceDto' + externalId: + type: string firstName: type: string maxLength: 512 @@ -25096,13 +27697,17 @@ components: enum: - EN - EN_NG + - EN_GM + - EN_LR - EN_GH + - EN_KE - EN_AF - FR - FR_CH - FR_TN - DE - DE_CH + - PT_CV - ES_BO - ES_EC - ES_CU @@ -25155,6 +27760,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -25166,6 +27772,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -25181,6 +27788,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED uniqueItems: true @@ -25387,6 +27998,12 @@ components: - IMMUNIZATION_DELETE - IMMUNIZATION_ARCHIVE - IMMUNIZATION_VIEW_ARCHIVED + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_CREATE + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EDIT + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE + - ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT - PERSON_VIEW - PERSON_EDIT - PERSON_DELETE @@ -25477,6 +28094,7 @@ components: - DASHBOARD_CONTACT_VIEW_TRANSMISSION_CHAINS - DASHBOARD_CAMPAIGNS_VIEW - DASHBOARD_SAMPLES_VIEW + - DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW - CASE_CLINICIAN_VIEW - THERAPY_VIEW - PRESCRIPTION_CREATE @@ -25527,6 +28145,7 @@ components: - ENVIRONMENT_DELETE - ENVIRONMENT_IMPORT - ENVIRONMENT_EXPORT + - ENVIRONMENT_LINK - ENVIRONMENT_SAMPLE_VIEW - ENVIRONMENT_SAMPLE_CREATE - ENVIRONMENT_SAMPLE_EDIT @@ -25565,10 +28184,23 @@ components: - SORMAS_TO_SORMAS_PROCESS - EXTERNAL_SURVEILLANCE_SHARE - EXTERNAL_SURVEILLANCE_DELETE - - EXTERNAL_MESSAGE_VIEW - - EXTERNAL_MESSAGE_PROCESS - EXTERNAL_MESSAGE_PUSH - - EXTERNAL_MESSAGE_DELETE + - EXTERNAL_MESSAGE_ACCESS + - EXTERNAL_MESSAGE_LABORATORY_VIEW + - EXTERNAL_MESSAGE_DOCTOR_DECLARATION_VIEW + - EXTERNAL_MESSAGE_LABORATORY_PROCESS + - EXTERNAL_MESSAGE_DOCTOR_DECLARATION_PROCESS + - EXTERNAL_MESSAGE_LABORATORY_DELETE + - EXTERNAL_MESSAGE_DOCTOR_DECLARATION_DELETE + - SURVEY_VIEW + - SURVEY_CREATE + - SURVEY_EDIT + - SURVEY_DELETE + - SURVEY_TOKEN_VIEW + - SURVEY_TOKEN_CREATE + - SURVEY_TOKEN_EDIT + - SURVEY_TOKEN_DELETE + - SURVEY_TOKEN_IMPORT - OUTBREAK_VIEW - OUTBREAK_EDIT - MANAGE_PUBLIC_EXPORT_CONFIGURATION @@ -25579,6 +28211,12 @@ components: - EXTERNAL_EMAIL_SEND - EXTERNAL_EMAIL_ATTACH_DOCUMENTS - CUSTOMIZABLE_ENUM_MANAGEMENT + - SYSTEM_CONFIGURATION + - DISEASE_MANAGEMENT + - EPIPULSE_EXPORT_VIEW + - EPIPULSE_EXPORT_CREATE + - EPIPULSE_EXPORT_DOWNLOAD + - EPIPULSE_EXPORT_DELETE uniqueItems: true uuid: type: string @@ -25673,12 +28311,21 @@ components: type: string enum: - BIONTECH_PFIZER + - PFIZER + - BAVARIAN_NORDIC - MODERNA - ASTRA_ZENECA - JOHNSON_JOHNSON + - KM_BIOLOGICS - NOVAVAX - SANOFI_GSK + - SANOFI_PASTEUR_BIOLOGICS - VALNEVA + - MERCK + - SANOFI_PASTEUR + - TAKEDA + - GSK + - OXFORD - UNKNOWN - OTHER vaccineName: @@ -25696,6 +28343,18 @@ components: - OXFORD_ASTRA_ZENECA - AD26_COV2_S - SANOFI_GSK + - MENABCWY + - ACAM2000 + - LC_16 + - PREVENAR_13_PFIZER + - VAXNEUVANCE_MERCK + - PREVNAR_20_PFIZER + - PNEUMOVAX_23_MERCK + - MVA_BN + - DENGVAXIA + - QDENGA + - RTS_S_AS01 + - R21_MATRIX_M - UNKNOWN - OTHER vaccineType: @@ -25762,6 +28421,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -25773,6 +28433,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -25788,6 +28449,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED inJurisdiction: @@ -25880,6 +28545,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -25891,6 +28557,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -25906,6 +28573,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED id: @@ -26051,6 +28722,7 @@ components: - SNAKE_BITE - RUBELLA - TUBERCULOSIS + - LATENT_TUBERCULOSIS - LEPROSY - LYMPHATIC_FILARIASIS - BURULI_ULCER @@ -26062,6 +28734,7 @@ components: - YAWS_ENDEMIC_SYPHILIS - MATERNAL_DEATHS - PERINATAL_DEATHS + - INFLUENZA - INFLUENZA_A - INFLUENZA_B - H_METAPNEUMOVIRUS @@ -26077,6 +28750,10 @@ components: - POST_IMMUNIZATION_ADVERSE_EVENTS_MILD - POST_IMMUNIZATION_ADVERSE_EVENTS_SEVERE - FHA + - INVASIVE_PNEUMOCOCCAL_INFECTION + - INVASIVE_MENINGOCOCCAL_INFECTION + - GIARDIASIS + - CRYPTOSPORIDIOSIS - OTHER - UNDEFINED numberOfCases: From 80a40474b756f9fde5fcacc7e1d6d6c378e7a9cf Mon Sep 17 00:00:00 2001 From: Karnaiah Pesula Date: Thu, 2 Apr 2026 14:53:33 +0200 Subject: [PATCH 34/55] Dengue review comments --- .../de/symeda/sormas/api/i18n/Captions.java | 1 - .../de/symeda/sormas/api/i18n/Strings.java | 1 + .../sormas/api/symptoms/SymptomsDto.java | 20 +++++-------------- .../src/main/resources/captions.properties | 9 ++++----- .../src/main/resources/strings.properties | 1 + .../sormas/backend/symptoms/Symptoms.java | 10 ---------- .../backend/symptoms/SymptomsFacadeEjb.java | 2 -- .../src/main/resources/sql/sormas_schema.sql | 2 -- .../symeda/sormas/ui/caze/CaseDataForm.java | 5 +++-- .../sormas/ui/symptoms/SymptomsForm.java | 2 -- 10 files changed, 14 insertions(+), 39 deletions(-) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java index 8771bcaa829..fdfe342a647 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java @@ -2943,7 +2943,6 @@ public interface Captions { String Symptoms_conjunctivalInjection = "Symptoms.conjunctivalInjection"; String Symptoms_conjunctivitis = "Symptoms.conjunctivitis"; String Symptoms_convulsion = "Symptoms.convulsion"; - String Symptoms_convulsions = "Symptoms.convulsions"; String Symptoms_cough = "Symptoms.cough"; String Symptoms_coughingBlood = "Symptoms.coughingBlood"; String Symptoms_coughingBouts = "Symptoms.coughingBouts"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java index 089ed27a5f3..faaa071f8ce 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java @@ -20,6 +20,7 @@ public interface Strings { String between = "between"; String bpm = "bpm"; String by = "by"; + String caseDefinitionForDisease = "caseDefinitionForDisease"; String checkboxSetTickAnAnswerForAll = "checkboxSetTickAnAnswerForAll"; String classificationAllOf = "classificationAllOf"; String classificationClassificationRules = "classificationClassificationRules"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/symptoms/SymptomsDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/symptoms/SymptomsDto.java index e87468a18cb..1a5721650d4 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/symptoms/SymptomsDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/symptoms/SymptomsDto.java @@ -352,7 +352,6 @@ public class SymptomsDto extends PseudonymizableDto { public static final String GUILLAIN_BARRE_SYNDROME = "guillainBarreSyndrome"; public static final String LETHARGY = "lethargy"; public static final String CONFUSION = "confusion"; - public static final String CONVULSIONS = "convulsions"; public static final String PERSISTENT_VOMITING = "persistentVomiting"; public static final String RESTLESSNESS = "restlessness"; public static final String ACUTE_BLEEDING = "acuteBleeding"; @@ -483,9 +482,12 @@ public static SymptomsDto build() { POLIO, YELLOW_FEVER, ANTHRAX, + DENGUE, UNSPECIFIED_VHF, UNDEFINED, OTHER }) + @Complication(value = { + DENGUE }) @HideForCountries @SymptomGrouping(SymptomGroup.GASTROINTESTINAL) private SymptomState bloodInStool; @@ -2197,6 +2199,8 @@ public static SymptomsDto build() { @Diseases({ ANTHRAX, DENGUE }) + @Complication({ + DENGUE }) @HideForCountries @SymptomGrouping(SymptomGroup.NERVOUS_SYSTEM) private SymptomState convulsion; @@ -2922,12 +2926,6 @@ public static SymptomsDto build() { private SymptomState confusion; - @Diseases({ - DENGUE }) - @Complication({ - DENGUE }) - private SymptomState convulsions; - @Diseases({ DENGUE }) @SymptomGrouping(SymptomGroup.OTHER) @@ -5042,14 +5040,6 @@ public void setConfusion(SymptomState confusion) { this.confusion = confusion; } - public SymptomState getConvulsions() { - return convulsions; - } - - public void setConvulsions(SymptomState convulsions) { - this.convulsions = convulsions; - } - public SymptomState getPersistentVomiting() { return persistentVomiting; } diff --git a/sormas-api/src/main/resources/captions.properties b/sormas-api/src/main/resources/captions.properties index 66ede9cd756..530db5b810b 100644 --- a/sormas-api/src/main/resources/captions.properties +++ b/sormas-api/src/main/resources/captions.properties @@ -2829,7 +2829,7 @@ Symptoms.fever=Fever Symptoms.firstSymptom=First symptom Symptoms.fluidInLungCavity=Fluid in the lung cavity Symptoms.glasgowComaScale=Glasgow coma scale -Symptoms.gumsBleeding=Bleeding of the gums +Symptoms.gumsBleeding=Gums bleeding Symptoms.headache=Headache Symptoms.hearingloss=Acute hearing loss Symptoms.heartRate=Heart rate (bpm) @@ -2935,7 +2935,7 @@ Symptoms.tremor=Tremor Symptoms.unexplainedBleeding=Bleeding or bruising Symptoms.unilateralCataracts=Unilateral cataracts Symptoms.vomiting=Vomiting -Symptoms.convulsion=Convulsion +Symptoms.convulsion=Convulsions Symptoms.weight=Weight (kg) Symptoms.hydrophobia=Hydrophobia Symptoms.opisthotonus=Opisthotonus @@ -3022,7 +3022,6 @@ Symptoms.encephalitis=Encephalitis Symptoms.guillainBarreSyndrome=Guillain-barré syndrome Symptoms.lethargy=Lethargy Symptoms.confusion=Confusion -Symptoms.convulsions=Convulsions Symptoms.persistentVomiting=Persistent vomiting Symptoms.restlessness=Restlessness Symptoms.acuteBleeding=Acute bleeding @@ -3042,8 +3041,8 @@ Symptoms.clinicalManifestation= Clinical manifestation of the disease according Symptoms.clinicalManifestationText=Specify clinical manifestation Symptoms.disseminatedIntraVascularCoagulation=Disseminated intra-vascular coagulation Symptoms.scantHemorrhage=Other minor bleeding -Symptoms.otherNeurolocalSymptom=Other neurological symptom -Symptoms.otherNeurolocalSymptomText= Specify other neurological symptom +Symptoms.otherNeurolocalSymptom=Neurological symptom +Symptoms.otherNeurolocalSymptomText= Specify neurological symptom titleComplications=Complications titleNoComplications=No complications diff --git a/sormas-api/src/main/resources/strings.properties b/sormas-api/src/main/resources/strings.properties index 3de2dff1759..dafce9e2314 100644 --- a/sormas-api/src/main/resources/strings.properties +++ b/sormas-api/src/main/resources/strings.properties @@ -116,6 +116,7 @@ classificationPersonAged = Person aged classificationNotACase = Not a case Classification classificationProbable = Probable Classification classificationRulesFor = Classification Rules For +caseDefinitionForDisease = Case definition for disease classificationSymptomsAllOf = All symptoms classificationSymptomsAnyOf = Any symptom classificationSuspect = Possible Classification diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/symptoms/Symptoms.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/symptoms/Symptoms.java index cb7ab9052d0..4adb0957736 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/symptoms/Symptoms.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/symptoms/Symptoms.java @@ -289,7 +289,6 @@ public class Symptoms extends AbstractDomainObject { private SymptomState guillainBarreSyndrome; private SymptomState lethargy; private SymptomState confusion; - private SymptomState convulsions; private SymptomState persistentVomiting; private SymptomState restlessness; private SymptomState acuteBleeding; @@ -2279,15 +2278,6 @@ public void setConfusion(SymptomState confusion) { this.confusion = confusion; } - @Enumerated(EnumType.STRING) - public SymptomState getConvulsions() { - return convulsions; - } - - public void setConvulsions(SymptomState convulsions) { - this.convulsions = convulsions; - } - @Enumerated(EnumType.STRING) public SymptomState getPersistentVomiting() { return persistentVomiting; diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/symptoms/SymptomsFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/symptoms/SymptomsFacadeEjb.java index 6f2bfd65ef9..a29713a5705 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/symptoms/SymptomsFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/symptoms/SymptomsFacadeEjb.java @@ -254,7 +254,6 @@ public Symptoms fillOrBuildEntity(SymptomsDto source, Symptoms target, boolean c target.setGuillainBarreSyndrome(source.getGuillainBarreSyndrome()); target.setLethargy(source.getLethargy()); target.setConfusion(source.getConfusion()); - target.setConvulsions(source.getConvulsions()); target.setPersistentVomiting(source.getPersistentVomiting()); target.setRestlessness(source.getRestlessness()); target.setAcuteBleeding(source.getAcuteBleeding()); @@ -511,7 +510,6 @@ public static SymptomsDto toSymptomsDto(Symptoms symptoms) { target.setGuillainBarreSyndrome(source.getGuillainBarreSyndrome()); target.setLethargy(source.getLethargy()); target.setConfusion(source.getConfusion()); - target.setConvulsions(source.getConvulsions()); target.setPersistentVomiting(source.getPersistentVomiting()); target.setRestlessness(source.getRestlessness()); target.setAcuteBleeding(source.getAcuteBleeding()); diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 5cd6e52d1dc..ffaf301fbc4 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15308,7 +15308,6 @@ ALTER TABLE symptoms ADD COLUMN IF NOT EXISTS coldskin character varying(255); ALTER TABLE symptoms ADD COLUMN IF NOT EXISTS encephalitis character varying(255); ALTER TABLE symptoms ADD COLUMN IF NOT EXISTS guillainbarresyndrome character varying(255); ALTER TABLE symptoms ADD COLUMN IF NOT EXISTS confusion character varying(255); -ALTER TABLE symptoms ADD COLUMN IF NOT EXISTS convulsions character varying(255); ALTER TABLE symptoms ADD COLUMN IF NOT EXISTS persistentvomiting character varying(255); ALTER TABLE symptoms ADD COLUMN IF NOT EXISTS restlessness character varying(255); ALTER TABLE symptoms ADD COLUMN IF NOT EXISTS acutebleeding character varying(255); @@ -15337,7 +15336,6 @@ ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS coldskin character varying ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS encephalitis character varying(255); ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS guillainbarresyndrome character varying(255); ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS confusion character varying(255); -ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS convulsions character varying(255); ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS persistentvomiting character varying(255); ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS restlessness character varying(255); ALTER TABLE symptoms_history ADD COLUMN IF NOT EXISTS acutebleeding character varying(255); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java index 953fd9b9c9f..16c549d30a5 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java @@ -1113,7 +1113,8 @@ protected void addFields() { FieldVisibilityCheckers.withDisease(disease) .add(new CountryFieldVisibilityChecker(FacadeProvider.getConfigFacade().getCountryLocale())), UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale()), - new PersonReferenceDto(person.getUuid()))).setCaption(null); + new PersonReferenceDto(person.getUuid()))) + .setCaption(null); //diagnosis criteria if ((FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) && disease == Disease.TUBERCULOSIS) { @@ -1562,7 +1563,7 @@ private void getManualCaseDefinition() { }); popupWindow.setWidth(860, Unit.PIXELS); popupWindow.setHeight(80, Unit.PERCENTAGE); - popupWindow.setCaption(I18nProperties.getString(Strings.classificationRulesFor) + " " + disease); + popupWindow.setCaption(I18nProperties.getString(Strings.caseDefinitionForDisease) + " " + disease); }, ValoTheme.BUTTON_PRIMARY, FORCE_CAPTION); getContent().addComponent(caseDefinitionButton, CLASSIFICATION_RULES_LOC); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/symptoms/SymptomsForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/symptoms/SymptomsForm.java index 524866f4332..be4c51e3561 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/symptoms/SymptomsForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/symptoms/SymptomsForm.java @@ -530,7 +530,6 @@ public String getFormattedHtmlMessage() { GUILLAIN_BARRE_SYNDROME, LETHARGY, CONFUSION, - CONVULSIONS, PERSISTENT_VOMITING, RESTLESSNESS, ACUTE_BLEEDING, @@ -865,7 +864,6 @@ public String getFormattedHtmlMessage() { GUILLAIN_BARRE_SYNDROME, LETHARGY, CONFUSION, - CONVULSIONS, PERSISTENT_VOMITING, RESTLESSNESS, SEVERE_ORGAN_IMPAIRMENT, From 4d4fa600221a98a09ab5d6b26e815b87470b4bee Mon Sep 17 00:00:00 2001 From: raulbob Date: Tue, 7 Apr 2026 09:18:25 +0200 Subject: [PATCH 35/55] Updated app ci action Updated YAML syntax for GitHub Actions workflow, including enabling KVM, fixing quotes, and adjusting cache keys. --- .github/workflows/sormas_app_ci.yml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/sormas_app_ci.yml b/.github/workflows/sormas_app_ci.yml index 23828c0f625..196ff37de23 100644 --- a/.github/workflows/sormas_app_ci.yml +++ b/.github/workflows/sormas_app_ci.yml @@ -8,8 +8,7 @@ name: Java CI with Gradle env: JAVA: 17 - PRIVILEGED_RUN: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/development') - || github.event.pull_request.head.repo.full_name == github.repository }} + PRIVILEGED_RUN: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/development') || github.event.pull_request.head.repo.full_name == github.repository }} on: push: @@ -36,6 +35,11 @@ jobs: api-level: [26, 27, 28] steps: + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules && sudo udevadm trigger --name-match=kvm + - name: Checkout repository (with token) # Check if PR results from the repository: if yes, we have access to the secrets. # The token is only needed for privileged actions from within the repo, so no need @@ -56,7 +60,7 @@ jobs: uses: actions/setup-java@v5 with: java-version: ${{ env.JAVA }} - distribution: 'zulu' + distribution: "zulu" - name: Cache Maven packages # Check if PR results from the repository: if yes, it is safe to cache dependencies. @@ -99,13 +103,14 @@ jobs: path: | ~/.android/avd/* ~/.android/adb* - key: avd-${{ matrix.api-level }} - + key: avd-${{ matrix.api-level }}-x86_64 + - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} + arch: x86_64 force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: false @@ -115,20 +120,20 @@ jobs: uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} + arch: x86_64 force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true working-directory: ./sormas-app script: ./gradlew connectedAndroidTest - - name: mobsfscan uses: MobSF/mobsfscan@main with: - args: '. --sarif --output mobsf-results.sarif || true' + args: ". --sarif --output mobsf-results.sarif || true" - name: Upload mobsfscan report uses: github/codeql-action/upload-sarif@v4 with: - sarif_file: 'mobsf-results.sarif' + sarif_file: "mobsf-results.sarif" # needed as codeQL also performs an upload, and they clash otherwise - category: 'code-scanning/mobsfscan' + category: "code-scanning/mobsfscan" From 5ece351c5a4e1cad846242b253a19423fc4c347c Mon Sep 17 00:00:00 2001 From: sormas-robot Date: Tue, 7 Apr 2026 07:34:08 +0000 Subject: [PATCH 36/55] [GitHub Actions] Update openAPI spec files --- sormas-rest/swagger.json | 4 ---- sormas-rest/swagger.yaml | 6 ------ 2 files changed, 10 deletions(-) diff --git a/sormas-rest/swagger.json b/sormas-rest/swagger.json index e61129e240a..bf51239ef11 100644 --- a/sormas-rest/swagger.json +++ b/sormas-rest/swagger.json @@ -24482,10 +24482,6 @@ "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] }, - "convulsions" : { - "type" : "string", - "enum" : [ "YES", "NO", "UNKNOWN" ] - }, "cough" : { "type" : "string", "enum" : [ "YES", "NO", "UNKNOWN" ] diff --git a/sormas-rest/swagger.yaml b/sormas-rest/swagger.yaml index 7401e28ffbf..1124e80888b 100644 --- a/sormas-rest/swagger.yaml +++ b/sormas-rest/swagger.yaml @@ -25131,12 +25131,6 @@ components: - "YES" - "NO" - UNKNOWN - convulsions: - type: string - enum: - - "YES" - - "NO" - - UNKNOWN cough: type: string enum: From 15651a18c7b6605f65cc846de2516672e8d16240 Mon Sep 17 00:00:00 2001 From: raulbob Date: Tue, 7 Apr 2026 12:19:03 +0200 Subject: [PATCH 37/55] Update sql schema with TestReport renamed columns (#13901) * Update sql schema with TestReport renamed columns * Fixed remarks in sql schema --- .../src/main/resources/sql/sormas_schema.sql | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index ffaf301fbc4..b42e00d89fa 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15648,6 +15648,37 @@ ALTER TABLE healthconditions_history ADD COLUMN IF NOT EXISTS malariainfectedyea INSERT INTO schema_version (version_number, comment) VALUES (615, '#13801, #13814 - Malaria and Dengue sampel changes'); +-- 07-04-2026 Test report new fields related to Malaria and Dengue samples and pathogenform. +ALTER TABLE testreport ADD COLUMN IF NOT EXISTS serotypetext varchar(255); +UPDATE testreport SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotyperesult') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotype') THEN + ALTER TABLE testreport RENAME COLUMN genotyperesult TO genotype; + END IF; + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotyperesulttext') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotypetext') THEN + ALTER TABLE testreport RENAME COLUMN genotyperesulttext TO genotypetext; + END IF; +END $$; + +ALTER TABLE testreport_history ADD COLUMN IF NOT EXISTS serotypetext varchar(255); +UPDATE testreport_history SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotyperesult') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotype') THEN + ALTER TABLE testreport_history RENAME COLUMN genotyperesult TO genotype; + END IF; + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotyperesulttext') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotypetext') THEN + ALTER TABLE testreport_history RENAME COLUMN genotyperesulttext TO genotypetext; + END IF; +END $$; + +INSERT INTO schema_version (version_number, comment) VALUES (616, '#13801, #13814 - Malaria and Dengue TestReport columns renames'); + -- #13828 - Customizable Fields CREATE TABLE IF NOT EXISTS customizablefieldmetadata ( @@ -15786,6 +15817,6 @@ CREATE TRIGGER delete_history_trigger_customizablefieldvalue ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; -INSERT INTO schema_version (version_number, comment) VALUES (616, '#13828 - Add history tables for customizable fields'); +INSERT INTO schema_version (version_number, comment) VALUES (617, '#13828 - Add history tables for customizable fields'); --- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** +-- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** \ No newline at end of file From 78a8e45133956742f799d118f8d4b7f9b9eb798b Mon Sep 17 00:00:00 2001 From: raulbob Date: Tue, 7 Apr 2026 11:23:54 +0200 Subject: [PATCH 38/55] Updated java ci to avoid unnecessary commits for swagger files Add check to avoid committing if no changes are detected. --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8299f611e0..c6594f0a27c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,5 +116,9 @@ jobs: cp /tmp/openapi/swagger.* sormas-rest/ git add sormas-rest/swagger.* - git commit -m "[GitHub Actions] Update openAPI spec files" - git push + if ! git diff --cached --quiet; then + git commit -m "[GitHub Actions] Update openAPI spec files" + git push + else + echo "swagger files are already up to date, nothing to commit" + fi From 239d8c351fc25c19a1812dab4f409d64b9ff5fed Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Tue, 17 Mar 2026 00:38:37 +0100 Subject: [PATCH 39/55] #13883 - Fixed IGRA inputs value change listeners - Refactored duplicated listeners for IGRA inputs into two listeners - Fixed error caused by locale conversions with IGRA numeric inputs --- .../sormas/ui/samples/PathogenTestForm.java | 503 +++++++----------- 1 file changed, 201 insertions(+), 302 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index 7b08d74d525..00bda64dad0 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -31,6 +31,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -41,7 +42,11 @@ import org.slf4j.LoggerFactory; import com.vaadin.ui.Label; +import com.vaadin.v7.data.Property; +import com.vaadin.v7.data.fieldgroup.BeanFieldGroup; +import com.vaadin.v7.data.util.BeanItem; import com.vaadin.v7.data.util.converter.Converter; +import com.vaadin.v7.data.util.converter.ConverterUtil; import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; import com.vaadin.v7.ui.CheckBox; import com.vaadin.v7.ui.ComboBox; @@ -657,317 +662,25 @@ protected void addFields() { addFields( FieldConfiguration.builder(PathogenTestDto.TUBE_NIL) .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(e -> { - final String tubeNilFieldValue = (String) e.getProperty().getValue(); - final NullableOptionGroup tubeNilGt10Field = getField(PathogenTestDto.TUBE_NIL_GT10); - final Float tubeNilValue = getValue().getTubeNil(); - final Boolean tubeNilGt10Value = getValue().getTubeNilGT10(); - - // we are called for a new entry - if(tubeNilValue == null - && tubeNilGt10Value == null - && tubeNilFieldValue == null - && tubeNilGt10Field.getNullableValue() == null) { - tubeNilGt10Field.select(false); - return; - } - - if(tubeNilFieldValue == null) { - tubeNilGt10Field.select(false); - return; - } - Float tubeNilNewValue = null; - try { - tubeNilNewValue = Float.parseFloat(tubeNilFieldValue); - } catch (NumberFormatException ex) { - // if it is not a number we clear the field - getField(PathogenTestDto.TUBE_NIL).clear(); - tubeNilGt10Field.select(false); - return; - } - // now we have a current and old value - if(tubeNilNewValue > 10) { - tubeNilGt10Field.select(true); - } else { - tubeNilGt10Field.select(false); - } - }) + .valueChangeListener(new TuberculosisIGRAInputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_NIL,PathogenTestDto.TUBE_NIL_GT10)) .build(), FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1) .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(e -> { - final String tubeAgTb1FieldValue = (String) e.getProperty().getValue(); - final NullableOptionGroup tubeAgTb1Gt10Field = getField(PathogenTestDto.TUBE_AG_TB1_GT10); - final Float tubeAgTb1Value = getValue().getTubeAgTb1(); - final Boolean tubeAgTb1Gt10Value = getValue().getTubeAgTb1GT10(); - - // we are called for a new entry - if(tubeAgTb1Value == null - && tubeAgTb1Gt10Value == null - && tubeAgTb1FieldValue == null - && tubeAgTb1Gt10Field.getNullableValue() == null) { - tubeAgTb1Gt10Field.select(false); - return; - } - - if(tubeAgTb1FieldValue == null) { - tubeAgTb1Gt10Field.select(false); - return; - } - Float tubeAgTb1NewValue = null; - try { - tubeAgTb1NewValue = Float.parseFloat(tubeAgTb1FieldValue); - } catch (NumberFormatException ex) { - // if it is not a number we clear the field - getField(PathogenTestDto.TUBE_AG_TB1).clear(); - tubeAgTb1Gt10Field.select(false); - return; - } - // now we have a current and old value - if(tubeAgTb1NewValue > 10) { - tubeAgTb1Gt10Field.select(true); - } else { - tubeAgTb1Gt10Field.select(false); - } - }) - .build(), + .valueChangeListener(new TuberculosisIGRAInputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_AG_TB1,PathogenTestDto.TUBE_AG_TB1_GT10)).build(), FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2) .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(e -> { - final String tubeAgTb2FieldValue = (String) e.getProperty().getValue(); - final NullableOptionGroup tubeAgTb2Gt10Field = getField(PathogenTestDto.TUBE_AG_TB2_GT10); - final Float tubeAgTb2Value = getValue().getTubeAgTb2(); - final Boolean tubeAgTb2Gt10Value = getValue().getTubeAgTb2GT10(); - - // we are called for a new entry - if(tubeAgTb2Value == null - && tubeAgTb2Gt10Value == null - && tubeAgTb2FieldValue == null - && tubeAgTb2Gt10Field.getNullableValue() == null) { - tubeAgTb2Gt10Field.select(false); - return; - } - - if(tubeAgTb2FieldValue == null) { - tubeAgTb2Gt10Field.select(false); - return; - } - Float tubeAgTb2NewValue = null; - try { - tubeAgTb2NewValue = Float.parseFloat(tubeAgTb2FieldValue); - } catch (NumberFormatException ex) { - // if it is not a number we clear the field - getField(PathogenTestDto.TUBE_AG_TB2).clear(); - tubeAgTb2Gt10Field.select(false); - return; - } - // now we have a current and old value - if(tubeAgTb2NewValue > 10) { - tubeAgTb2Gt10Field.select(true); - } else { - tubeAgTb2Gt10Field.select(false); - } - }) - .build(), + .valueChangeListener(new TuberculosisIGRAInputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_AG_TB2,PathogenTestDto.TUBE_AG_TB2_GT10)).build(), FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE) .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(e -> { - final String tubeMitogeneFieldValue = (String) e.getProperty().getValue(); - final NullableOptionGroup tubeMitogeneGt10Field = getField(PathogenTestDto.TUBE_MITOGENE_GT10); - final Float tubeMitogeneValue = getValue().getTubeMitogene(); - final Boolean tubeMitogeneGt10Value = getValue().getTubeMitogeneGT10(); - - // we are called for a new entry - if(tubeMitogeneValue == null - && tubeMitogeneGt10Value == null - && tubeMitogeneFieldValue == null - && tubeMitogeneGt10Field.getNullableValue() == null) { - tubeMitogeneGt10Field.select(false); - return; - } - - if(tubeMitogeneFieldValue == null) { - tubeMitogeneGt10Field.select(false); - return; - } - Float tubeMitogeneNewValue = null; - try { - tubeMitogeneNewValue = Float.parseFloat(tubeMitogeneFieldValue); - } catch (NumberFormatException ex) { - // if it is not a number we clear the field - getField(PathogenTestDto.TUBE_MITOGENE).clear(); - tubeMitogeneGt10Field.select(false); - return; - } - // now we have a current and old value - if(tubeMitogeneNewValue > 10) { - tubeMitogeneGt10Field.select(true); - } else { - tubeMitogeneGt10Field.select(false); - } - }) - .build()); + .valueChangeListener(new TuberculosisIGRAInputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_MITOGENE,PathogenTestDto.TUBE_MITOGENE_GT10)).build()); //@formatter:on //@formatter:off addFields( - FieldConfiguration.builder(PathogenTestDto.TUBE_NIL_GT10).valueChangeListener(event -> { - final Object propertySingleValue = event.getProperty().getValue() instanceof Collection - ? ((Collection) event.getProperty().getValue()).stream().findFirst().orElse(null) - : event.getProperty().getValue(); - final Float tubeNilValue = getValue().getTubeNil(); - - // we are called for a new entry or initial calls - if(propertySingleValue == null && tubeNilValue == null) { - final NullableOptionGroup tubeNilGt10Field = getField(PathogenTestDto.TUBE_NIL_GT10); - tubeNilGt10Field.select(false); - return; - } - final boolean checked = Boolean.TRUE.equals(propertySingleValue); - final Field tubeNilField = getField(PathogenTestDto.TUBE_NIL); - - final String tubeNilFieldValue = (String) tubeNilField.getValue(); - if(tubeNilFieldValue == null) { - // if there is no value we don't care about the checkbox value - return; - } - Float tubeNilNewValue = null; - try { - tubeNilNewValue = Float.valueOf(tubeNilFieldValue); - } catch (NumberFormatException ex) { - // if it's not a number we don't care about the value - tubeNilField.clear(); - return; - } - // if the checkbox is checked and the value is less than 10, we clear the field - if (checked && tubeNilNewValue < 10) { - tubeNilField.clear(); - return; - } - // if the checkbox is unchecked and the value is greater than or equal to 10, we clear the field - if(!checked && tubeNilNewValue >= 10) { - tubeNilField.clear(); - return; - } - }).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1_GT10).valueChangeListener(event -> { - final Object propertySingleValue = event.getProperty().getValue() instanceof Collection - ? ((Collection) event.getProperty().getValue()).stream().findFirst().orElse(null) - : event.getProperty().getValue(); - final Float tubeAgTb1Value = getValue().getTubeAgTb1(); - - // we are called for a new entry or initial calls - if(propertySingleValue == null && tubeAgTb1Value == null) { - final NullableOptionGroup tubeAgTb1Gt10Field = getField(PathogenTestDto.TUBE_AG_TB1_GT10); - tubeAgTb1Gt10Field.select(false); - return; - } - final boolean checked = Boolean.TRUE.equals(propertySingleValue); - final Field tubeAgTb1Field = getField(PathogenTestDto.TUBE_AG_TB1); - - final String tubeAgTb1FieldValue = (String) tubeAgTb1Field.getValue(); - if(tubeAgTb1FieldValue == null) { - // if there is no value we don't care about the checkbox value - return; - } - Float tubeAgTb1NewValue = null; - try { - tubeAgTb1NewValue = Float.valueOf(tubeAgTb1FieldValue); - } catch (NumberFormatException ex) { - // if it's not a number we don't care about the value - tubeAgTb1Field.clear(); - return; - } - // if the checkbox is checked and the value is less than or equal to 10, we clear the field - if (checked && tubeAgTb1NewValue <= 10) { - tubeAgTb1Field.clear(); - return; - } - // if the checkbox is unchecked and the value is greater than 10, we clear the field - if(!checked && tubeAgTb1NewValue > 10) { - tubeAgTb1Field.clear(); - return; - } - }).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2_GT10).valueChangeListener(event -> { - final Object propertySingleValue = event.getProperty().getValue() instanceof Collection - ? ((Collection) event.getProperty().getValue()).stream().findFirst().orElse(null) - : event.getProperty().getValue(); - final Float tubeAgTb2Value = getValue().getTubeAgTb2(); - - // we are called for a new entry or initial calls - if(propertySingleValue == null && tubeAgTb2Value == null) { - final NullableOptionGroup tubeAgTb2Gt10Field = getField(PathogenTestDto.TUBE_AG_TB2_GT10); - tubeAgTb2Gt10Field.select(false); - return; - } - final boolean checked = Boolean.TRUE.equals(propertySingleValue); - final Field tubeAgTb2Field = getField(PathogenTestDto.TUBE_AG_TB2); - - final String tubeAgTb2FieldValue = (String) tubeAgTb2Field.getValue(); - if(tubeAgTb2FieldValue == null) { - // if there is no value we don't care about the checkbox value - return; - } - Float tubeAgTb2NewValue = null; - try { - tubeAgTb2NewValue = Float.valueOf(tubeAgTb2FieldValue); - } catch (NumberFormatException ex) { - // if it's not a number we don't care about the value - tubeAgTb2Field.clear(); - return; - } - // if the checkbox is checked and the value is less than or equal to 10, we clear the field - if (checked && tubeAgTb2NewValue <= 10) { - tubeAgTb2Field.clear(); - return; - } - // if the checkbox is unchecked and the value is greater than 10, we clear the field - if(!checked && tubeAgTb2NewValue > 10) { - tubeAgTb2Field.clear(); - return; - } - }).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE_GT10).valueChangeListener(event -> { - final Object propertySingleValue = event.getProperty().getValue() instanceof Collection - ? ((Collection) event.getProperty().getValue()).stream().findFirst().orElse(null) - : event.getProperty().getValue(); - final Float tubeMitogeneValue = getValue().getTubeMitogene(); - - // we are called for a new entry or initial calls - if(propertySingleValue == null && tubeMitogeneValue == null) { - final NullableOptionGroup tubeMitogeneGt10Field = getField(PathogenTestDto.TUBE_MITOGENE_GT10); - tubeMitogeneGt10Field.select(false); - return; - } - final boolean checked = Boolean.TRUE.equals(propertySingleValue); - final Field tubeMitogeneField = getField(PathogenTestDto.TUBE_MITOGENE); - - final String tubeMitogeneFieldValue = (String) tubeMitogeneField.getValue(); - if(tubeMitogeneFieldValue == null) { - // if there is no value we don't care about the checkbox value - return; - } - Float tubeMitogeneNewValue = null; - try { - tubeMitogeneNewValue = Float.valueOf(tubeMitogeneFieldValue); - } catch (NumberFormatException ex) { - // if it's not a number we don't care about the value - tubeMitogeneField.clear(); - return; - } - // if the checkbox is checked and the value is less than or equal to 10, we clear the field - if (checked && tubeMitogeneNewValue <= 10) { - tubeMitogeneField.clear(); - return; - } - // if the checkbox is unchecked and the value is greater than 10, we clear the field - if(!checked && tubeMitogeneNewValue > 10) { - tubeMitogeneField.clear(); - return; - } - }).build() - ); + FieldConfiguration.builder(PathogenTestDto.TUBE_NIL_GT10).valueChangeListener(new TuberculosisIGRAGT10InputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_NIL_GT10,PathogenTestDto.TUBE_NIL)).build(), + FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1_GT10).valueChangeListener(new TuberculosisIGRAGT10InputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_AG_TB1_GT10,PathogenTestDto.TUBE_AG_TB1)).build(), + FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2_GT10).valueChangeListener(new TuberculosisIGRAGT10InputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_AG_TB2_GT10,PathogenTestDto.TUBE_AG_TB2)).build(), + FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE_GT10).valueChangeListener(new TuberculosisIGRAGT10InputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_MITOGENE_GT10,PathogenTestDto.TUBE_MITOGENE)).build()); //@formatter:on setVisibleClear( @@ -1314,11 +1027,197 @@ protected void addFields() { || isVisibleAllowed(PathogenTestDto.PRESCRIBER_COUNTRY)); } - static class TestTypeValueChangeListener implements ValueChangeListener { + /** + * This class is to be used for the Tuberculosis IGRA input value change listeners. + * It will check/uncheck the Tuberculosis IGRA greater than 10 checkbox dependiong on the value of the input field. + *

+ * Note: ideally a custom component should be used for both fields, to avoid potential race conditions between the two listeners. + */ + protected static class TuberculosisIGRAInputValueChangeListener implements ValueChangeListener { + + private final String igraInputFieldId; + private final String igraGT10FieldId; + private final BeanFieldGroup fieldGroup; + + public TuberculosisIGRAInputValueChangeListener(BeanFieldGroup fg, String igraInputFieldId, String igraGT10FieldId) { + this.igraInputFieldId = igraInputFieldId; + this.igraGT10FieldId = igraGT10FieldId; + this.fieldGroup = fg; + } + + @Override + public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { + + final Field igraInputField = fieldGroup.getField(igraInputFieldId); + + if (igraInputField == null) { + return; + } + + final BeanItem beanItemDataSource = fieldGroup.getItemDataSource(); + + // the input field is always a TextField with a String as value + // we need to make a hard assumtion that the input field value is a Float + + // we check to see if the model property is numeric + // the model at this point will not be updated, so we only check type + final Property igraValueProp = beanItemDataSource.getItemProperty(igraInputFieldId); + + if (!Number.class.isAssignableFrom(igraValueProp.getType())) { + // we will not deal with non-numeric values + return; + } + + // we know that the model property is numeric + // we could get the original value with: igraValueProp.getValue(); + + // we need to convert the value to number + // and we need to do it locale aware and need to finagle with types + @SuppressWarnings({ + "unchecked", + "rawtypes" }) + final Number igraNewValue = igraInputField.getValue() == null + ? null + : (Number) ConverterUtil.getConverter(igraInputField.getType(), (Class) igraValueProp.getType(), null /* current session */) + .convertToModel(igraInputField.getValue(), igraValueProp.getType(), null /* current locale */); + + final Boolean checked = igraNewValue == null ? null : igraNewValue.floatValue() > 10; + + // now we need to set the value of the GT10 field + @SuppressWarnings("unchecked") + final Field igraGT10Field = (Field) fieldGroup.getField(igraGT10FieldId); + if (igraGT10Field == null) { + // if we can't find the field, we don't care + return; + } + + // lets make sure the property is a boolean + final Property igraGT10Prop = beanItemDataSource.getItemProperty(igraGT10FieldId); + if (igraGT10Prop == null || !Boolean.class.isAssignableFrom(igraGT10Prop.getType())) { + // if we can't find the property, or we can't set it we don't care + return; + } + + // now field is supposed to be a boolean + // booleans come in two flavors: collection based and primitive + final boolean isCollection = Collection.class.isAssignableFrom(igraGT10Field.getType()); + + if (!isCollection) { + // primitive booleans are easy + final boolean currentChecked = Boolean.TRUE.equals(igraGT10Field.getValue()); + if (checked != null && checked != currentChecked) { + igraGT10Field.setValue(checked); + } + } else { + // well have to do it the hard way + final Collection currentSet = (Collection) igraGT10Field.getValue(); + final boolean currentChecked = currentSet != null && !currentSet.isEmpty() && currentSet.contains(Boolean.TRUE); + if (checked != null && checked != currentChecked) { + final HashSet set = new HashSet<>(); + set.add(checked); + igraGT10Field.setValue(set); + } + } + } + } + + /** + * This class is to be used for the Tuberculosis IGRA greater than 10 checkboxes value change listeners. + * It will clear the associated input field if the checkbox is checked and the + * value is less than or equal to 10. + * In reverse if the value is greater than 10 and the checkbox is not checked it will clear the input field. + *

+ * Note: ideally a custom component should be used for both fields, to avoid potential race conditions between the two listeners. + */ + protected static class TuberculosisIGRAGT10InputValueChangeListener implements ValueChangeListener { + + private final String igraInputFieldId; + private final String igraGT10FieldId; + private final BeanFieldGroup fieldGroup; + + public TuberculosisIGRAGT10InputValueChangeListener(BeanFieldGroup fg, String igraGT10FieldId, String igraInputFieldId) { + this.igraInputFieldId = igraInputFieldId; + this.igraGT10FieldId = igraGT10FieldId; + this.fieldGroup = fg; + } @Override public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { - // TODO Auto-generated method stub + final BeanItem beanItemDataSource = fieldGroup.getItemDataSource(); + + final Property igraValueProp = beanItemDataSource.getItemProperty(igraInputFieldId); + if (igraValueProp == null || !Number.class.isAssignableFrom(igraValueProp.getType())) { + return; + } + + // lets make sure the GT10 property is a boolean + final Property igraGT10Prop = beanItemDataSource.getItemProperty(igraGT10FieldId); + if (igraGT10Prop == null || !Boolean.class.isAssignableFrom(igraGT10Prop.getType())) { + // if we can't find the property, or we can't set it we don't care + return; + } + + // let's try to get the numeric input field and converted value + final Field igraInputField = fieldGroup.getField(igraInputFieldId); + if (igraInputField == null) { + return; + } + + @SuppressWarnings({ + "unchecked", + "rawtypes" }) + final Number igraNewValue = igraInputField.getValue() == null + ? null + : (Number) ConverterUtil.getConverter(igraInputField.getType(), (Class) igraValueProp.getType(), null /* current session */) + .convertToModel(igraInputField.getValue(), igraValueProp.getType(), null /* current locale */); + + // now let's try to determine if the checkbox is checked (we know it's a boolean) + @SuppressWarnings("unchecked") + final Field igraGT10Field = (Field) fieldGroup.getField(igraGT10FieldId); + if (igraGT10Field == null) { + // if we can't find the field, we don't care + return; + } + + // booleans come in two flavors: collection based and primitive + final boolean isCollection = Collection.class.isAssignableFrom(igraGT10Field.getType()); + + Boolean checked = false; + + // value can be true or false/null(presumed false) + if (!isCollection) { + // primitive booleans are easy + checked = igraGT10Field.getValue() == null ? null : Boolean.TRUE.equals(igraGT10Field.getValue()); + } else { + Collection set = (Collection) igraGT10Field.getValue(); + checked = set == null || set.isEmpty() ? null : set.contains(Boolean.TRUE); + } + + if (checked == null) { // the checbox is neither checked nor unchecked + checked = igraNewValue != null && igraNewValue.floatValue() > 10; + + if (!isCollection) { + // primitive booleans are easy + igraGT10Field.setValue(checked); + } else { + final HashSet set = new HashSet<>(); + set.add(checked); + igraGT10Field.setValue(set); + } + + // don't need to clear anything else because there was no check/uncheck before + return; + } + + if ((checked && igraNewValue != null && igraNewValue.floatValue() <= 10) // checked but value is filled in and less than 10 + || (!checked && igraNewValue != null && igraNewValue.floatValue() > 10) // not checked but value is filled in an greater than 10 + ) { + try { + igraInputField.clear(); + } catch (ReadOnlyException ex) { + // ignore read-only + } + } } } From 75ccd8c13913aafd107f68115f07026817925323 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Tue, 17 Mar 2026 01:50:11 +0100 Subject: [PATCH 40/55] Added guards against number conversion errors --- .../sormas/ui/samples/PathogenTestForm.java | 78 +++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index 00bda64dad0..dee28842868 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -41,11 +41,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.vaadin.server.UserError; +import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.Label; import com.vaadin.v7.data.Property; import com.vaadin.v7.data.fieldgroup.BeanFieldGroup; import com.vaadin.v7.data.util.BeanItem; import com.vaadin.v7.data.util.converter.Converter; +import com.vaadin.v7.data.util.converter.Converter.ConversionException; import com.vaadin.v7.data.util.converter.ConverterUtil; import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; import com.vaadin.v7.ui.CheckBox; @@ -65,6 +68,7 @@ import de.symeda.sormas.api.environment.environmentsample.Pathogen; import de.symeda.sormas.api.i18n.Captions; import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Strings; import de.symeda.sormas.api.i18n.Validations; import de.symeda.sormas.api.infrastructure.facility.FacilityDto; import de.symeda.sormas.api.infrastructure.facility.FacilityReferenceDto; @@ -1045,6 +1049,9 @@ public TuberculosisIGRAInputValueChangeListener(BeanFieldGroup this.fieldGroup = fg; } + @SuppressWarnings({ + "unchecked", + "rawtypes" }) @Override public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { @@ -1054,6 +1061,10 @@ public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { return; } + if (igraInputField instanceof AbstractComponent) { + ((AbstractComponent) igraInputField).setComponentError(null); + } + final BeanItem beanItemDataSource = fieldGroup.getItemDataSource(); // the input field is always a TextField with a String as value @@ -1073,13 +1084,21 @@ public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { // we need to convert the value to number // and we need to do it locale aware and need to finagle with types - @SuppressWarnings({ - "unchecked", - "rawtypes" }) - final Number igraNewValue = igraInputField.getValue() == null - ? null - : (Number) ConverterUtil.getConverter(igraInputField.getType(), (Class) igraValueProp.getType(), null /* current session */) - .convertToModel(igraInputField.getValue(), igraValueProp.getType(), null /* current locale */); + + Number igraNewValue = null; + + try { + igraNewValue = igraInputField.getValue() == null + ? null + : (Number) ConverterUtil + .getConverter(igraInputField.getType(), (Class) igraValueProp.getType(), null /* current session */) + .convertToModel(igraInputField.getValue(), igraValueProp.getType(), igraInputField.getLocale()); + } catch (ConversionException e) { + if (igraInputField instanceof AbstractComponent) { + ((AbstractComponent) igraInputField).setComponentError(new UserError(I18nProperties.getString(Strings.errorInvalidValue))); + } + return; + } final Boolean checked = igraNewValue == null ? null : igraNewValue.floatValue() > 10; @@ -1105,17 +1124,17 @@ public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { if (!isCollection) { // primitive booleans are easy final boolean currentChecked = Boolean.TRUE.equals(igraGT10Field.getValue()); - if (checked != null && checked != currentChecked) { + if (checked != null && checked.booleanValue() != currentChecked) { igraGT10Field.setValue(checked); } } else { // well have to do it the hard way final Collection currentSet = (Collection) igraGT10Field.getValue(); final boolean currentChecked = currentSet != null && !currentSet.isEmpty() && currentSet.contains(Boolean.TRUE); - if (checked != null && checked != currentChecked) { + if (checked != null && checked.booleanValue() != currentChecked) { final HashSet set = new HashSet<>(); set.add(checked); - igraGT10Field.setValue(set); + igraGT10Field.setValue(Collections.unmodifiableSet(set)); } } } @@ -1141,8 +1160,22 @@ public TuberculosisIGRAGT10InputValueChangeListener(BeanFieldGroup igraInputField = fieldGroup.getField(igraInputFieldId); + if (igraInputField == null) { + return; + } + + if (igraInputField instanceof AbstractComponent) { + ((AbstractComponent) igraInputField).setComponentError(null); + } + final BeanItem beanItemDataSource = fieldGroup.getItemDataSource(); final Property igraValueProp = beanItemDataSource.getItemProperty(igraInputFieldId); @@ -1157,20 +1190,21 @@ public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { return; } - // let's try to get the numeric input field and converted value - final Field igraInputField = fieldGroup.getField(igraInputFieldId); - if (igraInputField == null) { + Number igraNewValue = null; + + try { + igraNewValue = igraInputField.getValue() == null + ? null + : (Number) ConverterUtil + .getConverter(igraInputField.getType(), (Class) igraValueProp.getType(), null /* current session */) + .convertToModel(igraInputField.getValue(), igraValueProp.getType(), igraInputField.getLocale() /* current locale */); + } catch (ConversionException e) { + if (igraInputField instanceof AbstractComponent) { + ((AbstractComponent) igraInputField).setComponentError(new UserError(I18nProperties.getString(Strings.errorInvalidValue))); + } return; } - @SuppressWarnings({ - "unchecked", - "rawtypes" }) - final Number igraNewValue = igraInputField.getValue() == null - ? null - : (Number) ConverterUtil.getConverter(igraInputField.getType(), (Class) igraValueProp.getType(), null /* current session */) - .convertToModel(igraInputField.getValue(), igraValueProp.getType(), null /* current locale */); - // now let's try to determine if the checkbox is checked (we know it's a boolean) @SuppressWarnings("unchecked") final Field igraGT10Field = (Field) fieldGroup.getField(igraGT10FieldId); @@ -1202,7 +1236,7 @@ public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { } else { final HashSet set = new HashSet<>(); set.add(checked); - igraGT10Field.setValue(set); + igraGT10Field.setValue(Collections.unmodifiableSet(set)); } // don't need to clear anything else because there was no check/uncheck before From b6b733b057651caecc0d3c978ed82602376d41e2 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Tue, 17 Mar 2026 09:19:26 +0100 Subject: [PATCH 41/55] Removed full type reference --- .../java/de/symeda/sormas/ui/samples/PathogenTestForm.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index dee28842868..eb028305102 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -1053,7 +1053,7 @@ public TuberculosisIGRAInputValueChangeListener(BeanFieldGroup "unchecked", "rawtypes" }) @Override - public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { + public void valueChange(Property.ValueChangeEvent event) { final Field igraInputField = fieldGroup.getField(igraInputFieldId); @@ -1164,7 +1164,7 @@ public TuberculosisIGRAGT10InputValueChangeListener(BeanFieldGroup igraInputField = fieldGroup.getField(igraInputFieldId); From 26bf66c974d04ea32cbd9275c1ff1854ba028947 Mon Sep 17 00:00:00 2001 From: Obinna Henry <55580796+obinna-h-n@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:31:24 +0100 Subject: [PATCH 42/55] implement ui for new exposure form exposures changes --- .../sormas/api/exposure/ExposureCategory.java | 10 + .../api/exposure/ExposureSubSetting.java | 33 ++- .../de/symeda/sormas/api/i18n/Captions.java | 13 + .../de/symeda/sormas/api/i18n/Strings.java | 1 + .../src/main/resources/captions.properties | 15 ++ .../src/main/resources/strings.properties | 1 + .../sormas/ui/exposure/ExposureForm.java | 230 ++++++++++++++++++ 7 files changed, 297 insertions(+), 6 deletions(-) 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 index 01a6ff2b7cb..9fcd3ce4e8b 100644 --- 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 @@ -15,6 +15,8 @@ package de.symeda.sormas.api.exposure; +import java.util.EnumSet; + import de.symeda.sormas.api.i18n.I18nProperties; public enum ExposureCategory { @@ -28,6 +30,14 @@ public enum ExposureCategory { VERTICAL_TRANSMISSION, WATER_BORNE; + public boolean hasNoSetting() { + return EnumSet.of(ANIMAL_CONTACT, FOMITE_TRANSMISSION, FOOD_BORNE).contains(this); + } + + public boolean hasNoSubSetting() { + return EnumSet.of(ANIMAL_CONTACT, FOMITE_TRANSMISSION).contains(this); + } + @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 index 2678517adf1..92a89025fa9 100644 --- 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 @@ -65,13 +65,15 @@ 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) { + if (category == null) { + return Collections.emptyList(); + } + + if (category.hasNoSubSetting()) { + return Collections.emptyList(); + } + if (setting == null) { return Collections.emptyList(); } boolean hasSpecific = Arrays.stream(values()).anyMatch(s -> s.category == category && s.setting == setting); @@ -83,6 +85,25 @@ public static List getValues(ExposureCategory category, Expo .collect(Collectors.toList()); } + public static List getValuesForCategoryOnly(ExposureCategory category) { + if (category == null) { + return Collections.emptyList(); + } + + if (category.hasNoSubSetting()) { + return Collections.emptyList(); + } + + // Check if this category has subsettings with null setting + boolean hasCategoryOnlySubSettings = Arrays.stream(values()).anyMatch(s -> s.category == category && s.setting == null); + + if (!hasCategoryOnlySubSettings) { + return Collections.emptyList(); + } + + return Arrays.stream(values()).filter(s -> (s.category == category && s.setting == null) || 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/i18n/Captions.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java index fdfe342a647..f518fce3992 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java @@ -1827,6 +1827,8 @@ public interface Captions { String exportSormasData = "exportSormasData"; String exportUserRoles = "exportUserRoles"; String Exposure = "Exposure"; + String Exposure_animalCategory = "Exposure.animalCategory"; + String Exposure_animalCategoryDetails = "Exposure.animalCategoryDetails"; String Exposure_animalCondition = "Exposure.animalCondition"; String Exposure_animalContactType = "Exposure.animalContactType"; String Exposure_animalContactTypeDetails = "Exposure.animalContactTypeDetails"; @@ -1837,6 +1839,8 @@ public interface Captions { String Exposure_bodyOfWater = "Exposure.bodyOfWater"; String Exposure_childcareFacilityDetails = "Exposure.childcareFacilityDetails"; String Exposure_connectionNumber = "Exposure.connectionNumber"; + String Exposure_contactFactorDetails = "Exposure.contactFactorDetails"; + String Exposure_contactFactors = "Exposure.contactFactors"; String Exposure_contactToBodyFluids = "Exposure.contactToBodyFluids"; String Exposure_contactToCase = "Exposure.contactToCase"; String Exposure_deceasedPersonIll = "Exposure.deceasedPersonIll"; @@ -1845,10 +1849,16 @@ public interface Captions { String Exposure_domesticSwimming = "Exposure.domesticSwimming"; String Exposure_eatingRawAnimalProducts = "Exposure.eatingRawAnimalProducts"; String Exposure_endDate = "Exposure.endDate"; + String Exposure_exposureCategory = "Exposure.exposureCategory"; + String Exposure_exposureComment = "Exposure.exposureComment"; String Exposure_exposureDate = "Exposure.exposureDate"; String Exposure_exposureRole = "Exposure.exposureRole"; + String Exposure_exposureSetting = "Exposure.exposureSetting"; + String Exposure_exposureSettingDetails = "Exposure.exposureSettingDetails"; + String Exposure_exposureSubSettingDetails = "Exposure.exposureSubSettingDetails"; String Exposure_exposureType = "Exposure.exposureType"; String Exposure_exposureTypeDetails = "Exposure.exposureTypeDetails"; + String Exposure_fomiteTransmissionLocation = "Exposure.fomiteTransmissionLocation"; String Exposure_gatheringDetails = "Exposure.gatheringDetails"; String Exposure_gatheringType = "Exposure.gatheringType"; String Exposure_habitationDetails = "Exposure.habitationDetails"; @@ -1872,6 +1882,8 @@ public interface Captions { String Exposure_probableInfectionEnvironment = "Exposure.probableInfectionEnvironment"; String Exposure_prophylaxis = "Exposure.prophylaxis"; String Exposure_prophylaxisDate = "Exposure.prophylaxisDate"; + String Exposure_protectiveMeasureDetails = "Exposure.protectiveMeasureDetails"; + String Exposure_protectiveMeasures = "Exposure.protectiveMeasures"; String Exposure_protectiveMeasuresDetails = "Exposure.protectiveMeasuresDetails"; String Exposure_rawFoodContact = "Exposure.rawFoodContact"; String Exposure_rawFoodContactText = "Exposure.rawFoodContactText"; @@ -1880,6 +1892,7 @@ public interface Captions { String Exposure_sexualExposureText = "Exposure.sexualExposureText"; String Exposure_shortDistance = "Exposure.shortDistance"; String Exposure_startDate = "Exposure.startDate"; + String Exposure_subSettings = "Exposure.subSettings"; String Exposure_swimmingLocation = "Exposure.swimmingLocation"; String Exposure_swimmingLocationType = "Exposure.swimmingLocationType"; String Exposure_symptomaticIndividualText = "Exposure.symptomaticIndividualText"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java index faaa071f8ce..4d87c7ee71a 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java @@ -675,6 +675,7 @@ public interface Strings { String headingExportUserRightsFailed = "headingExportUserRightsFailed"; String headingExposureDetails = "headingExposureDetails"; String headingExposureInvestigation = "headingExposureInvestigation"; + String headingExposures = "headingExposures"; String headingExtendFollowUp = "headingExtendFollowUp"; String headingExtendQuarantine = "headingExtendQuarantine"; String headingExternalEmailDetails = "headingExternalEmailDetails"; diff --git a/sormas-api/src/main/resources/captions.properties b/sormas-api/src/main/resources/captions.properties index 530db5b810b..4439f3b6702 100644 --- a/sormas-api/src/main/resources/captions.properties +++ b/sormas-api/src/main/resources/captions.properties @@ -1588,6 +1588,21 @@ Exposure.sexualExposureText= Specify sexual exposure Exposure.rawFoodContact= Contact with raw pet food Exposure.rawFoodContactText= Specify contact with raw pet food Exposure.symptomaticIndividualText= Specify contact with symptomatic individual + +Exposure.exposureCategory=Exposure category +Exposure.exposureSetting=Exposure setting +Exposure.exposureSettingDetails=Other setting details +Exposure.subSettings=Sub-settings +Exposure.exposureSubSettingDetails=Other sub-settings details +Exposure.animalCategory=Animal category +Exposure.animalCategoryDetails=Details of the animal category +Exposure.fomiteTransmissionLocation=Fomite transmission location +Exposure.contactFactors=Contact factors +Exposure.contactFactorDetails=Other contact factors details +Exposure.protectiveMeasures=Protective measures +Exposure.protectiveMeasureDetails=Other protective measures details +Exposure.exposureComment=Comment + # Facility facilityActiveFacilities=Active facilities facilityArchivedFacilities=Archived facilities diff --git a/sormas-api/src/main/resources/strings.properties b/sormas-api/src/main/resources/strings.properties index dafce9e2314..a36ff061d83 100644 --- a/sormas-api/src/main/resources/strings.properties +++ b/sormas-api/src/main/resources/strings.properties @@ -832,6 +832,7 @@ headingAdjustQuarantine = Adjust quarantine headingExtendFollowUp = Extend follow-up period headingExposureInvestigation = Exposure Investigation headingEpiDataSourceCaseContacts = Contacts with Source Case +headingExposures = Exposures headingExposureDetails = Exposure Details headingEpiConclusion = Conclusion headingClusterType = Cluster Type diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java index 8dc05fb31ce..890cf22859b 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java @@ -26,13 +26,16 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Set; import com.vaadin.icons.VaadinIcons; import com.vaadin.shared.ui.ContentMode; import com.vaadin.ui.Label; import com.vaadin.ui.themes.ValoTheme; import com.vaadin.v7.data.util.converter.Converter; +import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.OptionGroup; import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; @@ -46,7 +49,12 @@ import de.symeda.sormas.api.event.TypeOfPlace; 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.ExposureDto; +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.GatheringType; import de.symeda.sormas.api.exposure.HabitationType; @@ -74,6 +82,7 @@ public class ExposureForm extends AbstractEditForm { private static final long serialVersionUID = 8262753698264714832L; + private static final String LOC_EXPOSURES_HEADING = "locExposuresHeading"; private static final String LOC_EXPOSURE_DETAILS_HEADING = "locExposureDetailsHeading"; private static final String LOC_LOCATION_HEADING = "locLocationHeading"; private static final String LOC_ANIMAL_CONTACT_DETAILS_HEADING = "locAnimalContactDetailsHeading"; @@ -84,6 +93,35 @@ public class ExposureForm extends AbstractEditForm { private static final String UUID_REPORTING_USER = fluidRowLocs(ExposureDto.UUID, ExposureDto.REPORTING_USER); private static final String OTHER_STANDARD_FIELDS = fluidRowLocs(ExposureDto.START_DATE, ExposureDto.END_DATE) + + loc(LOC_EXPOSURES_HEADING) + + fluidRowLocs(ExposureDto.EXPOSURE_CATEGORY, null, null) + + fluidRow( + fluidColumn(4, 0, locs( + ExposureDto.EXPOSURE_SETTING, + ExposureDto.SUB_SETTINGS, + ExposureDto.ANIMAL_CATEGORY, + ExposureDto.FOMITE_TRANSMISSION_LOCATION + )), + fluidColumn(4, 0, locs( + ExposureDto.CONTACT_FACTORS + )), + fluidColumn(4, 0, locs( + ExposureDto.PROTECTIVE_MEASURES + )) + ) + + fluidRow( + fluidColumn(4, 0, locs( + ExposureDto.EXPOSURE_SETTING_DETAILS, + ExposureDto.EXPOSURE_SUB_SETTING_DETAILS, + ExposureDto.ANIMAL_CATEGORY_DETAILS + )), + fluidColumn(4, 0, locs( + ExposureDto.CONTACT_FACTOR_DETAILS + )), + fluidColumn(4, 0, locs( + ExposureDto.PROTECTIVE_MEASURE_DETAILS + )) + ) + loc(ExposureDto.DESCRIPTION) + fluidRow( fluidColumnLoc(6, 0, ExposureDto.EXPOSURE_TYPE), @@ -158,6 +196,20 @@ public class ExposureForm extends AbstractEditForm { private LocationEditForm locationForm; private Disease disease; + private ComboBox categoryField; + private ComboBox settingField; + private TextField settingDetailsField; + private OptionGroup subSettingsField; + private TextField subSettingsDetailsField; + private OptionGroup contactFactorsField; + private TextField contactFactorDetailsField; + private OptionGroup protectiveMeasuresField; + private TextField protectiveMeasureDetailsField; + private NullableOptionGroup animalConditionField; + private NullableOptionGroup animalCategoryField; + private TextField animalCategoryDetailsField; + private NullableOptionGroup fomiteTransmissionLocationField; + public ExposureForm( boolean create, Class epiDataParentClass, @@ -214,6 +266,8 @@ protected void addFields() { } private void addHeadingsAndInfoTexts() { + getContent().addComponent(new Label(h3(I18nProperties.getString(Strings.headingExposures)), ContentMode.HTML), LOC_EXPOSURES_HEADING); + getContent() .addComponent(new Label(h3(I18nProperties.getString(Strings.headingExposureDetails)), ContentMode.HTML), LOC_EXPOSURE_DETAILS_HEADING); @@ -239,6 +293,92 @@ private void addBasicFields() { DateComparisonValidator.addStartEndValidators(startDate, endDate, false); + List categories = Arrays.asList(ExposureCategory.values()); + categoryField = addField(ExposureDto.EXPOSURE_CATEGORY, ComboBox.class); + categoryField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); + categoryField.setRequired(true); + FieldHelper.updateItems(categoryField, categories); + + settingField = addField(ExposureDto.EXPOSURE_SETTING, ComboBox.class); + settingField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); + settingField.setRequired(true); + + settingDetailsField = addField(ExposureDto.EXPOSURE_SETTING_DETAILS, TextField.class); + settingDetailsField.setVisible(false); + + subSettingsField = addField(ExposureDto.SUB_SETTINGS, OptionGroup.class); + subSettingsField.setMultiSelect(true); + CssStyles.style(subSettingsField, CssStyles.CAPTION_ON_TOP); + + subSettingsDetailsField = addField(ExposureDto.EXPOSURE_SUB_SETTING_DETAILS, TextField.class); + subSettingsDetailsField.setVisible(false); + + //animalConditionField = addField(ExposureDto.ANIMAL_CONDITION, NullableOptionGroup.class); + animalCategoryField = addField(ExposureDto.ANIMAL_CATEGORY, NullableOptionGroup.class); + + contactFactorsField = addField(ExposureDto.CONTACT_FACTORS, OptionGroup.class); + contactFactorsField.setMultiSelect(true); + CssStyles.style(contactFactorsField, CssStyles.CAPTION_ON_TOP); + + contactFactorDetailsField = addField(ExposureDto.CONTACT_FACTOR_DETAILS, TextField.class); + contactFactorDetailsField.setVisible(false); + + protectiveMeasuresField = addField(ExposureDto.PROTECTIVE_MEASURES, OptionGroup.class); + protectiveMeasuresField.setMultiSelect(true); + CssStyles.style(protectiveMeasuresField, CssStyles.CAPTION_ON_TOP); + + protectiveMeasureDetailsField = addField(ExposureDto.PROTECTIVE_MEASURE_DETAILS, TextField.class); + protectiveMeasureDetailsField.setVisible(false); + + categoryField.addValueChangeListener(e -> { + ExposureCategory selectedCategory = (ExposureCategory) e.getProperty().getValue(); + updateSettingFieldItems(selectedCategory); + + // Also update subSettings when category changes (setting will be null/cleared) + updateSubSettingsFieldItems(selectedCategory, (ExposureSetting) settingField.getValue()); + + // Update contact factors and protective measures when category changes + updateContactFactorsFieldItems(selectedCategory, (ExposureSetting) settingField.getValue()); + updateProtectiveMeasuresFieldItems(selectedCategory, (ExposureSetting) settingField.getValue()); + }); + + settingField.addValueChangeListener(e -> { + ExposureSetting selectedSetting = (ExposureSetting) e.getProperty().getValue(); + + // 1. Show/hide settingDetailsField if setting is OTHER + settingDetailsField.setVisible(selectedSetting == ExposureSetting.OTHER); + + // 2. Update subSettings based on category and setting + ExposureCategory selectedCategory = (ExposureCategory) categoryField.getValue(); + updateSubSettingsFieldItems(selectedCategory, selectedSetting); + + // 3. Update contact factors and protective measures based on category and setting + updateContactFactorsFieldItems(selectedCategory, selectedSetting); + updateProtectiveMeasuresFieldItems(selectedCategory, selectedSetting); + }); + + subSettingsField.addValueChangeListener(e -> { + // 3. Show/hide subSettingsDetailsField if subSettings contains OTHER + @SuppressWarnings("unchecked") + Set selectedSubSettings = (Set) e.getProperty().getValue(); + boolean containsOther = selectedSubSettings != null && selectedSubSettings.contains(ExposureSubSetting.OTHER); + subSettingsDetailsField.setVisible(containsOther); + }); + + contactFactorsField.addValueChangeListener(e -> { + @SuppressWarnings("unchecked") + Set selectedContactFactors = (Set) e.getProperty().getValue(); + boolean containsOther = selectedContactFactors != null && selectedContactFactors.contains(ExposureContactFactor.OTHER); + contactFactorDetailsField.setVisible(containsOther); + }); + + protectiveMeasuresField.addValueChangeListener(e -> { + @SuppressWarnings("unchecked") + Set selectedProtectiveMeasures = (Set) e.getProperty().getValue(); + boolean containsOther = selectedProtectiveMeasures != null && selectedProtectiveMeasures.contains(ExposureProtectiveMeasure.OTHER); + protectiveMeasureDetailsField.setVisible(containsOther); + }); + addFields( ExposureDto.EXPOSURE_TYPE, ExposureDto.EXPOSURE_TYPE_DETAILS, @@ -430,6 +570,74 @@ private void setUpRequirements() { Collections.singletonList(ExposureType.OTHER)); } + private void updateSettingFieldItems(ExposureCategory category) { + List settings = ExposureSetting.getValues(category); + FieldHelper.updateItems(settingField, settings); + + // Clear the field and its dependent details field + settingField.setValue(null); + 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); + } + } + } + + private void updateSubSettingsFieldItems(ExposureCategory category, ExposureSetting setting) { + List subSettings; + + // For categories that have no setting but do have subsettings (e.g., FOOD_BORNE), + // we need to get subsettings based only on category + if (category != null && category.hasNoSetting()) { + subSettings = ExposureSubSetting.getValuesForCategoryOnly(category); + } else { + subSettings = ExposureSubSetting.getValues(category, setting); + } + + FieldHelper.updateItems(subSettingsField, subSettings); + + // Clear the field and its dependent details field + subSettingsField.setValue(null); + subSettingsDetailsField.setValue(null); + subSettingsDetailsField.setVisible(false); + + // Hide subSettings field if no options available + subSettingsField.setVisible(!subSettings.isEmpty()); + } + + private void updateContactFactorsFieldItems(ExposureCategory category, ExposureSetting setting) { + List contactFactors = ExposureContactFactor.getValues(category, setting); + FieldHelper.updateItems(contactFactorsField, contactFactors); + + // Clear the field and its dependent details field + contactFactorsField.setValue(null); + contactFactorDetailsField.setValue(null); + contactFactorDetailsField.setVisible(false); + + // Hide contactFactors field if no options available + contactFactorsField.setVisible(!contactFactors.isEmpty()); + } + + private void updateProtectiveMeasuresFieldItems(ExposureCategory category, ExposureSetting setting) { + List protectiveMeasures = ExposureProtectiveMeasure.getValues(category, setting); + FieldHelper.updateItems(protectiveMeasuresField, protectiveMeasures); + + // Clear the field and its dependent details field + protectiveMeasuresField.setValue(null); + protectiveMeasureDetailsField.setValue(null); + protectiveMeasureDetailsField.setVisible(false); + + // Hide protectiveMeasures field if no options available + protectiveMeasuresField.setVisible(!protectiveMeasures.isEmpty()); + } + @Override public void setValue(ExposureDto newFieldValue) throws ReadOnlyException, Converter.ConversionException { super.setValue(newFieldValue); @@ -445,6 +653,28 @@ public void setValue(ExposureDto newFieldValue) throws ReadOnlyException, Conver .forEach(i -> cbContactToCase.setItemCaption(i, ((ContactReferenceDto) i).getCaptionAlwaysWithUuid())); } + if (newFieldValue != null) { + ExposureCategory category = newFieldValue.getExposureCategory(); + ExposureSetting setting = newFieldValue.getExposureSetting(); + + updateSettingFieldItems(category); + updateSubSettingsFieldItems(category, setting); + updateContactFactorsFieldItems(category, setting); + updateProtectiveMeasuresFieldItems(category, setting); + + // Initialize visibility for details fields + settingDetailsField.setVisible(setting == ExposureSetting.OTHER); + + Set subSettings = newFieldValue.getSubSettings(); + subSettingsDetailsField.setVisible(subSettings != null && subSettings.contains(ExposureSubSetting.OTHER)); + + Set contactFactors = newFieldValue.getContactFactors(); + contactFactorDetailsField.setVisible(contactFactors != null && contactFactors.contains(ExposureContactFactor.OTHER)); + + Set protectiveMeasures = newFieldValue.getProtectiveMeasures(); + protectiveMeasureDetailsField.setVisible(protectiveMeasures != null && protectiveMeasures.contains(ExposureProtectiveMeasure.OTHER)); + } + // HACK: Binding to the fields will call field listeners that may clear/modify the values of other fields. // this hopefully resets everything to its correct value locationForm.discard(); From 9655302b83009b80b237a01fd1ae527600c10ffa Mon Sep 17 00:00:00 2001 From: Obinna Henry <55580796+obinna-h-n@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:32:40 +0100 Subject: [PATCH 43/55] implement ui for disease specific configuration of exposure user interface --- .../api/disease/DiseaseConfigurationDto.java | 13 + .../disease/DiseaseConfigurationIndexDto.java | 10 + .../sormas/api/exposure/ExposureDto.java | 1 + .../de/symeda/sormas/api/i18n/Captions.java | 6 + .../de/symeda/sormas/api/i18n/Strings.java | 1 + .../src/main/resources/captions.properties | 9 +- sormas-api/src/main/resources/enum.properties | 8 + .../src/main/resources/strings.properties | 1 + .../backend/disease/DiseaseConfiguration.java | 15 + .../DiseaseConfigurationFacadeEjb.java | 8 + .../backend/epidata/EpiDataFacadeEjb.java | 28 +- .../sormas/backend/exposure/Exposure.java | 17 +- .../ExposureCategorySetConverter.java | 43 ++ .../src/main/resources/sql/sormas_schema.sql | 24 +- .../disease/DiseaseConfigurationEditForm.java | 11 +- .../disease/DiseaseConfigurationGrid.java | 4 +- .../symeda/sormas/ui/epidata/EpiDataForm.java | 20 +- .../sormas/ui/exposure/ExposureForm.java | 491 +++++++++++++----- .../sormas/ui/exposure/ExposuresField.java | 156 ++---- .../webapp/VAADIN/themes/sormas/global.scss | 2 +- 20 files changed, 585 insertions(+), 283 deletions(-) create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/exposure/ExposureCategorySetConverter.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationDto.java index bc0c94fc041..42895e4e8e5 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationDto.java @@ -1,9 +1,11 @@ package de.symeda.sormas.api.disease; import java.util.List; +import java.util.Set; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.EntityDto; +import de.symeda.sormas.api.exposure.ExposureCategory; public class DiseaseConfigurationDto extends EntityDto { @@ -28,6 +30,7 @@ public class DiseaseConfigurationDto extends EntityDto { public static final String EXTENDED_CLASSIFICATION_MULTI = "extendedClassificationMulti"; public static final String AGE_GROUPS = "ageGroups"; public static final String AUTOMATIC_SAMPLE_ASSIGNMENT_THRESHOLD = "automaticSampleAssignmentThreshold"; + public static final String EXPOSURE_CATEGORIES = "exposureCategories"; private Disease disease; private Boolean active; @@ -47,6 +50,8 @@ public class DiseaseConfigurationDto extends EntityDto { private List ageGroups; private Integer automaticSampleAssignmentThreshold; + private Set exposureCategories; + public Disease getDisease() { return disease; } @@ -182,4 +187,12 @@ public Integer getAutomaticSampleAssignmentThreshold() { public void setAutomaticSampleAssignmentThreshold(Integer automaticSampleAssignmentThreshold) { this.automaticSampleAssignmentThreshold = automaticSampleAssignmentThreshold; } + + public Set getExposureCategories() { + return exposureCategories; + } + + public void setExposureCategories(Set exposureCategories) { + this.exposureCategories = exposureCategories; + } } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationIndexDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationIndexDto.java index 0314c9d9c6d..c92562c682e 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationIndexDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseConfigurationIndexDto.java @@ -43,6 +43,7 @@ public class DiseaseConfigurationIndexDto extends EntityDto { public static final String EXTENDED_CLASSIFICATION_MULTI = "extendedClassificationMulti"; public static final String AGE_GROUPS = "ageGroups"; public static final String AUTOMATIC_SAMPLE_ASSIGNMENT_THRESHOLD = "automaticSampleAssignmentThreshold"; + public static final String EXPOSURE_CATEGORY_NAMES = "exposureCategoryNames"; private Disease disease; private Boolean active; @@ -61,6 +62,7 @@ public class DiseaseConfigurationIndexDto extends EntityDto { private Boolean extendedClassificationMulti; private List ageGroups; private Integer automaticSampleAssignmentThreshold; + private String exposureCategoryNames; public Disease getDisease() { return disease; @@ -197,4 +199,12 @@ public Integer getAutomaticSampleAssignmentThreshold() { public void setAutomaticSampleAssignmentThreshold(Integer automaticSampleAssignmentThreshold) { this.automaticSampleAssignmentThreshold = automaticSampleAssignmentThreshold; } + + public String getExposureCategoryNames() { + return exposureCategoryNames; + } + + public void setExposureCategoryNames(String exposureCategoryNames) { + this.exposureCategoryNames = exposureCategoryNames; + } } 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 7d9eb8ccf94..42ab1f6553f 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 @@ -129,6 +129,7 @@ public class ExposureDto extends PseudonymizableDto { 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 CONDITION_OF_ANIMAL = "conditionOfAnimal"; public static final String ANIMAL_CATEGORY = "animalCategory"; public static final String ANIMAL_CATEGORY_DETAILS = "animalCategoryDetails"; public static final String FOMITE_TRANSMISSION_LOCATION = "fomiteTransmissionLocation"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java index f518fce3992..1efeef0a8a1 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java @@ -1427,6 +1427,8 @@ public interface Captions { String DiseaseConfiguration_caseSurveillanceEnabled = "DiseaseConfiguration.caseSurveillanceEnabled"; String DiseaseConfiguration_disease = "DiseaseConfiguration.disease"; String DiseaseConfiguration_eventParticipantFollowUpDuration = "DiseaseConfiguration.eventParticipantFollowUpDuration"; + String DiseaseConfiguration_exposureCategories = "DiseaseConfiguration.exposureCategories"; + String DiseaseConfiguration_exposureCategoryNames = "DiseaseConfiguration.exposureCategoryNames"; String DiseaseConfiguration_extendedClassification = "DiseaseConfiguration.extendedClassification"; String DiseaseConfiguration_extendedClassificationMulti = "DiseaseConfiguration.extendedClassificationMulti"; String DiseaseConfiguration_followUpDuration = "DiseaseConfiguration.followUpDuration"; @@ -1584,6 +1586,7 @@ public interface Captions { String EpiData_largeOutbreaksArea = "EpiData.largeOutbreaksArea"; String EpiData_modeOfTransmission = "EpiData.modeOfTransmission"; String EpiData_modeOfTransmissionType = "EpiData.modeOfTransmissionType"; + String EpiData_otherDetails = "EpiData.otherDetails"; String epiDataNoSourceContacts = "epiDataNoSourceContacts"; String epipulseActiveExports = "epipulseActiveExports"; String epipulseAllExports = "epipulseAllExports"; @@ -3286,6 +3289,9 @@ public interface Captions { String titleDiseaseConfigurationAgeGroup = "titleDiseaseConfigurationAgeGroup"; String titleDiseaseConfigurationCaseDefinition = "titleDiseaseConfigurationCaseDefinition"; String titleDiseaseConfigurationGeneral = "titleDiseaseConfigurationGeneral"; + String titleExposureActivitySection = "titleExposureActivitySection"; + String titleExposureLocationSection = "titleExposureLocationSection"; + String titleExposuresSection = "titleExposuresSection"; String titleNoComplications = "titleNoComplications"; String to = "to"; String total = "total"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java index 4d87c7ee71a..2d7df0d4e69 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java @@ -650,6 +650,7 @@ public interface Strings { String headingEnvironmentSamplesRestored = "headingEnvironmentSamplesRestored"; String headingEpiConclusion = "headingEpiConclusion"; String headingEpiCurve = "headingEpiCurve"; + String headingEpiDataOtherInformation = "headingEpiDataOtherInformation"; String headingEpiDataSourceCaseContacts = "headingEpiDataSourceCaseContacts"; String headingEpipulseExportCreated = "headingEpipulseExportCreated"; String headingErrorReportNotAvailable = "headingErrorReportNotAvailable"; diff --git a/sormas-api/src/main/resources/captions.properties b/sormas-api/src/main/resources/captions.properties index 4439f3b6702..f9ed2b2e7a3 100644 --- a/sormas-api/src/main/resources/captions.properties +++ b/sormas-api/src/main/resources/captions.properties @@ -1159,6 +1159,7 @@ EpiData.infectionSource= Suspected vehicle or source of infection EpiData.infectionSourceText= Specify source of infection EpiData.importedCase= Imported Case EpiData.country= Country of contamination +EpiData.otherDetails= General comment #Therapy Therapy.directlyObservedTreatment = Directly observed treatment Therapy.mdrXdrTuberculosis = MDR/XDR tuberculosis @@ -1594,14 +1595,18 @@ Exposure.exposureSetting=Exposure setting Exposure.exposureSettingDetails=Other setting details Exposure.subSettings=Sub-settings Exposure.exposureSubSettingDetails=Other sub-settings details +Exposure.conditionOfAnimal=Condition of animal Exposure.animalCategory=Animal category -Exposure.animalCategoryDetails=Details of the animal category +Exposure.animalCategoryDetails=Details of the animal Exposure.fomiteTransmissionLocation=Fomite transmission location Exposure.contactFactors=Contact factors Exposure.contactFactorDetails=Other contact factors details Exposure.protectiveMeasures=Protective measures Exposure.protectiveMeasureDetails=Other protective measures details Exposure.exposureComment=Comment +titleExposuresSection=Exposure details +titleExposureActivitySection=Activity details +titleExposureLocationSection=Location of exposure # Facility facilityActiveFacilities=Active facilities @@ -3689,6 +3694,8 @@ DiseaseConfiguration.automaticSampleAssignmentThreshold=Automatic sample assignm DiseaseConfiguration.incubationPeriodEnabled=Incubation period enabled DiseaseConfiguration.minIncubationPeriod=Minimum incubation period DiseaseConfiguration.maxIncubationPeriod=Maximum incubation period +DiseaseConfiguration.exposureCategories=Exposure categories +DiseaseConfiguration.exposureCategoryNames=Exposure categories titleDiseaseConfigurationGeneral=General titleDiseaseConfigurationCaseDefinition=Case definition titleDiseaseConfigurationAgeGroup=Age groups diff --git a/sormas-api/src/main/resources/enum.properties b/sormas-api/src/main/resources/enum.properties index b2376867cb0..cbc929e9ca0 100644 --- a/sormas-api/src/main/resources/enum.properties +++ b/sormas-api/src/main/resources/enum.properties @@ -3052,3 +3052,11 @@ ExposureProtectiveMeasure.WASHED=Washed ExposureProtectiveMeasure.MEDICATION=Medication ExposureProtectiveMeasure.C_SECTION=C-Section ExposureProtectiveMeasure.OTHER=Other + +# AnimalCategory +AnimalCategory.DOMESTIC=Domestic +AnimalCategory.WILD=Wild + +# FomiteTransmissionLocation +FomiteTransmissionLocation.INSIDE_HOME=Inside home +FomiteTransmissionLocation.OUTSIDE=Outside \ No newline at end of file diff --git a/sormas-api/src/main/resources/strings.properties b/sormas-api/src/main/resources/strings.properties index a36ff061d83..811a9abee24 100644 --- a/sormas-api/src/main/resources/strings.properties +++ b/sormas-api/src/main/resources/strings.properties @@ -834,6 +834,7 @@ headingExposureInvestigation = Exposure Investigation headingEpiDataSourceCaseContacts = Contacts with Source Case headingExposures = Exposures headingExposureDetails = Exposure Details +headingEpiDataOtherInformation = Other information headingEpiConclusion = Conclusion headingClusterType = Cluster Type headingAnimalContactDetails = Animal Contact Details diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfiguration.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfiguration.java index a2de47cb9a0..5e006f1fef4 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfiguration.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfiguration.java @@ -3,6 +3,7 @@ import static de.symeda.sormas.api.utils.FieldConstraints.CHARACTER_LIMIT_TEXT; import java.util.List; +import java.util.Set; import javax.persistence.Column; import javax.persistence.Convert; @@ -12,7 +13,9 @@ import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.audit.AuditIgnore; +import de.symeda.sormas.api.exposure.ExposureCategory; import de.symeda.sormas.backend.common.AbstractDomainObject; +import de.symeda.sormas.backend.exposure.ExposureCategorySetConverter; @Entity(name = DiseaseConfiguration.TABLE_NAME) @AuditIgnore(retainWrites = true) @@ -54,6 +57,8 @@ public class DiseaseConfiguration extends AbstractDomainObject { private Integer automaticSampleAssignmentThreshold; + private Set exposureCategories; + public static DiseaseConfiguration build(Disease disease) { DiseaseConfiguration configuration = new DiseaseConfiguration(); configuration.setDisease(disease); @@ -214,4 +219,14 @@ public Integer getAutomaticSampleAssignmentThreshold() { public void setAutomaticSampleAssignmentThreshold(Integer automaticSampleAssignmentThreshold) { this.automaticSampleAssignmentThreshold = automaticSampleAssignmentThreshold; } + + @Column + @Convert(converter = ExposureCategorySetConverter.class) + public Set getExposureCategories() { + return exposureCategories; + } + + public void setExposureCategories(Set exposureCategories) { + this.exposureCategories = exposureCategories; + } } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfigurationFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfigurationFacadeEjb.java index 22995b13650..14f3be74f66 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfigurationFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/disease/DiseaseConfigurationFacadeEjb.java @@ -54,6 +54,7 @@ import de.symeda.sormas.api.disease.DiseaseConfigurationDto; import de.symeda.sormas.api.disease.DiseaseConfigurationFacade; import de.symeda.sormas.api.disease.DiseaseConfigurationIndexDto; +import de.symeda.sormas.api.exposure.ExposureCategory; import de.symeda.sormas.api.user.UserRight; import de.symeda.sormas.api.utils.SortProperty; import de.symeda.sormas.backend.common.sort.EntitySortUtils; @@ -252,6 +253,10 @@ private DiseaseConfigurationIndexDto toIndexDto(DiseaseConfiguration entity) { dto.setAgeGroups(entity.getAgeGroups()); dto.setAutomaticSampleAssignmentThreshold(entity.getAutomaticSampleAssignmentThreshold()); + if (entity.getExposureCategories() != null && !entity.getExposureCategories().isEmpty()) { + dto.setExposureCategoryNames(entity.getExposureCategories().stream().map(ExposureCategory::toString).collect(Collectors.joining(", "))); + } + return dto; } @@ -419,6 +424,7 @@ public static DiseaseConfigurationDto toDto(DiseaseConfiguration source) { target.setExtendedClassificationMulti(source.getExtendedClassificationMulti()); target.setAgeGroups(source.getAgeGroups()); target.setAutomaticSampleAssignmentThreshold(source.getAutomaticSampleAssignmentThreshold()); + target.setExposureCategories(source.getExposureCategories()); return target; } @@ -509,6 +515,8 @@ public DiseaseConfiguration fillOrBuildEntity(@NotNull DiseaseConfigurationDto s target.setMinIncubationPeriod(source.getMinIncubationPeriod()); target.setIncubationPeriodEnabled(source.getIncubationPeriodEnabled()); target.setCaseDefinitionText(source.getCaseDefinitionText()); + target.setExposureCategories(source.getExposureCategories()); + //update changedate: required for mobile app to be aware of changes target.setChangeDate(new Timestamp((new Date()).getTime())); 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 feb073c54db..8c031994b65 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 @@ -23,8 +23,6 @@ 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; @@ -33,10 +31,7 @@ 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; @@ -219,26 +214,9 @@ public Exposure fillOrBuildExposureEntity(ExposureDto source, Exposure target, b 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); + target.setSubSettings(source.getSubSettings() != null ? source.getSubSettings() : new HashSet<>()); + target.setContactFactors(source.getContactFactors() != null ? source.getContactFactors() : new HashSet<>()); + target.setProtectiveMeasures(source.getProtectiveMeasures() != null ? source.getProtectiveMeasures() : new HashSet<>()); 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 a86bfd6005d..a4c1f532198 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 @@ -34,7 +34,6 @@ 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; @@ -83,6 +82,7 @@ public class Exposure extends AbstractDomainObject { 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 CONDITION_OF_ANIMAL = "conditionOfAnimal"; public static final String ANIMAL_CATEGORY = "animalCategory"; public static final String ANIMAL_CATEGORY_DETAILS = "animalCategoryDetails"; public static final String FOMITE_TRANSMISSION_LOCATION = "fomiteTransmissionLocation"; @@ -906,10 +906,7 @@ public void setFomiteTransmissionLocation(FomiteTransmissionLocation fomiteTrans @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" })) + joinColumns = @JoinColumn(name = "exposure_id", referencedColumnName = Exposure.ID, nullable = false)) @Column(name = "subsetting", nullable = false) public Set getSubSettings() { return subSettings; @@ -922,10 +919,7 @@ public void setSubSettings(Set 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" })) + joinColumns = @JoinColumn(name = "exposure_id", referencedColumnName = Exposure.ID, nullable = false)) @Column(name = "contactfactor", nullable = false) public Set getContactFactors() { return contactFactors; @@ -938,10 +932,7 @@ public void setContactFactors(Set 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" })) + joinColumns = @JoinColumn(name = "exposure_id", referencedColumnName = Exposure.ID, nullable = false)) @Column(name = "protectivemeasure", nullable = false) public Set getProtectiveMeasures() { return protectiveMeasures; diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/exposure/ExposureCategorySetConverter.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/exposure/ExposureCategorySetConverter.java new file mode 100644 index 00000000000..b6c6fbad388 --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/exposure/ExposureCategorySetConverter.java @@ -0,0 +1,43 @@ +/* + * 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.backend.exposure; + +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.persistence.AttributeConverter; + +import org.apache.commons.lang3.StringUtils; + +import de.symeda.sormas.api.exposure.ExposureCategory; + +public class ExposureCategorySetConverter implements AttributeConverter, String> { + + @Override + public String convertToDatabaseColumn(Set exposureCategories) { + return exposureCategories != null + ? String.join(",", exposureCategories.stream().map(ExposureCategory::name).collect(Collectors.toSet())) + : null; + } + + @Override + public Set convertToEntityAttribute(String exposureCategorysText) { + return exposureCategorysText != null + ? Stream.of(StringUtils.split(exposureCategorysText, ",")).map(ExposureCategory::valueOf).collect(Collectors.toSet()) + : null; + } +} diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index b42e00d89fa..92d5f505782 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15679,6 +15679,28 @@ END $$; INSERT INTO schema_version (version_number, comment) VALUES (616, '#13801, #13814 - Malaria and Dengue TestReport columns renames'); +-- update delete triggers for exposure related tables + +alter table exposures_subsettings drop constraint unq_exposures_subsettings_0; +alter table exposures_subsettings add constraint exposures_subsettings_pk primary key (exposure_id, subsetting); +DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_subsettings; + +alter table exposures_contactfactors drop constraint unq_exposures_contactfactors_0; +alter table exposures_contactfactors add constraint exposures_contactfactors_pk primary key (exposure_id, contactfactor); +DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_contactfactors; + +alter table exposures_protectivemeasures drop constraint unq_exposures_protectivemeasures_0; +alter table exposures_protectivemeasures add constraint exposures_protectivemeasures_pk primary key (exposure_id, protectivemeasure); +DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_protectivemeasures; + +INSERT INTO schema_version (version_number, comment) VALUES (617, '#13887 update keys and drop delete history triggers for new exposures tables'); + +alter table public.diseaseconfiguration add exposurecategories varchar(255); +alter table public.diseaseconfiguration_history add exposurecategories varchar(255); + +INSERT INTO schema_version (version_number, comment) VALUES (618, '#13887 add exposure categories to disease configuration'); + + -- #13828 - Customizable Fields CREATE TABLE IF NOT EXISTS customizablefieldmetadata ( @@ -15817,6 +15839,6 @@ CREATE TRIGGER delete_history_trigger_customizablefieldvalue ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; -INSERT INTO schema_version (version_number, comment) VALUES (617, '#13828 - Add history tables for customizable fields'); +INSERT INTO schema_version (version_number, comment) VALUES (619, '#13828 - Add history tables for customizable fields'); -- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** \ No newline at end of file diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/disease/DiseaseConfigurationEditForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/disease/DiseaseConfigurationEditForm.java index 64547ebe440..6fde92997a7 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/disease/DiseaseConfigurationEditForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/disease/DiseaseConfigurationEditForm.java @@ -28,6 +28,7 @@ import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.disease.DiseaseConfigurationDto; +import de.symeda.sormas.api.exposure.ExposureCategory; import de.symeda.sormas.api.i18n.Captions; import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; @@ -36,6 +37,7 @@ import de.symeda.sormas.ui.utils.AbstractEditForm; import de.symeda.sormas.ui.utils.CssStyles; import de.symeda.sormas.ui.utils.FieldHelper; +import de.symeda.sormas.ui.utils.components.CheckboxSet; import de.symeda.sormas.ui.utils.components.DiseaseConfigurationAgeGroupComponent; public class DiseaseConfigurationEditForm extends AbstractEditForm { @@ -53,7 +55,8 @@ public class DiseaseConfigurationEditForm extends AbstractEditForm exposureCategoriesField; + public DiseaseConfigurationEditForm() { super( @@ -116,6 +121,10 @@ protected void addFields() { cbExtendedClassification = addField(generalLayout, DiseaseConfigurationDto.EXTENDED_CLASSIFICATION, CheckBox.class); cbExtendedClassificationMulti = addField(generalLayout, DiseaseConfigurationDto.EXTENDED_CLASSIFICATION_MULTI, CheckBox.class); + exposureCategoriesField = addField(generalLayout, DiseaseConfigurationDto.EXPOSURE_CATEGORIES, CheckboxSet.class); + exposureCategoriesField.setColumnCount(3); + exposureCategoriesField.setItems(Arrays.asList(ExposureCategory.values()), null, null); + ageGroupsComponent = addField(ageGroupLayout, DiseaseConfigurationDto.AGE_GROUPS, DiseaseConfigurationAgeGroupComponent.class); ageGroupsComponent.setCaption(I18nProperties.getPrefixCaption(DiseaseConfigurationDto.I18N_PREFIX, DiseaseConfigurationDto.AGE_GROUPS)); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/disease/DiseaseConfigurationGrid.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/disease/DiseaseConfigurationGrid.java index d92cc1acf24..33b26dcf20a 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/disease/DiseaseConfigurationGrid.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/disease/DiseaseConfigurationGrid.java @@ -59,9 +59,11 @@ public DiseaseConfigurationGrid(DiseaseConfigurationCriteria criteria) { DiseaseConfigurationIndexDto.EVENT_PARTICIPANT_FOLLOW_UP_DURATION, DiseaseConfigurationIndexDto.EXTENDED_CLASSIFICATION, DiseaseConfigurationIndexDto.EXTENDED_CLASSIFICATION_MULTI, - DiseaseConfigurationIndexDto.AUTOMATIC_SAMPLE_ASSIGNMENT_THRESHOLD); + DiseaseConfigurationIndexDto.AUTOMATIC_SAMPLE_ASSIGNMENT_THRESHOLD, + DiseaseConfigurationIndexDto.EXPOSURE_CATEGORY_NAMES); getColumn(DiseaseConfigurationIndexDto.AGE_GROUPS).setSortable(false); + getColumn(DiseaseConfigurationIndexDto.EXPOSURE_CATEGORY_NAMES).setSortable(false); addEditColumn(e -> ControllerProvider.getDiseaseConfirgurationController().editDiseaseConfiguration(e.getUuid())); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java index 75e340125a8..6fb20a3a375 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java @@ -17,6 +17,7 @@ *******************************************************************************/ package de.symeda.sormas.ui.epidata; +import static de.symeda.sormas.ui.utils.CssStyles.H3; import static de.symeda.sormas.ui.utils.CssStyles.VSPACE_3; import static de.symeda.sormas.ui.utils.CssStyles.VSPACE_TOP_3; import static de.symeda.sormas.ui.utils.LayoutUtil.divsCss; @@ -41,6 +42,7 @@ import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.DateField; import com.vaadin.v7.ui.Field; +import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.CountryHelper; @@ -55,6 +57,7 @@ import de.symeda.sormas.api.epidata.EpiDataDto; import de.symeda.sormas.api.exposure.InfectionSource; import de.symeda.sormas.api.exposure.ModeOfTransmission; +import de.symeda.sormas.api.i18n.Descriptions; import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.i18n.Strings; import de.symeda.sormas.api.infrastructure.country.CountryReferenceDto; @@ -85,6 +88,7 @@ public class EpiDataForm extends AbstractEditForm { private static final String LOC_EXP_PERIOD_HEADING = "locExpPeriodHeading"; private static final String EXPOSURE_DATES_LAYOUT = fluidRowLocs(3, "EXPOSURE_START_DATE_LABEL", 3, "EXPOSURE_START_DATE_VALUE", 3, "EXPOSURE_END_DATE_LABEL", 3, "EXPOSURE_END_DATE_VALUE"); + private static final String LOC_OTHER_INFORMATION_HEADING = "locOtherInformationHeading"; //@formatter:off private static final String MAIN_HTML_LAYOUT = @@ -112,6 +116,9 @@ public class EpiDataForm extends AbstractEditForm { private static final String SOURCE_CONTACTS_HTML_LAYOUT = locCss(VSPACE_TOP_3, LOC_SOURCE_CASE_CONTACTS_HEADING) + loc(EpiDataDto.CONTACT_WITH_SOURCE_CASE_KNOWN); + + private static final String OTHER_INFORMATION_HTML_LAYOUT = + loc(LOC_OTHER_INFORMATION_HEADING) + fluidRowLocs(EpiDataDto.OTHER_DETAILS); //@formatter:on private final Disease disease; @@ -216,6 +223,12 @@ protected void addFields() { exposuresField.addValueChangeListener(e -> { ogExposureDetailsKnown.setEnabled(CollectionUtils.isEmpty(exposuresField.getValue())); }); + + TextArea additionalDetails = addField(EpiDataDto.OTHER_DETAILS, TextArea.class); + additionalDetails.setRows(6); + additionalDetails.setDescription( + I18nProperties.getPrefixDescription(EpiDataDto.I18N_PREFIX, EpiDataDto.OTHER_DETAILS, "") + "\n" + + I18nProperties.getDescription(Descriptions.descGdpr)); } /** @@ -325,6 +338,10 @@ private void addHeadingsAndInfoTexts() { + divsCss(VSPACE_3, I18nProperties.getString(Strings.infoEpiDataSourceCaseContacts)), ContentMode.HTML), LOC_SOURCE_CASE_CONTACTS_HEADING); + + Label otherInformationLabel = new Label(I18nProperties.getString(Strings.headingEpiDataOtherInformation)); + otherInformationLabel.addStyleName(H3); + getContent().addComponent(otherInformationLabel, LOC_OTHER_INFORMATION_HEADING); } public void disableContactWithSourceCaseKnownField() { @@ -337,6 +354,7 @@ public void setGetSourceContactsCallback(Supplier> cal @Override protected String createHtmlLayout() { - return parentClass == CaseDataDto.class ? MAIN_HTML_LAYOUT + SOURCE_CONTACTS_HTML_LAYOUT : MAIN_HTML_LAYOUT; + String layout = parentClass == CaseDataDto.class ? MAIN_HTML_LAYOUT + SOURCE_CONTACTS_HTML_LAYOUT : MAIN_HTML_LAYOUT; + return layout + OTHER_INFORMATION_HTML_LAYOUT; } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java index 890cf22859b..ef309738be9 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java @@ -23,18 +23,23 @@ import static de.symeda.sormas.ui.utils.LayoutUtil.loc; import static de.symeda.sormas.ui.utils.LayoutUtil.locs; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; import java.util.List; import java.util.Set; import com.vaadin.icons.VaadinIcons; import com.vaadin.shared.ui.ContentMode; +import com.vaadin.ui.CustomLayout; import com.vaadin.ui.Label; import com.vaadin.ui.themes.ValoTheme; import com.vaadin.v7.data.util.converter.Converter; import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.Field; import com.vaadin.v7.ui.OptionGroup; import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; @@ -45,8 +50,11 @@ import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.caze.CaseDataDto; import de.symeda.sormas.api.contact.ContactReferenceDto; +import de.symeda.sormas.api.disease.DiseaseConfigurationDto; +import de.symeda.sormas.api.epidata.AnimalCondition; 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; @@ -56,6 +64,7 @@ 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; @@ -69,6 +78,7 @@ import de.symeda.sormas.api.utils.YesNoUnknown; import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.adverseeventsfollowingimmunization.components.form.FormSectionAccordion; import de.symeda.sormas.ui.location.LocationEditForm; import de.symeda.sormas.ui.utils.AbstractEditForm; import de.symeda.sormas.ui.utils.ComboBoxWithPlaceholder; @@ -82,6 +92,7 @@ public class ExposureForm extends AbstractEditForm { private static final long serialVersionUID = 8262753698264714832L; + public static final String MAIN_ACCORDION_LOC = "mainAccordionLoc"; private static final String LOC_EXPOSURES_HEADING = "locExposuresHeading"; private static final String LOC_EXPOSURE_DETAILS_HEADING = "locExposureDetailsHeading"; private static final String LOC_LOCATION_HEADING = "locLocationHeading"; @@ -90,39 +101,43 @@ public class ExposureForm extends AbstractEditForm { private static final String LOC_CONCLUSION_HEADING = "locConclusionHeading"; //@formatter:off + public static final String MAIN_ACCORDION_LAYOUT = fluidRowLocs(MAIN_ACCORDION_LOC); + private static final String UUID_REPORTING_USER = fluidRowLocs(ExposureDto.UUID, ExposureDto.REPORTING_USER); - private static final String OTHER_STANDARD_FIELDS = + + private static final String EXPOSURE_DETAILS_LAYOUT = fluidRowLocs(ExposureDto.START_DATE, ExposureDto.END_DATE) + - loc(LOC_EXPOSURES_HEADING) + - fluidRowLocs(ExposureDto.EXPOSURE_CATEGORY, null, null) + - fluidRow( - fluidColumn(4, 0, locs( - ExposureDto.EXPOSURE_SETTING, - ExposureDto.SUB_SETTINGS, - ExposureDto.ANIMAL_CATEGORY, - ExposureDto.FOMITE_TRANSMISSION_LOCATION - )), - fluidColumn(4, 0, locs( - ExposureDto.CONTACT_FACTORS - )), - fluidColumn(4, 0, locs( - ExposureDto.PROTECTIVE_MEASURES - )) - ) + - fluidRow( - fluidColumn(4, 0, locs( - ExposureDto.EXPOSURE_SETTING_DETAILS, - ExposureDto.EXPOSURE_SUB_SETTING_DETAILS, - ExposureDto.ANIMAL_CATEGORY_DETAILS - )), - fluidColumn(4, 0, locs( - ExposureDto.CONTACT_FACTOR_DETAILS - )), - fluidColumn(4, 0, locs( - ExposureDto.PROTECTIVE_MEASURE_DETAILS - )) - ) + - loc(ExposureDto.DESCRIPTION) + + loc(LOC_EXPOSURES_HEADING) + + fluidRowLocs(ExposureDto.EXPOSURE_CATEGORY, ExposureDto.EXPOSURE_SETTING, ExposureDto.EXPOSURE_SETTING_DETAILS) + + fluidRow( + fluidColumn(4, 0, locs( + ExposureDto.SUB_SETTINGS, + ExposureDto.CONDITION_OF_ANIMAL, + ExposureDto.ANIMAL_CATEGORY, + ExposureDto.FOMITE_TRANSMISSION_LOCATION + )), + fluidColumn(4, 0, locs( + ExposureDto.CONTACT_FACTORS + )), + fluidColumn(4, 0, locs( + ExposureDto.PROTECTIVE_MEASURES + )) + ) + + fluidRow( + fluidColumn(4, 0, locs( + ExposureDto.EXPOSURE_SUB_SETTING_DETAILS, + ExposureDto.ANIMAL_CATEGORY_DETAILS + )), + fluidColumn(4, 0, locs( + ExposureDto.CONTACT_FACTOR_DETAILS + )), + fluidColumn(4, 0, locs( + ExposureDto.PROTECTIVE_MEASURE_DETAILS + )) + ) + + loc(ExposureDto.DESCRIPTION); + + private static final String ACTIVITY_DETAILS_LAYOUT = fluidRow( fluidColumnLoc(6, 0, ExposureDto.EXPOSURE_TYPE), fluidColumn(6, 0, locs( @@ -133,49 +148,50 @@ public class ExposureForm extends AbstractEditForm { ExposureDto.TYPE_OF_CHILDCARE_FACILITY )) ) + - fluidRow( - fluidColumn(12, 0, locs( - ExposureDto.GATHERING_DETAILS, - ExposureDto.HABITATION_DETAILS, - ExposureDto.TYPE_OF_ANIMAL_DETAILS, - ExposureDto.CHILDCARE_FACILITY_DETAILS - )) - ) + - loc(LOC_EXPOSURE_DETAILS_HEADING) + - loc(ExposureDto.EXPOSURE_ROLE) + - loc(ExposureDto.RISK_AREA) + - loc(ExposureDto.LARGE_ATTENDANCE_NUMBER) + - loc(ExposureDto.INDOORS) + - loc(ExposureDto.OUTDOORS) + - loc(ExposureDto.WEARING_MASK) + - loc(ExposureDto.WEARING_PPE) + - loc(ExposureDto.OTHER_PROTECTIVE_MEASURES) + - loc(ExposureDto.PROTECTIVE_MEASURES_DETAILS) + - loc(ExposureDto.SHORT_DISTANCE) + - loc(ExposureDto.LONG_FACE_TO_FACE_CONTACT) + - loc(ExposureDto.ANIMAL_MARKET) + - loc(ExposureDto.PERCUTANEOUS) + - loc(ExposureDto.CONTACT_TO_BODY_FLUIDS) + - loc(ExposureDto.HANDLING_SAMPLES) + - loc(ExposureDto.EATING_RAW_ANIMAL_PRODUCTS) + - loc(ExposureDto.HANDLING_ANIMALS) + - fluidRowLocs(ExposureDto.TRAVEL_ACCOMMODATION, ExposureDto.TRAVEL_ACCOMMODATION_TYPE) + - fluidRowLocs(ExposureDto.DOMESTIC_SWIMMING, ExposureDto.INTERNATIONAL_SWIMMING) + - fluidRowLocs(ExposureDto.RAW_FOOD_CONTACT, ExposureDto.RAW_FOOD_CONTACT_TEXT) + - fluidRowLocs(ExposureDto.SWIMMING_LOCATION, ExposureDto.SWIMMING_LOCATION_TYPE) + - fluidRow(fluidColumnLoc(6,0,ExposureDto.SEXUAL_EXPOSURE_TEXT)) + - fluidRow(fluidColumnLoc(6,0,ExposureDto.SYMPTOMATIC_INDIVIDUAL_TEXT)) + - loc(ExposureDto.CONTACT_TO_CASE) + - loc(LOC_ANIMAL_CONTACT_DETAILS_HEADING) + - loc(ExposureDto.ANIMAL_CONDITION) + - fluidRowLocs(ExposureDto.ANIMAL_CONTACT_TYPE, ExposureDto.ANIMAL_CONTACT_TYPE_DETAILS) + - fluidRowLocs(ExposureDto.ANIMAL_LOCATION, ExposureDto.ANIMAL_LOCATION_TEXT) + - loc(ExposureDto.ANIMAL_VACCINATED) + - loc(LOC_BURIAL_DETAILS_HEADING) + - loc(ExposureDto.PHYSICAL_CONTACT_DURING_PREPARATION) + - loc(ExposureDto.PHYSICAL_CONTACT_WITH_BODY) + - fluidRowLocs(ExposureDto.DECEASED_PERSON_NAME, ExposureDto.DECEASED_PERSON_RELATION) + - + fluidRow( + fluidColumn(12, 0, locs( + ExposureDto.GATHERING_DETAILS, + ExposureDto.HABITATION_DETAILS, + ExposureDto.TYPE_OF_ANIMAL_DETAILS, + ExposureDto.CHILDCARE_FACILITY_DETAILS + )) + ) + + loc(LOC_EXPOSURE_DETAILS_HEADING) + + loc(ExposureDto.EXPOSURE_ROLE) + + loc(ExposureDto.RISK_AREA) + + loc(ExposureDto.LARGE_ATTENDANCE_NUMBER) + + loc(ExposureDto.INDOORS) + + loc(ExposureDto.OUTDOORS) + + loc(ExposureDto.WEARING_MASK) + + loc(ExposureDto.WEARING_PPE) + + loc(ExposureDto.OTHER_PROTECTIVE_MEASURES) + + loc(ExposureDto.PROTECTIVE_MEASURES_DETAILS) + + loc(ExposureDto.SHORT_DISTANCE) + + loc(ExposureDto.LONG_FACE_TO_FACE_CONTACT) + + loc(ExposureDto.ANIMAL_MARKET) + + loc(ExposureDto.PERCUTANEOUS) + + loc(ExposureDto.CONTACT_TO_BODY_FLUIDS) + + loc(ExposureDto.HANDLING_SAMPLES) + + loc(ExposureDto.EATING_RAW_ANIMAL_PRODUCTS) + + loc(ExposureDto.HANDLING_ANIMALS) + + fluidRowLocs(ExposureDto.TRAVEL_ACCOMMODATION, ExposureDto.TRAVEL_ACCOMMODATION_TYPE) + + fluidRowLocs(ExposureDto.DOMESTIC_SWIMMING, ExposureDto.INTERNATIONAL_SWIMMING) + + fluidRowLocs(ExposureDto.RAW_FOOD_CONTACT, ExposureDto.RAW_FOOD_CONTACT_TEXT) + + fluidRowLocs(ExposureDto.SWIMMING_LOCATION, ExposureDto.SWIMMING_LOCATION_TYPE) + + fluidRow(fluidColumnLoc(6,0,ExposureDto.SEXUAL_EXPOSURE_TEXT)) + + fluidRow(fluidColumnLoc(6,0,ExposureDto.SYMPTOMATIC_INDIVIDUAL_TEXT)) + + loc(ExposureDto.CONTACT_TO_CASE) + + loc(LOC_ANIMAL_CONTACT_DETAILS_HEADING) + + loc(ExposureDto.ANIMAL_CONDITION) + + fluidRowLocs(ExposureDto.ANIMAL_CONTACT_TYPE, ExposureDto.ANIMAL_CONTACT_TYPE_DETAILS) + + fluidRowLocs(ExposureDto.ANIMAL_LOCATION, ExposureDto.ANIMAL_LOCATION_TEXT) + + loc(ExposureDto.ANIMAL_VACCINATED) + + loc(LOC_BURIAL_DETAILS_HEADING) + + loc(ExposureDto.PHYSICAL_CONTACT_DURING_PREPARATION) + + loc(ExposureDto.PHYSICAL_CONTACT_WITH_BODY) + + fluidRowLocs(ExposureDto.DECEASED_PERSON_NAME, ExposureDto.DECEASED_PERSON_RELATION); + + private static final String LOCATION_DETAILS_LAYOUT = loc(LOC_LOCATION_HEADING) + fluidRow( fluidColumn(6, 0, locs(ExposureDto.TYPE_OF_PLACE)), @@ -193,6 +209,17 @@ public class ExposureForm extends AbstractEditForm { private final Class epiDataParentClass; private final List sourceContacts; + private CustomLayout exposureDetailsLayout; + private CustomLayout activityDetailsLayout; + private CustomLayout locationDetailsLayout; + + private Label exposuresHeading; + private Label activityDetailsHeading; + private Label locationHeading; + private Label animalContactDetailsHeading; + private Label burialDetailsHeading; + private Label conclusionHeading; + private LocationEditForm locationForm; private Disease disease; @@ -205,7 +232,7 @@ public class ExposureForm extends AbstractEditForm { private TextField contactFactorDetailsField; private OptionGroup protectiveMeasuresField; private TextField protectiveMeasureDetailsField; - private NullableOptionGroup animalConditionField; + private NullableOptionGroup conditionOfAnimalField; private NullableOptionGroup animalCategoryField; private TextField animalCategoryDetailsField; private NullableOptionGroup fomiteTransmissionLocationField; @@ -235,14 +262,26 @@ public ExposureForm( @SuppressWarnings("deprecation") @Override protected void addFields() { + + FormSectionAccordion accordion = new FormSectionAccordion(); + + exposureDetailsLayout = new CustomLayout(); + exposureDetailsLayout.setTemplateContents(EXPOSURE_DETAILS_LAYOUT); + + activityDetailsLayout = new CustomLayout(); + activityDetailsLayout.setTemplateContents(ACTIVITY_DETAILS_LAYOUT); + + locationDetailsLayout = new CustomLayout(); + locationDetailsLayout.setTemplateContents(LOCATION_DETAILS_LAYOUT); + addHeadingsAndInfoTexts(); addBasicFields(); - addField(ExposureDto.DESCRIPTION, TextArea.class).setRows(5); + addField(exposureDetailsLayout, ExposureDto.DESCRIPTION, TextArea.class).setRows(5); - locationForm = addField(ExposureDto.LOCATION, LocationEditForm.class); + locationForm = addField(locationDetailsLayout, ExposureDto.LOCATION, LocationEditForm.class); locationForm.setCaption(null); - addField(ExposureDto.CONNECTION_NUMBER, TextField.class); + addField(locationDetailsLayout, ExposureDto.CONNECTION_NUMBER, TextField.class); getField(ExposureDto.MEANS_OF_TRANSPORT).addValueChangeListener(e -> { if (e.getProperty().getValue() == MeansOfTransport.PLANE) { getField(ExposureDto.CONNECTION_NUMBER).setCaption(I18nProperties.getCaption(Captions.exposureFlightNumber)); @@ -253,9 +292,15 @@ protected void addFields() { }); if (epiDataParentClass == CaseDataDto.class) { - addField(ExposureDto.CONTACT_TO_CASE, ComboBox.class); + addField(activityDetailsLayout, ExposureDto.CONTACT_TO_CASE, ComboBox.class); } + accordion.addFormSectionPanel(Captions.titleExposuresSection, true, exposureDetailsLayout); + accordion.addFormSectionPanel(Captions.titleExposureActivitySection, false, activityDetailsLayout); + accordion.addFormSectionPanel(Captions.titleExposureLocationSection, false, locationDetailsLayout); + + getContent().addComponent(accordion, MAIN_ACCORDION_LOC); + setUpVisibilityDependencies(); initializeVisibilitiesAndAllowedVisibilities(); @@ -266,68 +311,75 @@ protected void addFields() { } private void addHeadingsAndInfoTexts() { - getContent().addComponent(new Label(h3(I18nProperties.getString(Strings.headingExposures)), ContentMode.HTML), LOC_EXPOSURES_HEADING); - getContent() - .addComponent(new Label(h3(I18nProperties.getString(Strings.headingExposureDetails)), ContentMode.HTML), LOC_EXPOSURE_DETAILS_HEADING); + exposuresHeading = new Label(h3(I18nProperties.getString(Strings.headingExposures)), ContentMode.HTML); + exposureDetailsLayout.addComponent(exposuresHeading, LOC_EXPOSURES_HEADING); - getContent().addComponent( - new Label(h3(I18nProperties.getPrefixCaption(ExposureDto.I18N_PREFIX, ExposureDto.LOCATION)), ContentMode.HTML), - LOC_LOCATION_HEADING); + activityDetailsHeading = new Label(h3(I18nProperties.getString(Strings.headingExposureDetails)), ContentMode.HTML); + activityDetailsLayout.addComponent(activityDetailsHeading, LOC_EXPOSURE_DETAILS_HEADING); - getContent().addComponent( - new Label(h3(I18nProperties.getString(Strings.headingAnimalContactDetails)), ContentMode.HTML), - LOC_ANIMAL_CONTACT_DETAILS_HEADING); + locationHeading = new Label(h3(I18nProperties.getPrefixCaption(ExposureDto.I18N_PREFIX, ExposureDto.LOCATION)), ContentMode.HTML); + locationDetailsLayout.addComponent(locationHeading, LOC_LOCATION_HEADING); - getContent() - .addComponent(new Label(h3(I18nProperties.getString(Strings.headingBurialDetails)), ContentMode.HTML), LOC_BURIAL_DETAILS_HEADING); + animalContactDetailsHeading = new Label(h3(I18nProperties.getString(Strings.headingAnimalContactDetails)), ContentMode.HTML); + activityDetailsLayout.addComponent(animalContactDetailsHeading, LOC_ANIMAL_CONTACT_DETAILS_HEADING); - getContent().addComponent(new Label(h3(I18nProperties.getString(Strings.headingEpiConclusion)), ContentMode.HTML), LOC_CONCLUSION_HEADING); + burialDetailsHeading = new Label(h3(I18nProperties.getString(Strings.headingBurialDetails)), ContentMode.HTML); + activityDetailsLayout.addComponent(burialDetailsHeading, LOC_BURIAL_DETAILS_HEADING); + + conclusionHeading = new Label(h3(I18nProperties.getString(Strings.headingEpiConclusion)), ContentMode.HTML); + getContent().addComponent(conclusionHeading, LOC_CONCLUSION_HEADING); } private void addBasicFields() { addFields(ExposureDto.UUID, ExposureDto.REPORTING_USER, ExposureDto.PROBABLE_INFECTION_ENVIRONMENT); - DateTimeField startDate = addField(ExposureDto.START_DATE, DateTimeField.class); - DateTimeField endDate = addField(ExposureDto.END_DATE, DateTimeField.class); + DateTimeField startDate = addField(exposureDetailsLayout, ExposureDto.START_DATE, DateTimeField.class); + DateTimeField endDate = addField(exposureDetailsLayout, ExposureDto.END_DATE, DateTimeField.class); DateComparisonValidator.addStartEndValidators(startDate, endDate, false); - List categories = Arrays.asList(ExposureCategory.values()); - categoryField = addField(ExposureDto.EXPOSURE_CATEGORY, ComboBox.class); + categoryField = addField(exposureDetailsLayout, ExposureDto.EXPOSURE_CATEGORY, ComboBox.class); categoryField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); categoryField.setRequired(true); - FieldHelper.updateItems(categoryField, categories); - settingField = addField(ExposureDto.EXPOSURE_SETTING, ComboBox.class); + settingField = addField(exposureDetailsLayout, ExposureDto.EXPOSURE_SETTING, ComboBox.class); settingField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); - settingField.setRequired(true); - settingDetailsField = addField(ExposureDto.EXPOSURE_SETTING_DETAILS, TextField.class); + settingDetailsField = addField(exposureDetailsLayout, ExposureDto.EXPOSURE_SETTING_DETAILS, TextField.class); settingDetailsField.setVisible(false); - subSettingsField = addField(ExposureDto.SUB_SETTINGS, OptionGroup.class); + subSettingsField = addField(exposureDetailsLayout, ExposureDto.SUB_SETTINGS, OptionGroup.class); subSettingsField.setMultiSelect(true); CssStyles.style(subSettingsField, CssStyles.CAPTION_ON_TOP); - subSettingsDetailsField = addField(ExposureDto.EXPOSURE_SUB_SETTING_DETAILS, TextField.class); + subSettingsDetailsField = addField(exposureDetailsLayout, ExposureDto.EXPOSURE_SUB_SETTING_DETAILS, TextField.class); subSettingsDetailsField.setVisible(false); - //animalConditionField = addField(ExposureDto.ANIMAL_CONDITION, NullableOptionGroup.class); - animalCategoryField = addField(ExposureDto.ANIMAL_CATEGORY, NullableOptionGroup.class); + conditionOfAnimalField = addField(exposureDetailsLayout, ExposureDto.CONDITION_OF_ANIMAL, NullableOptionGroup.class); + conditionOfAnimalField.setVisible(false); + + animalCategoryField = addField(exposureDetailsLayout, ExposureDto.ANIMAL_CATEGORY, NullableOptionGroup.class); + animalCategoryField.setVisible(false); - contactFactorsField = addField(ExposureDto.CONTACT_FACTORS, OptionGroup.class); + animalCategoryDetailsField = addField(exposureDetailsLayout, ExposureDto.ANIMAL_CATEGORY_DETAILS, TextField.class); + animalCategoryDetailsField.setVisible(false); + + fomiteTransmissionLocationField = addField(exposureDetailsLayout, ExposureDto.FOMITE_TRANSMISSION_LOCATION, NullableOptionGroup.class); + fomiteTransmissionLocationField.setVisible(false); + + contactFactorsField = addField(exposureDetailsLayout, ExposureDto.CONTACT_FACTORS, OptionGroup.class); contactFactorsField.setMultiSelect(true); CssStyles.style(contactFactorsField, CssStyles.CAPTION_ON_TOP); - contactFactorDetailsField = addField(ExposureDto.CONTACT_FACTOR_DETAILS, TextField.class); + contactFactorDetailsField = addField(exposureDetailsLayout, ExposureDto.CONTACT_FACTOR_DETAILS, TextField.class); contactFactorDetailsField.setVisible(false); - protectiveMeasuresField = addField(ExposureDto.PROTECTIVE_MEASURES, OptionGroup.class); + protectiveMeasuresField = addField(exposureDetailsLayout, ExposureDto.PROTECTIVE_MEASURES, OptionGroup.class); protectiveMeasuresField.setMultiSelect(true); CssStyles.style(protectiveMeasuresField, CssStyles.CAPTION_ON_TOP); - protectiveMeasureDetailsField = addField(ExposureDto.PROTECTIVE_MEASURE_DETAILS, TextField.class); + protectiveMeasureDetailsField = addField(exposureDetailsLayout, ExposureDto.PROTECTIVE_MEASURE_DETAILS, TextField.class); protectiveMeasureDetailsField.setVisible(false); categoryField.addValueChangeListener(e -> { @@ -340,6 +392,12 @@ private void addBasicFields() { // Update contact factors and protective measures when category changes updateContactFactorsFieldItems(selectedCategory, (ExposureSetting) settingField.getValue()); updateProtectiveMeasuresFieldItems(selectedCategory, (ExposureSetting) settingField.getValue()); + + // Update animal contact fields based on category + updateAnimalContactFields(selectedCategory); + + // Update fomite transmission field based on category + updateFomiteTransmissionField(selectedCategory); }); settingField.addValueChangeListener(e -> { @@ -379,7 +437,45 @@ private void addBasicFields() { protectiveMeasureDetailsField.setVisible(containsOther); }); - addFields( + conditionOfAnimalField.addValueChangeListener(e -> { + Object value = e.getProperty().getValue(); + boolean hasValue = value != null; + + // Show/hide and set required for animalCategoryField + animalCategoryField.setVisible(hasValue); + animalCategoryField.setRequired(hasValue); + + // Clear dependent fields when conditionOfAnimal becomes null + if (!hasValue) { + animalCategoryField.setValue(null); + animalCategoryDetailsField.setValue(null); + animalCategoryDetailsField.setVisible(false); + } + }); + + animalCategoryField.addValueChangeListener(e -> { + Object value = e.getProperty().getValue(); + boolean hasValue = value != null; + + // Show/hide animalCategoryDetailsField + animalCategoryDetailsField.setVisible(hasValue); + + // Clear details field when animalCategory becomes null + if (!hasValue) { + animalCategoryDetailsField.setValue(null); + } + }); + + addField(locationDetailsLayout, ExposureDto.TYPE_OF_PLACE, ComboBox.class); + addField(locationDetailsLayout, ExposureDto.TYPE_OF_PLACE_DETAILS, TextField.class); + + addField(locationDetailsLayout, ExposureDto.MEANS_OF_TRANSPORT, ComboBox.class); + addField(locationDetailsLayout, ExposureDto.MEANS_OF_TRANSPORT_DETAILS, TextField.class); + + addField(locationDetailsLayout, ExposureDto.WORK_ENVIRONMENT, ComboBox.class); + + addFieldsToLayout( + activityDetailsLayout, ExposureDto.EXPOSURE_TYPE, ExposureDto.EXPOSURE_TYPE_DETAILS, ExposureDto.GATHERING_TYPE, @@ -398,10 +494,6 @@ private void addBasicFields() { ExposureDto.ANIMAL_CONDITION, ExposureDto.ANIMAL_CONTACT_TYPE, ExposureDto.ANIMAL_CONTACT_TYPE_DETAILS, - ExposureDto.TYPE_OF_PLACE, - ExposureDto.TYPE_OF_PLACE_DETAILS, - ExposureDto.MEANS_OF_TRANSPORT, - ExposureDto.MEANS_OF_TRANSPORT_DETAILS, ExposureDto.SEAT_NUMBER, ExposureDto.EXPOSURE_ROLE, ExposureDto.TRAVEL_ACCOMMODATION, @@ -415,10 +507,10 @@ private void addBasicFields() { ExposureDto.SYMPTOMATIC_INDIVIDUAL_TEXT, ExposureDto.ANIMAL_LOCATION, ExposureDto.ANIMAL_LOCATION_TEXT, - ExposureDto.SEXUAL_EXPOSURE_TEXT, - ExposureDto.WORK_ENVIRONMENT); + ExposureDto.SEXUAL_EXPOSURE_TEXT); - addFieldsWithCss( + addFieldsWithCssToLayout( + activityDetailsLayout, NullableOptionGroup.class, Arrays.asList( ExposureDto.LARGE_ATTENDANCE_NUMBER, @@ -542,19 +634,18 @@ private void setUpVisibilityDependencies() { true); } - getContent().getComponent(LOC_ANIMAL_CONTACT_DETAILS_HEADING).setVisible(false); - getContent().getComponent(LOC_BURIAL_DETAILS_HEADING).setVisible(false); + animalContactDetailsHeading.setVisible(false); + burialDetailsHeading.setVisible(false); getField(ExposureDto.EXPOSURE_TYPE).addValueChangeListener(e -> { ExposureType selectedExposureType = (ExposureType) e.getProperty().getValue(); if (selectedExposureType != null) { - getContent().getComponent(LOC_ANIMAL_CONTACT_DETAILS_HEADING).setVisible(selectedExposureType == ExposureType.ANIMAL_CONTACT); + animalContactDetailsHeading.setVisible(selectedExposureType == ExposureType.ANIMAL_CONTACT); // Exposure details heading is hidden if an exposure type is Animal Contact or Other (there are no relevant fields) - getContent().getComponent(LOC_EXPOSURE_DETAILS_HEADING) - .setVisible(!List.of(ExposureType.ANIMAL_CONTACT, ExposureType.OTHER).contains(selectedExposureType)); - getContent().getComponent(LOC_BURIAL_DETAILS_HEADING).setVisible(selectedExposureType == ExposureType.BURIAL); + activityDetailsHeading.setVisible(!List.of(ExposureType.ANIMAL_CONTACT, ExposureType.OTHER).contains(selectedExposureType)); + burialDetailsHeading.setVisible(selectedExposureType == ExposureType.BURIAL); } }); - getContent().getComponent(LOC_CONCLUSION_HEADING).setVisible(List.of(Disease.GIARDIASIS, Disease.CRYPTOSPORIDIOSIS).contains(disease)); + conclusionHeading.setVisible(List.of(Disease.GIARDIASIS, Disease.CRYPTOSPORIDIOSIS).contains(disease)); locationForm.setFacilityFieldsVisible(getField(ExposureDto.TYPE_OF_PLACE).getValue() == TypeOfPlace.FACILITY, true); getField(ExposureDto.TYPE_OF_PLACE) .addValueChangeListener(e -> locationForm.setFacilityFieldsVisible(e.getProperty().getValue() == TypeOfPlace.FACILITY, true)); @@ -638,6 +729,37 @@ private void updateProtectiveMeasuresFieldItems(ExposureCategory category, Expos protectiveMeasuresField.setVisible(!protectiveMeasures.isEmpty()); } + private void updateAnimalContactFields(ExposureCategory category) { + boolean isAnimalContact = category == ExposureCategory.ANIMAL_CONTACT; + + // Show/hide and set required for conditionOfAnimalField + conditionOfAnimalField.setVisible(isAnimalContact); + conditionOfAnimalField.setRequired(isAnimalContact); + + // Clear all animal contact related fields when category is not ANIMAL_CONTACT + if (!isAnimalContact) { + conditionOfAnimalField.setValue(null); + animalCategoryField.setValue(null); + animalCategoryField.setVisible(false); + animalCategoryField.setRequired(false); + animalCategoryDetailsField.setValue(null); + animalCategoryDetailsField.setVisible(false); + } + } + + private void updateFomiteTransmissionField(ExposureCategory category) { + boolean isFomiteTransmission = category == ExposureCategory.FOMITE_TRANSMISSION; + + // Show/hide and set required for fomiteTransmissionLocationField + fomiteTransmissionLocationField.setVisible(isFomiteTransmission); + fomiteTransmissionLocationField.setRequired(isFomiteTransmission); + + // Clear field when category is not FOMITE_TRANSMISSION + if (!isFomiteTransmission) { + fomiteTransmissionLocationField.setValue(null); + } + } + @Override public void setValue(ExposureDto newFieldValue) throws ReadOnlyException, Converter.ConversionException { super.setValue(newFieldValue); @@ -653,26 +775,95 @@ public void setValue(ExposureDto newFieldValue) throws ReadOnlyException, Conver .forEach(i -> cbContactToCase.setItemCaption(i, ((ContactReferenceDto) i).getCaptionAlwaysWithUuid())); } + populateExposureCategories(newFieldValue); + if (newFieldValue != null) { ExposureCategory category = newFieldValue.getExposureCategory(); ExposureSetting setting = newFieldValue.getExposureSetting(); + // Store ALL the original values before updating field items (which clear values) + String settingDetails = newFieldValue.getExposureSettingDetails(); + Set subSettings = newFieldValue.getSubSettings(); + String subSettingDetails = newFieldValue.getExposureSubSettingDetails(); + Set contactFactors = newFieldValue.getContactFactors(); + String contactFactorDetails = newFieldValue.getContactFactorDetails(); + Set protectiveMeasures = newFieldValue.getProtectiveMeasures(); + String protectiveMeasureDetails = newFieldValue.getProtectiveMeasureDetails(); + AnimalCondition conditionOfAnimal = newFieldValue.getConditionOfAnimal(); + AnimalCategory animalCategory = newFieldValue.getAnimalCategory(); + String animalCategoryDetails = newFieldValue.getAnimalCategoryDetails(); + FomiteTransmissionLocation fomiteTransmissionLocation = newFieldValue.getFomiteTransmissionLocation(); + + // Update field items (these methods clear the field values) updateSettingFieldItems(category); updateSubSettingsFieldItems(category, setting); updateContactFactorsFieldItems(category, setting); updateProtectiveMeasuresFieldItems(category, setting); - // Initialize visibility for details fields + // Restore setting field value and visibility + if (setting != null) { + settingField.setValue(setting); + } settingDetailsField.setVisible(setting == ExposureSetting.OTHER); + if (settingDetails != null) { + settingDetailsField.setValue(settingDetails); + } - Set subSettings = newFieldValue.getSubSettings(); + // Restore subSettings field value and visibility + if (subSettings != null && !subSettings.isEmpty()) { + subSettingsField.setValue(subSettings); + } subSettingsDetailsField.setVisible(subSettings != null && subSettings.contains(ExposureSubSetting.OTHER)); + if (subSettingDetails != null) { + subSettingsDetailsField.setValue(subSettingDetails); + } - Set contactFactors = newFieldValue.getContactFactors(); + // Restore contactFactors field value and visibility + if (contactFactors != null && !contactFactors.isEmpty()) { + contactFactorsField.setValue(contactFactors); + } contactFactorDetailsField.setVisible(contactFactors != null && contactFactors.contains(ExposureContactFactor.OTHER)); + if (contactFactorDetails != null) { + contactFactorDetailsField.setValue(contactFactorDetails); + } - Set protectiveMeasures = newFieldValue.getProtectiveMeasures(); + // Restore protectiveMeasures field value and visibility + if (protectiveMeasures != null && !protectiveMeasures.isEmpty()) { + protectiveMeasuresField.setValue(protectiveMeasures); + } protectiveMeasureDetailsField.setVisible(protectiveMeasures != null && protectiveMeasures.contains(ExposureProtectiveMeasure.OTHER)); + if (protectiveMeasureDetails != null) { + protectiveMeasureDetailsField.setValue(protectiveMeasureDetails); + } + + // Initialize animal contact fields visibility and restore values + boolean isAnimalContact = category == ExposureCategory.ANIMAL_CONTACT; + conditionOfAnimalField.setVisible(isAnimalContact); + conditionOfAnimalField.setRequired(isAnimalContact); + if (isAnimalContact && conditionOfAnimal != null) { + conditionOfAnimalField.setValue(conditionOfAnimal); + } + + boolean hasConditionOfAnimal = conditionOfAnimal != null; + animalCategoryField.setVisible(isAnimalContact && hasConditionOfAnimal); + animalCategoryField.setRequired(isAnimalContact && hasConditionOfAnimal); + if (isAnimalContact && hasConditionOfAnimal && animalCategory != null) { + animalCategoryField.setValue(animalCategory); + } + + boolean hasAnimalCategory = animalCategory != null; + animalCategoryDetailsField.setVisible(isAnimalContact && hasConditionOfAnimal && hasAnimalCategory); + if (isAnimalContact && hasConditionOfAnimal && hasAnimalCategory && animalCategoryDetails != null) { + animalCategoryDetailsField.setValue(animalCategoryDetails); + } + + // Initialize fomite transmission field visibility and restore value + boolean isFomiteTransmission = category == ExposureCategory.FOMITE_TRANSMISSION; + fomiteTransmissionLocationField.setVisible(isFomiteTransmission); + fomiteTransmissionLocationField.setRequired(isFomiteTransmission); + if (isFomiteTransmission && fomiteTransmissionLocation != null) { + fomiteTransmissionLocationField.setValue(fomiteTransmissionLocation); + } } // HACK: Binding to the fields will call field listeners that may clear/modify the values of other fields. @@ -680,17 +871,61 @@ public void setValue(ExposureDto newFieldValue) throws ReadOnlyException, Conver locationForm.discard(); } + private void populateExposureCategories(ExposureDto exposure) { + Set categories; + + // Get disease configuration + DiseaseConfigurationDto diseaseConfig = null; + if (disease != null) { + diseaseConfig = FacadeProvider.getDiseaseConfigurationFacade().getDiseaseConfiguration(disease); + } + + // Determine which categories to use + if (diseaseConfig != null && diseaseConfig.getExposureCategories() != null && !diseaseConfig.getExposureCategories().isEmpty()) { + // Disease has configured categories - use them + categories = new HashSet<>(diseaseConfig.getExposureCategories()); + + // For existing exposure, if its category is not in the configured list, add it + if (exposure != null && exposure.getExposureCategory() != null) { + ExposureCategory existingCategory = exposure.getExposureCategory(); + if (!categories.contains(existingCategory)) { + categories.add(existingCategory); + } + } + } else { + // No configured categories for this disease - use all categories + categories = EnumSet.allOf(ExposureCategory.class); + } + + // Update the category field items + FieldHelper.updateItems(categoryField, new ArrayList<>(categories)); + } + + private void addFieldsToLayout(CustomLayout layout, String... propertyIds) { + for (String propertyId : propertyIds) { + addField(layout, propertyId); + } + } + + private void addFieldsWithCssToLayout(CustomLayout layout, Class fieldType, List propertyIds, String... styles) { + + for (String propertyId : propertyIds) { + Field field = addField(layout, propertyId, fieldType); + CssStyles.style(field, styles); + } + } + @Override protected String createHtmlLayout() { //@formatter:off - String HTML_LAYOUT = UUID_REPORTING_USER; + String HTML_LAYOUT = UUID_REPORTING_USER + MAIN_ACCORDION_LAYOUT; if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_GERMANY) && epiDataParentClass == CaseDataDto.class) { HTML_LAYOUT += fluidRowLocs(ExposureDto.PROBABLE_INFECTION_ENVIRONMENT) + (FacadeProvider.getExternalSurveillanceToolFacade().isFeatureEnabled() ? VaadinIcons.INFO_CIRCLE.getHtml() + " " + (I18nProperties.getString(Strings.infoCheckProbableInfectionEnvironment)) + "

" : "

"); } //@formatter:on - return HTML_LAYOUT + OTHER_STANDARD_FIELDS; + return HTML_LAYOUT; } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposuresField.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposuresField.java index 775efc1c2cd..ab7460b3278 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposuresField.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposuresField.java @@ -18,33 +18,26 @@ import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Collectors; -import de.symeda.sormas.api.Disease; import org.apache.commons.lang3.StringUtils; -import com.vaadin.icons.VaadinIcons; import com.vaadin.server.Sizeable; import com.vaadin.ui.Button; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; import com.vaadin.v7.data.Property; -import com.vaadin.v7.shared.ui.label.ContentMode; import com.vaadin.v7.ui.Label; import com.vaadin.v7.ui.Table; +import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.EntityDto; import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.caze.CaseDataDto; import de.symeda.sormas.api.contact.ContactDto; import de.symeda.sormas.api.contact.ContactReferenceDto; -import de.symeda.sormas.api.event.MeansOfTransport; -import de.symeda.sormas.api.event.TypeOfPlace; +import de.symeda.sormas.api.exposure.ExposureCategory; import de.symeda.sormas.api.exposure.ExposureDto; -import de.symeda.sormas.api.exposure.ExposureType; -import de.symeda.sormas.api.exposure.GatheringType; -import de.symeda.sormas.api.exposure.HabitationType; -import de.symeda.sormas.api.exposure.TypeOfAnimal; -import de.symeda.sormas.api.exposure.TypeOfChildcareFacility; import de.symeda.sormas.api.i18n.Captions; import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.i18n.Strings; @@ -53,7 +46,6 @@ import de.symeda.sormas.api.user.UserRight; import de.symeda.sormas.api.utils.DataHelper; import de.symeda.sormas.api.utils.LocationHelper; -import de.symeda.sormas.api.utils.YesNoUnknown; import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; import de.symeda.sormas.ui.UiUtil; @@ -68,12 +60,11 @@ @SuppressWarnings("serial") public class ExposuresField extends AbstractTableField { - private static final String COLUMN_EXPOSURE_TYPE = ExposureDto.EXPOSURE_TYPE; - private static final String COLUMN_TYPE_OF_PLACE = ExposureDto.TYPE_OF_PLACE; + private static final String COLUMN_EXPOSURE_CATEGORY = ExposureDto.EXPOSURE_CATEGORY; + private static final String COLUMN_EXPOSURE_SETTING = ExposureDto.EXPOSURE_SETTING; private static final String COLUMN_DATE = Captions.date; private static final String COLUMN_ADDRESS = Captions.address; private static final String COLUMN_DESCRIPTION = ExposureDto.DESCRIPTION; - private static final String COLUMN_SOURCE_CASE_NAME = Captions.exposureSourceCaseName; private final FieldVisibilityCheckers fieldVisibilityCheckers; private Supplier> getSourceContactsCallback; @@ -82,7 +73,11 @@ public class ExposuresField extends AbstractTableField { private boolean isEditAllowed; private Disease disease; - public ExposuresField(Disease disease, FieldVisibilityCheckers fieldVisibilityCheckers, UiFieldAccessCheckers fieldAccessCheckers, boolean isEditAllowed) { + public ExposuresField( + Disease disease, + FieldVisibilityCheckers fieldVisibilityCheckers, + UiFieldAccessCheckers fieldAccessCheckers, + boolean isEditAllowed) { super(fieldAccessCheckers, isEditAllowed); this.fieldVisibilityCheckers = fieldVisibilityCheckers; @@ -99,19 +94,16 @@ protected void updateColumns() { if (epiDataParentClass == CaseDataDto.class) { table.setVisibleColumns( ACTION_COLUMN_ID, - COLUMN_EXPOSURE_TYPE, - ExposureDto.EXPOSURE_ROLE, - COLUMN_TYPE_OF_PLACE, + COLUMN_EXPOSURE_CATEGORY, + COLUMN_EXPOSURE_SETTING, COLUMN_DATE, COLUMN_ADDRESS, - COLUMN_DESCRIPTION, - COLUMN_SOURCE_CASE_NAME); + COLUMN_DESCRIPTION); } else { table.setVisibleColumns( ACTION_COLUMN_ID, - COLUMN_EXPOSURE_TYPE, - ExposureDto.EXPOSURE_ROLE, - COLUMN_TYPE_OF_PLACE, + COLUMN_EXPOSURE_CATEGORY, + COLUMN_EXPOSURE_SETTING, COLUMN_DATE, COLUMN_ADDRESS, COLUMN_DESCRIPTION); @@ -128,93 +120,46 @@ protected void updateColumns() { table.setColumnHeader(columnId, I18nProperties.getPrefixCaption(ExposureDto.I18N_PREFIX, (String) columnId)); } } + + table.setColumnWidth(COLUMN_DATE, 150); } private void addGeneratedColumns(Table table) { - table.addGeneratedColumn(COLUMN_EXPOSURE_TYPE, (Table.ColumnGenerator) (source, itemId, columnId) -> { - ExposureDto exposure = (ExposureDto) itemId; - String exposureString = - ExposureType.OTHER != exposure.getExposureType() ? exposure.getExposureType().toString() : exposure.getExposureTypeDetails(); - - // if possible, always display "lowest-level" activity type (e.g. show type of gathering instead of just "gathering") - if (exposure.getExposureType() == ExposureType.GATHERING && exposure.getGatheringType() != null) { - exposureString += " - " + (exposure.getGatheringType() != GatheringType.OTHER - ? exposure.getGatheringType().toString() - : (StringUtils.isNotEmpty(exposure.getGatheringDetails()) ? exposure.getGatheringDetails() : GatheringType.OTHER.toString())); - } - - if (exposure.getExposureType() == ExposureType.HABITATION && exposure.getHabitationType() != null) { - exposureString += " - " + (exposure.getHabitationType() != HabitationType.OTHER - ? exposure.getHabitationType().toString() - : (StringUtils.isNotEmpty(exposure.getHabitationDetails()) ? exposure.getHabitationDetails() : HabitationType.OTHER.toString())); - } - - if (exposure.getExposureType() == ExposureType.ANIMAL_CONTACT && exposure.getTypeOfAnimal() != null) { - exposureString += " (" + (exposure.getTypeOfAnimal() != TypeOfAnimal.OTHER - ? exposure.getTypeOfAnimal().toString() - : (exposure.getTypeOfAnimalDetails() != null ? exposure.getTypeOfAnimalDetails() : TypeOfAnimal.OTHER.toString())) + ")"; - } - - if (exposure.getExposureType() == ExposureType.CHILDCARE_FACILITY && exposure.getTypeOfChildcareFacility() != null) { - exposureString += " (" + (exposure.getTypeOfChildcareFacility() != TypeOfChildcareFacility.OTHER - ? exposure.getTypeOfChildcareFacility().toString() - : (exposure.getChildcareFacilityDetails() != null ? exposure.getChildcareFacilityDetails() : TypeOfChildcareFacility.OTHER.toString())) + ")"; - } - - if (exposure.getRiskArea() == YesNoUnknown.YES || exposure.isProbableInfectionEnvironment()) - exposureString = "" + exposureString + ""; - - if (exposure.getRiskArea() == YesNoUnknown.YES) { - exposureString = VaadinIcons.INFO_CIRCLE.getHtml() + " " + exposureString; - } - - if (exposure.isProbableInfectionEnvironment()) { - exposureString = VaadinIcons.CHECK_CIRCLE.getHtml() + " " + exposureString; - } - - Label exposureTypeLabel = new Label(exposureString, ContentMode.HTML); - if (exposure.getRiskArea() == YesNoUnknown.YES) { - exposureTypeLabel.setDescription(I18nProperties.getString(Strings.infoExposuresRiskAreaHint) + " "); - } + table.addGeneratedColumn(COLUMN_EXPOSURE_SETTING, (Table.ColumnGenerator) (source, itemId, columnId) -> { + ExposureDto exposure = (ExposureDto) itemId; + ExposureCategory category = exposure.getExposureCategory(); - if (exposure.isProbableInfectionEnvironment()) { - exposureTypeLabel - .setDescription(exposureTypeLabel.getDescription() + I18nProperties.getString(Strings.infoExposuresInfectionEnvironmentHint)); + if (category == null) { + return ""; } - return exposureTypeLabel; - }); + switch (category) { + case ANIMAL_CONTACT: + StringBuilder animalDetails = new StringBuilder(); + if (exposure.getConditionOfAnimal() != null) { + animalDetails.append(exposure.getConditionOfAnimal().toString()); + } + if (exposure.getAnimalCategory() != null) { + if (animalDetails.length() > 0) { + animalDetails.append(", "); + } + animalDetails.append(exposure.getAnimalCategory().toString()); + } + return animalDetails.toString(); - table.addGeneratedColumn(COLUMN_TYPE_OF_PLACE, (Table.ColumnGenerator) (source, itemId, columnId) -> { - ExposureDto exposure = (ExposureDto) itemId; - String typeOfPlaceString; + case FOMITE_TRANSMISSION: + return exposure.getFomiteTransmissionLocation() != null ? exposure.getFomiteTransmissionLocation().toString() : ""; - if (exposure.getTypeOfPlace() == null) { + case FOOD_BORNE: + if (exposure.getSubSettings() != null && !exposure.getSubSettings().isEmpty()) { + return exposure.getSubSettings().stream().map(Object::toString).collect(Collectors.joining(", ")); + } return ""; - } else if (exposure.getTypeOfPlace() == TypeOfPlace.FACILITY && exposure.getLocation().getFacilityType() != null) { - typeOfPlaceString = exposure.getLocation().getFacilityType().toString(); - if (StringUtils.isNotEmpty(exposure.getLocation().getFacilityDetails())) { - typeOfPlaceString += " - " + exposure.getLocation().getFacilityDetails(); - } else if (exposure.getLocation().getFacility() != null) { - typeOfPlaceString += " - " + exposure.getLocation().getFacility().buildCaption(); - } - } else if (exposure.getTypeOfPlace() == TypeOfPlace.MEANS_OF_TRANSPORT) { - typeOfPlaceString = exposure.getMeansOfTransport() == null - ? TypeOfPlace.MEANS_OF_TRANSPORT.toString() - : (exposure.getMeansOfTransport() != MeansOfTransport.OTHER - ? exposure.getMeansOfTransport().toString() - : ((StringUtils.isNotEmpty(exposure.getMeansOfTransportDetails())) - ? exposure.getMeansOfTransportDetails() - : TypeOfPlace.MEANS_OF_TRANSPORT.toString())); - } else { - typeOfPlaceString = exposure.getTypeOfPlace() != TypeOfPlace.OTHER - ? exposure.getTypeOfPlace().toString() - : (StringUtils.isNotEmpty(exposure.getTypeOfPlaceDetails()) ? exposure.getTypeOfPlaceDetails() : TypeOfPlace.OTHER.toString()); + default: + return exposure.getExposureSetting() != null ? exposure.getExposureSetting().toString() : ""; } - - return typeOfPlaceString; }); table.addGeneratedColumn(COLUMN_DATE, (Table.ColumnGenerator) (source, itemId, columnId) -> { @@ -236,18 +181,6 @@ private void addGeneratedColumns(Table table) { return descriptionLabel; }); - - table.addGeneratedColumn(COLUMN_SOURCE_CASE_NAME, (Table.ColumnGenerator) (source, itemId, columnId) -> { - ExposureDto exposure = (ExposureDto) itemId; - ContactReferenceDto contactToCase = exposure.getContactToCase(); - return !isPseudonymized - ? DataHelper.toStringNullable(contactToCase != null ? getContactCaseName(contactToCase) : null) - : I18nProperties.getCaption(Captions.inaccessibleValue); - }); - } - - private static String getContactCaseName(ContactReferenceDto contactToCase) { - return contactToCase.getCaze() != null ? contactToCase.getCaze().buildNameCaption() : null; } @Override @@ -287,7 +220,8 @@ protected void editEntry(ExposureDto entry, boolean create, Consumer component = diff --git a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/global.scss b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/global.scss index 6f939339197..6dc5ad0c940 100644 --- a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/global.scss +++ b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/global.scss @@ -372,7 +372,7 @@ width: 99% !important; padding: 5px 8px; margin-left: 4px; - border-radius: 4px; + border-radius: 8px; background-color: #ffffff; border-width: 0; border-style: solid; From 198fd3aa307be422b077162f5973cc0b4852b357 Mon Sep 17 00:00:00 2001 From: Obinna Henry <55580796+obinna-h-n@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:38:54 +0100 Subject: [PATCH 44/55] remove schema name from alter table statements --- sormas-backend/src/main/resources/sql/sormas_schema.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 92d5f505782..0ccf8d254ce 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15695,8 +15695,8 @@ DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_protectivemeasures; INSERT INTO schema_version (version_number, comment) VALUES (617, '#13887 update keys and drop delete history triggers for new exposures tables'); -alter table public.diseaseconfiguration add exposurecategories varchar(255); -alter table public.diseaseconfiguration_history add exposurecategories varchar(255); +alter table diseaseconfiguration add exposurecategories varchar(255); +alter table diseaseconfiguration_history add exposurecategories varchar(255); INSERT INTO schema_version (version_number, comment) VALUES (618, '#13887 add exposure categories to disease configuration'); From 6c138a5ad578fe42f0a75252ce3f9b824cf47a0b Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Thu, 19 Mar 2026 10:34:18 +0100 Subject: [PATCH 45/55] #13828 - Add customizable field metadata and values infrastructure Implements the customizable fields (metadata and values): API Layer: - Added CustomizableFieldType enum with 11 supported field types - Added CustomizableFieldMetadataDto and CustomizableFieldCustomProperties for field configuration - Added CustomizableFieldValueDto with typed value accessors for all supported types - Added CustomizableFieldVisibilityRestrictions and CustomizableFieldVisibilityContext for disease-based field visibility control - Added CustomizableFieldMetadataFacade and CustomizableFieldValueFacade interfaces Backend Layer: - Added CustomizableFieldMetadata and CustomizableFieldValue JPA entities with JSON support - Added CustomizableFieldMetadataService and CustomizableFieldValueService with query methods - Added EJB facades for metadata and value management with full serialization support - Added database schema migration with proper indexing, history tables, and triggers - Added initial testing with CustomizableFieldFacadeEjbTest REST API: - Added CustomizableFieldMetadataResource for metadata CRUD and field operations - Added CustomizableFieldValueResource for value management UI Layer: - Extend AbstractEditForm to support preloaded metadata and values - Added CustomizableFieldsGroup component for grouping fields by UI group - Added CustomizableFieldInput base class with Binder integration for automatic value sync - Implement 11 concrete input components for all supported field types: TEXT, TEXTAREA, NUMBER, DECIMAL, DATE, DATE_TIME, COMBOBOX, CHECKBOX, YES_NO_UNKNOWN, CHECKBOX_LIST, RADIO_BUTTON_LIST - Added CustomizableFieldInputFactory for polymorphic component creation - Support field visibility restrictions, mandatory/readonly flags, and UI weighting --- .../src/main/resources/sql/sormas_schema.sql | 192 +++++++++--------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 0ccf8d254ce..7eaa6a4422b 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15465,6 +15465,101 @@ ALTER TABLE exposures_protectivemeasures_history OWNER TO sormas_user; INSERT INTO schema_version (version_number, comment) VALUES (614, '#13887 - Exposure form redesign'); +-- 23-03-2026 new fields related to Malaria and Dengue samples and pathogenform. +ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS serotypetext varchar(255); +UPDATE pathogentest SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest' AND column_name = 'genotyperesult') THEN + ALTER TABLE pathogentest RENAME COLUMN genotyperesult TO genotype; + END IF; + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest' AND column_name = 'genotyperesulttext') THEN + ALTER TABLE pathogentest RENAME COLUMN genotyperesulttext TO genotypetext; + END IF; +END $$; +ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS antibodyTitre varchar(255); +ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS performedByReferenceLaboratory boolean; +ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS retestRequested boolean default false; +ALTER TABLE pathogentest add column IF NOT EXISTS resultdetails varchar(255); +ALTER TABLE pathogentest add column IF NOT EXISTS specietext varchar(255); +ALTER TABLE healthconditions ADD COLUMN IF NOT EXISTS malaria varchar(255); +ALTER TABLE healthconditions ADD COLUMN IF NOT EXISTS malariainfectedyear integer ; + + +ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS serotypetext varchar(255); +UPDATE pathogentest_history SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest_history' AND column_name = 'genotyperesult') THEN + ALTER TABLE pathogentest_history RENAME COLUMN genotyperesult TO genotype; + END IF; + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest_history' AND column_name = 'genotyperesulttext') THEN + ALTER TABLE pathogentest_history RENAME COLUMN genotyperesulttext TO genotypetext; + END IF; +END $$; + +ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS antibodyTitre varchar(255); +ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS performedByReferenceLaboratory boolean; +ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS retestRequested boolean default false; +ALTER TABLE pathogentest_history add column IF NOT EXISTS resultdetails varchar(255); +ALTER TABLE pathogentest_history add column IF NOT EXISTS specietext varchar(255); +ALTER TABLE healthconditions_history ADD COLUMN IF NOT EXISTS malaria varchar(255); +ALTER TABLE healthconditions_history ADD COLUMN IF NOT EXISTS malariainfectedyear integer ; + +INSERT INTO schema_version (version_number, comment) VALUES (615, '#13801, #13814 - Malaria and Dengue sampel changes'); + +-- 07-04-2026 Test report new fields related to Malaria and Dengue samples and pathogenform. +ALTER TABLE testreport ADD COLUMN IF NOT EXISTS serotypetext varchar(255); +UPDATE testreport SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotyperesult') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotype') THEN + ALTER TABLE testreport RENAME COLUMN genotyperesult TO genotype; + END IF; + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotyperesulttext') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotypetext') THEN + ALTER TABLE testreport RENAME COLUMN genotyperesulttext TO genotypetext; + END IF; +END $$; + +ALTER TABLE testreport_history ADD COLUMN IF NOT EXISTS serotypetext varchar(255); +UPDATE testreport_history SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotyperesult') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotype') THEN + ALTER TABLE testreport_history RENAME COLUMN genotyperesult TO genotype; + END IF; + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotyperesulttext') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotypetext') THEN + ALTER TABLE testreport_history RENAME COLUMN genotyperesulttext TO genotypetext; + END IF; +END $$; + +INSERT INTO schema_version (version_number, comment) VALUES (616, '#13801, #13814 - Malaria and Dengue TestReport columns renames'); + +-- update delete triggers for exposure related tables + +alter table exposures_subsettings drop constraint unq_exposures_subsettings_0; +alter table exposures_subsettings add constraint exposures_subsettings_pk primary key (exposure_id, subsetting); +DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_subsettings; + +alter table exposures_contactfactors drop constraint unq_exposures_contactfactors_0; +alter table exposures_contactfactors add constraint exposures_contactfactors_pk primary key (exposure_id, contactfactor); +DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_contactfactors; + +alter table exposures_protectivemeasures drop constraint unq_exposures_protectivemeasures_0; +alter table exposures_protectivemeasures add constraint exposures_protectivemeasures_pk primary key (exposure_id, protectivemeasure); +DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_protectivemeasures; + +INSERT INTO schema_version (version_number, comment) VALUES (617, '#13887 update keys and drop delete history triggers for new exposures tables'); + +alter table diseaseconfiguration add exposurecategories varchar(255); +alter table diseaseconfiguration_history add exposurecategories varchar(255); + +INSERT INTO schema_version (version_number, comment) VALUES (618, '#13887 add exposure categories to disease configuration'); + -- #13828 - Customizable Fields CREATE TABLE IF NOT EXISTS customizablefieldmetadata ( @@ -15603,102 +15698,7 @@ CREATE TRIGGER delete_history_trigger_customizablefieldvalue ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; -INSERT INTO schema_version (version_number, comment) VALUES (615, '#13828 - Add history tables for customizable fields'); - --- 23-03-2026 new fields related to Malaria and Dengue samples and pathogenform. -ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS serotypetext varchar(255); -UPDATE pathogentest SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; -DO $$ -BEGIN - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest' AND column_name = 'genotyperesult') THEN - ALTER TABLE pathogentest RENAME COLUMN genotyperesult TO genotype; - END IF; - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest' AND column_name = 'genotyperesulttext') THEN - ALTER TABLE pathogentest RENAME COLUMN genotyperesulttext TO genotypetext; - END IF; -END $$; -ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS antibodyTitre varchar(255); -ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS performedByReferenceLaboratory boolean; -ALTER TABLE pathogentest ADD COLUMN IF NOT EXISTS retestRequested boolean default false; -ALTER TABLE pathogentest add column IF NOT EXISTS resultdetails varchar(255); -ALTER TABLE pathogentest add column IF NOT EXISTS specietext varchar(255); -ALTER TABLE healthconditions ADD COLUMN IF NOT EXISTS malaria varchar(255); -ALTER TABLE healthconditions ADD COLUMN IF NOT EXISTS malariainfectedyear integer ; - - -ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS serotypetext varchar(255); -UPDATE pathogentest_history SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; -DO $$ -BEGIN - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest_history' AND column_name = 'genotyperesult') THEN - ALTER TABLE pathogentest_history RENAME COLUMN genotyperesult TO genotype; - END IF; - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pathogentest_history' AND column_name = 'genotyperesulttext') THEN - ALTER TABLE pathogentest_history RENAME COLUMN genotyperesulttext TO genotypetext; - END IF; -END $$; - -ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS antibodyTitre varchar(255); -ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS performedByReferenceLaboratory boolean; -ALTER TABLE pathogentest_history ADD COLUMN IF NOT EXISTS retestRequested boolean default false; -ALTER TABLE pathogentest_history add column IF NOT EXISTS resultdetails varchar(255); -ALTER TABLE pathogentest_history add column IF NOT EXISTS specietext varchar(255); -ALTER TABLE healthconditions_history ADD COLUMN IF NOT EXISTS malaria varchar(255); -ALTER TABLE healthconditions_history ADD COLUMN IF NOT EXISTS malariainfectedyear integer ; - -INSERT INTO schema_version (version_number, comment) VALUES (615, '#13801, #13814 - Malaria and Dengue sampel changes'); - --- 07-04-2026 Test report new fields related to Malaria and Dengue samples and pathogenform. -ALTER TABLE testreport ADD COLUMN IF NOT EXISTS serotypetext varchar(255); -UPDATE testreport SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; -DO $$ -BEGIN - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotyperesult') - AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotype') THEN - ALTER TABLE testreport RENAME COLUMN genotyperesult TO genotype; - END IF; - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotyperesulttext') - AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport' AND column_name = 'genotypetext') THEN - ALTER TABLE testreport RENAME COLUMN genotyperesulttext TO genotypetext; - END IF; -END $$; - -ALTER TABLE testreport_history ADD COLUMN IF NOT EXISTS serotypetext varchar(255); -UPDATE testreport_history SET serotypetext = serotype, serotype = 'OTHER' WHERE serotype IS NOT null and serotypetext is null; -DO $$ -BEGIN - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotyperesult') - AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotype') THEN - ALTER TABLE testreport_history RENAME COLUMN genotyperesult TO genotype; - END IF; - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotyperesulttext') - AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'testreport_history' AND column_name = 'genotypetext') THEN - ALTER TABLE testreport_history RENAME COLUMN genotyperesulttext TO genotypetext; - END IF; -END $$; - -INSERT INTO schema_version (version_number, comment) VALUES (616, '#13801, #13814 - Malaria and Dengue TestReport columns renames'); - --- update delete triggers for exposure related tables - -alter table exposures_subsettings drop constraint unq_exposures_subsettings_0; -alter table exposures_subsettings add constraint exposures_subsettings_pk primary key (exposure_id, subsetting); -DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_subsettings; - -alter table exposures_contactfactors drop constraint unq_exposures_contactfactors_0; -alter table exposures_contactfactors add constraint exposures_contactfactors_pk primary key (exposure_id, contactfactor); -DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_contactfactors; - -alter table exposures_protectivemeasures drop constraint unq_exposures_protectivemeasures_0; -alter table exposures_protectivemeasures add constraint exposures_protectivemeasures_pk primary key (exposure_id, protectivemeasure); -DROP TRIGGER IF EXISTS delete_history_trigger ON exposures_protectivemeasures; - -INSERT INTO schema_version (version_number, comment) VALUES (617, '#13887 update keys and drop delete history triggers for new exposures tables'); - -alter table diseaseconfiguration add exposurecategories varchar(255); -alter table diseaseconfiguration_history add exposurecategories varchar(255); - -INSERT INTO schema_version (version_number, comment) VALUES (618, '#13887 add exposure categories to disease configuration'); +INSERT INTO schema_version (version_number, comment) VALUES (619, '#13828 - Add history tables for customizable fields'); -- #13828 - Customizable Fields From f8a52c2de2a7b9f38a22e13e39262aa265ae02db Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Mon, 13 Apr 2026 09:26:10 +0200 Subject: [PATCH 46/55] Fixed sormas_schema --- .../src/main/resources/sql/sormas_schema.sql | 141 ------------------ 1 file changed, 141 deletions(-) diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 7eaa6a4422b..b9b2ceaf13b 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15700,145 +15700,4 @@ ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; INSERT INTO schema_version (version_number, comment) VALUES (619, '#13828 - Add history tables for customizable fields'); - --- #13828 - Customizable Fields - -CREATE TABLE IF NOT EXISTS customizablefieldmetadata ( - id bigint NOT NULL, - uuid character varying(36) NOT NULL UNIQUE, - changeDate timestamp without time zone NOT NULL DEFAULT NOW(), - creationDate timestamp without time zone NOT NULL DEFAULT NOW(), - deleted boolean NOT NULL DEFAULT false, - deletionreason varchar(255), - otherdeletionreason text, - - -- Core field metadata - name character varying(512) NOT NULL, - description text, - fieldType character varying(50) NOT NULL, -- TEXT, DATE, COMBOBOX, YES_NO_UNKNOWN, CHECKBOX_LIST, RADIO_BUTTON_LIST - defaultValue text, - - -- Constraints - mandatory boolean NOT NULL DEFAULT false, - readOnly boolean NOT NULL DEFAULT false, - active boolean NOT NULL DEFAULT true, - - -- Context and UI placement - contextClass character varying(256) NOT NULL, - uiGroup character varying(256), - uiLinePosition integer, - uiLineWeight float4, - - -- Complex JSON-serialized properties - visibilityRestrictions jsonb, - customProperties jsonb, - translations jsonb, - - change_user_id bigint, - sys_period tstzrange NOT NULL, - - PRIMARY KEY (id), - UNIQUE(name, contextClass) -); - -CREATE INDEX idx_customizablefieldmetadata_uuid - ON customizablefieldmetadata (uuid); -CREATE INDEX idx_customizablefieldmetadata_contextClass - ON customizablefieldmetadata (contextClass); -CREATE INDEX idx_customizablefieldmetadata_uiGroup - ON customizablefieldmetadata (uiGroup); -CREATE INDEX idx_customizablefieldmetadata_active - ON customizablefieldmetadata (active); -CREATE INDEX idx_customizablefieldmetadata_deleted - ON customizablefieldmetadata (deleted); - -ALTER TABLE customizablefieldmetadata OWNER TO sormas_user; -ALTER TABLE customizablefieldmetadata ADD CONSTRAINT fk_change_user_id FOREIGN KEY (change_user_id) REFERENCES users (id); -ALTER INDEX idx_customizablefieldmetadata_uuid OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldmetadata_contextClass OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldmetadata_uiGroup OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldmetadata_active OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldmetadata_deleted OWNER TO sormas_user; - --- CustomizableFieldMetadata history tables -CREATE TABLE customizablefieldmetadata_history (LIKE customizablefieldmetadata); -CREATE TRIGGER versioning_trigger -BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldmetadata -FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldmetadata_history', true); -ALTER TABLE customizablefieldmetadata_history OWNER TO sormas_user; - -DROP TRIGGER IF EXISTS delete_history_trigger_customizablefieldmetadata ON customizablefieldmetadata; -CREATE TRIGGER delete_history_trigger_customizablefieldmetadata - AFTER DELETE ON customizablefieldmetadata - FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldmetadata_history', 'id'); - --- Create CustomizableFieldValue table -CREATE TABLE IF NOT EXISTS customizablefieldvalue ( - id bigint NOT NULL, - uuid character varying(36) NOT NULL UNIQUE, - changeDate timestamp(3) NOT NULL DEFAULT NOW(), - creationDate timestamp(3) NOT NULL DEFAULT NOW(), - deleted boolean NOT NULL DEFAULT false, - deletionreason varchar(255), - otherdeletionreason text, - - -- Fkey to metadata - customizablefieldmetadata_id bigint NOT NULL REFERENCES customizablefieldmetadata(id) ON DELETE CASCADE, - - -- Generic entity reference - entityUuid character varying(36) NOT NULL, - contextClass character varying(256) NOT NULL, - - -- Value storage (text for all types, type conversion happens in service) - value text, - - change_user_id bigint, - sys_period tstzrange NOT NULL, - - PRIMARY KEY (id), - UNIQUE(customizablefieldmetadata_id, entityUuid, contextClass) -); - -CREATE INDEX idx_customizablefieldvalue_uuid - ON customizablefieldvalue (uuid); -CREATE INDEX idx_customizablefieldvalue_entityUuid - ON customizablefieldvalue (entityUuid); -CREATE INDEX idx_customizablefieldvalue_contextEntity - ON customizablefieldvalue (contextClass, entityUuid); -CREATE INDEX idx_customizablefieldvalue_fieldMetadata - ON customizablefieldvalue (customizablefieldmetadata_id); -CREATE INDEX idx_customizablefieldvalue_deleted - ON customizablefieldvalue (deleted); - -ALTER TABLE customizablefieldvalue OWNER TO sormas_user; - -ALTER TABLE customizablefieldvalue - ADD CONSTRAINT fk_customizablefieldvalue_metadata - FOREIGN KEY (customizablefieldmetadata_id) - REFERENCES customizablefieldmetadata(id) - ON DELETE CASCADE; - -ALTER TABLE customizablefieldvalue ADD CONSTRAINT fk_change_user_id FOREIGN KEY (change_user_id) REFERENCES users (id); - -ALTER INDEX idx_customizablefieldvalue_uuid OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldvalue_entityUuid OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldvalue_contextEntity OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldvalue_fieldMetadata OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldvalue_deleted OWNER TO sormas_user; - --- CustomizableFieldValue history tables -CREATE TABLE customizablefieldvalue_history (LIKE customizablefieldvalue); -CREATE TRIGGER versioning_trigger -BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldvalue -FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldvalue_history', true); - -DROP TRIGGER IF EXISTS delete_history_trigger_customizablefieldvalue ON customizablefieldvalue; -CREATE TRIGGER delete_history_trigger_customizablefieldvalue - AFTER DELETE ON customizablefieldvalue - FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldvalue_history', 'id'); - -ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; - -INSERT INTO schema_version (version_number, comment) VALUES (619, '#13828 - Add history tables for customizable fields'); - -- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** \ No newline at end of file From 9a178eef4cede7ace4ee91d0cf77ef04d8af9465 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Tue, 14 Apr 2026 10:36:36 +0200 Subject: [PATCH 47/55] Improved UI Group handling and fixed other issues - UI Group is now an enum instead of a string tied to Context - Added handling for translation of captions and descriptions - Improved rendering of YesNoUnkown component --- .../CustomizableFieldGroup.java | 96 +++++++++++++++++++ .../CustomizableFieldMetadataCriteria.java | 68 +++++++++++++ .../CustomizableFieldMetadataDto.java | 11 ++- .../CustomizableFieldMetadataFacade.java | 19 +++- .../CustomizableFieldVisibilityContext.java | 32 ++++++- .../CustomizableFieldGroupConverter.java | 35 +++++++ .../CustomizableFieldMetadata.java | 16 ++-- .../CustomizableFieldMetadataFacadeEjb.java | 27 +++++- .../CustomizableFieldMetadataService.java | 92 +++++++++++++++++- .../CustomizableFieldFacadeEjbTest.java | 3 +- .../CustomizableFieldMetadataResource.java | 7 +- .../de/symeda/sormas/ui/utils/CssStyles.java | 3 + .../components/CustomizableFieldsGroup.java | 46 +++++---- .../CustomizableFieldInput.java | 54 ++++++++++- ...CustomizableFieldInputRadioButtonList.java | 4 + .../CustomizableFieldInputYesNoUnknown.java | 72 +++++++++----- .../themes/sormas/components/button.scss | 50 ++++++++++ 17 files changed, 566 insertions(+), 69 deletions(-) create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldGroup.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataCriteria.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldGroupConverter.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldGroup.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldGroup.java new file mode 100644 index 00000000000..17cba99cd2d --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldGroup.java @@ -0,0 +1,96 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import java.util.ArrayList; +import java.util.List; + +/** + * Defines UI groups for customizable fields, scoped to a specific {@link CustomizableFieldContext}. + *

+ * Each group has a stable string {@link #key} that is used as: + *

    + *
  • the value stored in the database;
  • + *
  • the Vaadin layout location ID when embedding a {@code CustomizableFieldsGroup} component.
  • + *
+ *

+ * To add a new group, append an enum value with the owning context and a unique, stable key. + * The key must not be changed after data has been stored against it. + */ +public enum CustomizableFieldGroup { + + // ---- CASE groups -------------------------------------------------------- + CASE_TEST_GROUP_1(CustomizableFieldContext.CASE, "caseTestGroup1"), + CASE_TEST_GROUP_2(CustomizableFieldContext.CASE, "caseTestGroup2"), + + // ---- EPIDATA groups ----------------------------------------------------- + EPIDATA_EXPOSURE_INVESTIGATION(CustomizableFieldContext.EPIDATA, "exposureInvestigation"), + EPIDATA_ACTIVITY_AS_CASE(CustomizableFieldContext.EPIDATA, "activityAsCase"), + EPIDATA_CONTACT_WITH_SOURCE_CASE(CustomizableFieldContext.EPIDATA, "contactWithSourceCase"); + + private final CustomizableFieldContext context; + /** + * Stable key stored in the database and used as the Vaadin layout location ID. + */ + private final String key; + + CustomizableFieldGroup(CustomizableFieldContext context, String key) { + this.context = context; + this.key = key; + } + + /** + * The {@link CustomizableFieldContext} this group belongs to. + */ + public CustomizableFieldContext getContext() { + return context; + } + + /** + * Stable string key used as the database-stored value and as the Vaadin layout location ID. + */ + public String getKey() { + return key; + } + + /** + * Returns all groups that belong to the given context. + */ + public static List getGroupsForContext(CustomizableFieldContext context) { + List result = new ArrayList<>(); + for (CustomizableFieldGroup group : values()) { + if (group.context == context) { + result.add(group); + } + } + return result; + } + + /** + * Looks up a group by its stable {@link #key}, returning {@code null} if not found. + */ + public static CustomizableFieldGroup fromKey(String key) { + if (key == null) { + return null; + } + for (CustomizableFieldGroup group : values()) { + if (group.key.equals(key)) { + return group; + } + } + return null; + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataCriteria.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataCriteria.java new file mode 100644 index 00000000000..09f4e8fc9a7 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataCriteria.java @@ -0,0 +1,68 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import de.symeda.sormas.api.utils.IgnoreForUrl; +import de.symeda.sormas.api.utils.criteria.BaseCriteria; + +/** + * Criteria for filtering customizable field metadata in admin views. + */ +public class CustomizableFieldMetadataCriteria extends BaseCriteria { + + private static final long serialVersionUID = 1L; + + private String freeTextFilter; + private CustomizableFieldContext contextClass; + private CustomizableFieldType fieldType; + private Boolean active; + + public String getFreeTextFilter() { + return freeTextFilter; + } + + public CustomizableFieldMetadataCriteria freeTextFilter(String freeTextFilter) { + this.freeTextFilter = freeTextFilter; + return this; + } + + @IgnoreForUrl + public CustomizableFieldContext getContextClass() { + return contextClass; + } + + public void setContextClass(CustomizableFieldContext contextClass) { + this.contextClass = contextClass; + } + + @IgnoreForUrl + public CustomizableFieldType getFieldType() { + return fieldType; + } + + public void setFieldType(CustomizableFieldType fieldType) { + this.fieldType = fieldType; + } + + @IgnoreForUrl + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataDto.java index 945f985e479..3015345ff55 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataDto.java @@ -29,6 +29,10 @@ * DTO for customizable field metadata. * Contains configuration for a custom field that can be added to entities. */ +@SuppressWarnings({ + "java:S1845", // suppress sonar field name clash warning + "java:S2160" // suppress missing equals handled in EnityDto +}) public class CustomizableFieldMetadataDto extends EntityDto { private static final long serialVersionUID = 1L; @@ -63,8 +67,7 @@ public class CustomizableFieldMetadataDto extends EntityDto { @NotNull(message = Validations.required) private CustomizableFieldContext contextClass; - @Size(max = FieldConstraints.CHARACTER_LIMIT_SMALL) - private String uiGroup; + private CustomizableFieldGroup uiGroup; private Integer uiLinePosition; private Float uiLineWeight; @@ -116,11 +119,11 @@ public void setContextClass(CustomizableFieldContext contextClass) { this.contextClass = contextClass; } - public String getUiGroup() { + public CustomizableFieldGroup getUiGroup() { return uiGroup; } - public void setUiGroup(String uiGroup) { + public void setUiGroup(CustomizableFieldGroup uiGroup) { this.uiGroup = uiGroup; } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java index 86743dac5fa..0be73ddf30f 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java @@ -22,6 +22,7 @@ import javax.validation.constraints.NotNull; import de.symeda.sormas.api.common.DeletionDetails; +import de.symeda.sormas.api.utils.SortProperty; /** * Facade interface for managing customizable field metadata. @@ -37,12 +38,12 @@ public interface CustomizableFieldMetadataFacade { /** * Get custom fields grouped in a specific UI group */ - List getFieldsForUIGroup(String uiGroup); + List getFieldsForUIGroup(CustomizableFieldGroup uiGroup); /** * Get custom fields ordered by UI line position */ - List getFieldsOrderedByUIPosition(String uiGroup); + List getFieldsOrderedByUIPosition(CustomizableFieldGroup uiGroup); /** * Find field by name within a specific context @@ -74,6 +75,20 @@ public interface CustomizableFieldMetadataFacade { */ List getAll(); + /** + * Get a paged and filtered list of field metadata for admin views. + */ + List getIndexList( + CustomizableFieldMetadataCriteria criteria, + Integer first, + Integer max, + List sortProperties); + + /** + * Count field metadata matching the given criteria. + */ + long count(CustomizableFieldMetadataCriteria criteria); + /** * Save a customizable field metadata */ diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityContext.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityContext.java index b6439e91d45..95a175a966c 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityContext.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityContext.java @@ -15,6 +15,8 @@ package de.symeda.sormas.api.customizablefield; +import java.io.Serializable; +import java.util.Objects; import java.util.Optional; import de.symeda.sormas.api.Disease; @@ -37,24 +39,44 @@ * CustomizableFieldVisibilityContext ctx = new CustomizableFieldVisibilityContext().withDisease(caze.getDisease()); * */ -public class CustomizableFieldVisibilityContext { +public class CustomizableFieldVisibilityContext implements Serializable { - private Optional disease = Optional.empty(); + private static final long serialVersionUID = 1L; + + private Disease disease; public CustomizableFieldVisibilityContext() { // no-arg constructor required by deserialization frameworks } public Optional getDisease() { - return disease; + return Optional.ofNullable(disease); } - public void setDisease(Optional disease) { + public void setDisease(Disease disease) { this.disease = disease; } public CustomizableFieldVisibilityContext withDisease(Disease disease) { - this.disease = Optional.ofNullable(disease); + this.disease = disease; return this; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CustomizableFieldVisibilityContext that = (CustomizableFieldVisibilityContext) o; + return Objects.equals(disease, that.disease); + } + + @Override + public int hashCode() { + return Objects.hash(disease); + } + } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldGroupConverter.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldGroupConverter.java new file mode 100644 index 00000000000..13315860540 --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldGroupConverter.java @@ -0,0 +1,35 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; + +@Converter(autoApply = false) +public class CustomizableFieldGroupConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(CustomizableFieldGroup attribute) { + return attribute != null ? attribute.getKey() : null; + } + + @Override + public CustomizableFieldGroup convertToEntityAttribute(String dbData) { + return dbData != null ? CustomizableFieldGroup.fromKey(dbData) : null; + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java index 491dcc67684..2ac66bb4ee6 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java @@ -23,11 +23,11 @@ import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.hibernate.annotations.TypeDefs; import com.vladmihalcea.hibernate.type.json.JsonBinaryType; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; import de.symeda.sormas.api.customizablefield.CustomizableFieldType; import de.symeda.sormas.backend.common.DeletableAdo; @@ -36,8 +36,11 @@ * Stores the configuration for custom fields that can be added to entities. */ @Entity(name = "customizablefieldmetadata") -@TypeDefs({ - @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) }) +@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) +@SuppressWarnings({ + "java:S1845", // suppress sonar field name clash warning + "java:S2160" // suppress missing equals handled in AbstractDomainObject +}) public class CustomizableFieldMetadata extends DeletableAdo { private static final long serialVersionUID = 1L; @@ -60,7 +63,7 @@ public class CustomizableFieldMetadata extends DeletableAdo { private String description; private CustomizableFieldType fieldType; private CustomizableFieldContext contextClass; - private String uiGroup; + private CustomizableFieldGroup uiGroup; private Integer uiLinePosition; private Float uiLineWeight; private boolean active = true; @@ -112,11 +115,12 @@ public void setContextClass(CustomizableFieldContext contextClass) { } @Column(length = 256) - public String getUiGroup() { + @Convert(converter = CustomizableFieldGroupConverter.class) + public CustomizableFieldGroup getUiGroup() { return uiGroup; } - public void setUiGroup(String uiGroup) { + public void setUiGroup(CustomizableFieldGroup uiGroup) { this.uiGroup = uiGroup; } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java index 80906bdcf44..0e78a8ba917 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java @@ -34,9 +34,12 @@ import de.symeda.sormas.api.common.DeletionDetails; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; import de.symeda.sormas.api.customizablefield.CustomizableFieldCustomProperties; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataCriteria; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityRestrictions; +import de.symeda.sormas.api.utils.SortProperty; import de.symeda.sormas.backend.util.DtoHelper; /** @@ -59,7 +62,7 @@ public List getActiveFieldsForContext(Customizable } @Override - public List getFieldsForUIGroup(String uiGroup) { + public List getFieldsForUIGroup(CustomizableFieldGroup uiGroup) { return customizableFieldMetadataService.getFieldsForUIGroup(uiGroup) .stream() .map(CustomizableFieldMetadataFacadeEjb::toDto) @@ -67,8 +70,8 @@ public List getFieldsForUIGroup(String uiGroup) { } @Override - public List getFieldsOrderedByUIPosition(String uiGroup) { - return getFieldsForUIGroup(uiGroup); // Already ordered in service + public List getFieldsOrderedByUIPosition(CustomizableFieldGroup uiGroup) { + return getFieldsForUIGroup(uiGroup); // Already ordered in service } @Override @@ -112,6 +115,24 @@ public List getAll() { return customizableFieldMetadataService.getAll().stream().map(CustomizableFieldMetadataFacadeEjb::toDto).collect(Collectors.toList()); } + @Override + public List getIndexList( + CustomizableFieldMetadataCriteria criteria, + Integer first, + Integer max, + List sortProperties) { + + return customizableFieldMetadataService.getIndexList(criteria, first, max, sortProperties) + .stream() + .map(CustomizableFieldMetadataFacadeEjb::toDto) + .collect(Collectors.toList()); + } + + @Override + public long count(CustomizableFieldMetadataCriteria criteria) { + return customizableFieldMetadataService.count(criteria); + } + @Override public CustomizableFieldMetadataDto save(CustomizableFieldMetadataDto dto) { CustomizableFieldMetadata entity = customizableFieldMetadataService.getByUuid(dto.getUuid()); diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java index 06be823c98f..f40c76669e7 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java @@ -15,17 +15,25 @@ package de.symeda.sormas.backend.customizablefield; +import java.util.ArrayList; import java.util.List; import javax.ejb.LocalBean; import javax.ejb.Stateless; +import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.From; +import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import org.apache.commons.lang3.StringUtils; + import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataCriteria; +import de.symeda.sormas.api.utils.SortProperty; import de.symeda.sormas.backend.common.AdoServiceWithUserFilterAndJurisdiction; /** @@ -61,7 +69,7 @@ public List getActiveFieldsForContext(CustomizableFie return em.createQuery(cq).getResultList(); } - public List getFieldsForUIGroup(String uiGroup) { + public List getFieldsForUIGroup(CustomizableFieldGroup uiGroup) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(CustomizableFieldMetadata.class); Root root = cq.from(CustomizableFieldMetadata.class); @@ -117,4 +125,86 @@ public CustomizableFieldMetadata cloneField(String sourceUuid, String newName) { ensurePersisted(clone); return clone; } + + public List getIndexList( + CustomizableFieldMetadataCriteria criteria, + Integer first, + Integer max, + List sortProperties) { + + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(CustomizableFieldMetadata.class); + Root root = cq.from(CustomizableFieldMetadata.class); + + Predicate filter = buildCriteriaFilter(criteria, cb, root); + if (filter != null) { + cq.where(filter); + } + + if (sortProperties != null && !sortProperties.isEmpty()) { + List orders = new ArrayList<>(); + for (SortProperty sortProperty : sortProperties) { + orders.add(sortProperty.ascending ? cb.asc(root.get(sortProperty.propertyName)) : cb.desc(root.get(sortProperty.propertyName))); + } + cq.orderBy(orders); + } else { + cq.orderBy(cb.asc(root.get(CustomizableFieldMetadata.NAME))); + } + + TypedQuery query = em.createQuery(cq); + if (first != null) { + query.setFirstResult(first); + } + if (max != null) { + query.setMaxResults(max); + } + return query.getResultList(); + } + + public long count(CustomizableFieldMetadataCriteria criteria) { + + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Long.class); + Root root = cq.from(CustomizableFieldMetadata.class); + cq.select(cb.count(root)); + + Predicate filter = buildCriteriaFilter(criteria, cb, root); + if (filter != null) { + cq.where(filter); + } + + return em.createQuery(cq).getSingleResult(); + } + + private Predicate buildCriteriaFilter(CustomizableFieldMetadataCriteria criteria, CriteriaBuilder cb, Root root) { + + if (criteria == null) { + return null; + } + + List predicates = new ArrayList<>(); + + if (criteria.getContextClass() != null) { + predicates.add(cb.equal(root.get(CustomizableFieldMetadata.CONTEXT_CLASS), criteria.getContextClass())); + } + + if (criteria.getFieldType() != null) { + predicates.add(cb.equal(root.get(CustomizableFieldMetadata.FIELD_TYPE), criteria.getFieldType())); + } + + if (criteria.getActive() != null) { + predicates.add(cb.equal(root.get(CustomizableFieldMetadata.ACTIVE), criteria.getActive())); + } + + if (!StringUtils.isBlank(criteria.getFreeTextFilter())) { + String likePattern = "%" + criteria.getFreeTextFilter().toLowerCase() + "%"; + predicates.add( + cb.or( + cb.like(cb.lower(root.get(CustomizableFieldMetadata.NAME)), likePattern), + cb.like(cb.lower(root.get(CustomizableFieldMetadata.DESCRIPTION)), likePattern), + cb.like(cb.lower(root.get(CustomizableFieldMetadata.UI_GROUP)), likePattern))); + } + + return predicates.isEmpty() ? null : cb.and(predicates.toArray(new Predicate[0])); + } } diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java index b945a4aa810..ff0e79059f2 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java @@ -33,6 +33,7 @@ import de.symeda.sormas.api.caze.CaseDataDto; import de.symeda.sormas.api.caze.InvestigationStatus; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; import de.symeda.sormas.api.customizablefield.CustomizableFieldType; @@ -66,7 +67,7 @@ void testRenderCustomizableFields(CustomizableFieldContext context) { metadata.setName("customField_" + context.name().toLowerCase()); metadata.setFieldType(CustomizableFieldType.TEXT); metadata.setContextClass(context); - metadata.setUiGroup(context.name().toLowerCase()); + metadata.setUiGroup(CustomizableFieldGroup.getGroupsForContext(context).stream().findFirst().orElse(null)); metadata.setUiLinePosition(1); CustomizableFieldMetadataDto savedMetadata = metadataFacade.save(metadata); diff --git a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java index 10f8b8a5111..73fe49c5631 100644 --- a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java +++ b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java @@ -32,6 +32,7 @@ import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.common.DeletionDetails; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; @@ -67,7 +68,11 @@ public List getActiveFieldsForContext(@PathParam(" @GET @Path("/uigroup/{uiGroup}") - public List getFieldsForUIGroup(@PathParam("uiGroup") String uiGroup) { + public List getFieldsForUIGroup(@PathParam("uiGroup") String uiGroupKey) { + CustomizableFieldGroup uiGroup = CustomizableFieldGroup.fromKey(uiGroupKey); + if (uiGroup == null) { + return java.util.Collections.emptyList(); + } return getFacade().getFieldsOrderedByUIPosition(uiGroup); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java index 73c4ab36c99..a97990c4eb7 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java @@ -169,6 +169,9 @@ private CssStyles() { public static final String BUTTON_CAPTION_OVERFLOW = "caption-overflow-label"; public static final String GEOCODE_BUTTON_HIGHLIGHT = "geocode-button-highlight"; + public static final String YES_NO_UNKNOWN_GROUP = "yes-no-unknown-group"; + public static final String YES_NO_UNKNOWN_OPTION_SELECTED = "yes-no-unknown-selected"; + // Link styles public static final String LINK_BUTTON = "button"; public static final String LINK_BUTTON_PRIMARY = "button-primary"; diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java index 2f7c43f5a82..6d59781eb55 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java @@ -26,6 +26,7 @@ import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.VerticalLayout; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityContext; @@ -42,55 +43,61 @@ * Usage: * *

- * CustomizableFieldsGroup group = new CustomizableFieldsGroup("myGroup");
- * group.setFieldsMetadata(fieldsList);   // full list – group filters by its own uiGroup
+ * CustomizableFieldsGroup group = new CustomizableFieldsGroup(CustomizableFieldGroup.EPIDATA_EXPOSURE_INVESTIGATION);
+ * group.setFieldsMetadata(fieldsList);   // full list – group filters by its own group
  * group.setFieldsValues(valuesMap);
  * group.updateFieldsDisplay();
- * form.addComponent(group, "myGroup");   // use the uiGroup as the layout location ID
+ * form.addComponent(group, CustomizableFieldGroup.EPIDATA_EXPOSURE_INVESTIGATION.getKey());
  * 
*/ +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160", // suppress sonar missing equals +}) public class CustomizableFieldsGroup extends VerticalLayout { private static final long serialVersionUID = 1L; private static final String STYLE_NAME_CUSTOMIZABLE_FIELDS_GROUP = "customizable-fields-group"; - private final String uiGroup; + private final CustomizableFieldGroup group; private final Map> fieldComponents; private final List> valueChangeListeners = new ArrayList<>(); private List fieldsMetadata; private Map fieldsValues; + + @SuppressWarnings("java:S1948") // CustomizableFieldVisibilityContext implements Serializable; Sonar can't verify it private CustomizableFieldVisibilityContext visibilityContext; /** * Creates a group scoped to the given UI group. - * Only fields whose metadata {@code uiGroup} equals this value will be rendered. + * Only fields whose metadata {@code uiGroup} equals this group will be rendered. * - * @param uiGroup - * the UI group identifier, must not be null + * @param group + * the UI group, must not be null */ - public CustomizableFieldsGroup(String uiGroup) { - if (uiGroup == null) { - throw new IllegalArgumentException("uiGroup must not be null"); + public CustomizableFieldsGroup(CustomizableFieldGroup group) { + if (group == null) { + throw new IllegalArgumentException("group must not be null"); } - this.uiGroup = uiGroup; + this.group = group; this.fieldComponents = new HashMap<>(); this.setSpacing(true); this.setMargin(false); this.setWidth(100, Unit.PERCENTAGE); this.addStyleName(STYLE_NAME_CUSTOMIZABLE_FIELDS_GROUP); - this.addStyleName(uiGroup); + this.addStyleName(group.getKey()); - this.setId(uiGroup); + this.setId(group.getKey()); } /** * Returns the UI group this group is scoped to. * - * @return the UI group identifier + * @return the UI group */ - public String getUiGroup() { - return uiGroup; + public CustomizableFieldGroup getGroup() { + return group; } /** @@ -172,7 +179,7 @@ public void updateFieldsDisplay() { boolean visibilityContextMatch = visibilityContext == null || metadata.getVisibilityRestrictions() == null || metadata.getVisibilityRestrictions().matches(visibilityContext); - if (metadata.isActive() && uiGroup.equals(metadata.getUiGroup()) && visibilityContextMatch) { + if (metadata.isActive() && metadata.getUiGroup() == group && visibilityContextMatch) { groupFields.add(metadata); } } @@ -207,6 +214,7 @@ public void updateFieldsDisplay() { * @param lineFields * fields to place side-by-side on this row */ + @SuppressWarnings("unchecked") private void addLineRow(List lineFields) { HorizontalLayout row = new HorizontalLayout(); row.setWidth(100, Unit.PERCENTAGE); @@ -227,8 +235,7 @@ private void addLineRow(List lineFields) { float expandRatio = metadata.getUiLineWeight() != null ? metadata.getUiLineWeight() : 1.0f; row.setExpandRatio(field, expandRatio); - for (@SuppressWarnings("unchecked") - HasValue.ValueChangeListener listener : valueChangeListeners) { + for (HasValue.ValueChangeListener listener : valueChangeListeners) { ((CustomizableFieldInput) field).addValueChangeListener(listener); } @@ -245,6 +252,7 @@ private void addLineRow(List lineFields) { * the UUID of the field metadata * @return the field component, or null if not found */ + @SuppressWarnings("java:S1452") // wildcard return type is intentional for CustomizableFieldInput public CustomizableFieldInput getFieldByMetadataUuid(String metadataUuid) { return fieldComponents.get(metadataUuid); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInput.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInput.java index c655603d438..91822a0ba99 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInput.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInput.java @@ -15,6 +15,7 @@ package de.symeda.sormas.ui.utils.components.customizablefield; +import java.util.Map; import java.util.Objects; import org.apache.commons.lang3.StringUtils; @@ -25,8 +26,10 @@ import com.vaadin.ui.Component; import com.vaadin.ui.CustomField; +import de.symeda.sormas.api.Language; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.i18n.I18nProperties; /** * Abstract base for editable customizable field input components (Vaadin v8). @@ -53,6 +56,7 @@ * {@link com.vaadin.data.Validator}s to the binding before it is finalised. * */ +@SuppressWarnings("java:S2160") // sonar missing equals, ok for Vaadin UI components public abstract class CustomizableFieldInput extends CustomField { private static final long serialVersionUID = 1L; @@ -214,13 +218,59 @@ public CustomizableFieldMetadataDto getFieldMetadata() { /** * Applies caption, mandatory indicator and read-only state from metadata. + * The caption and description are resolved from the translations map for the current user language, + * falling back to the stored name/description when no matching translation exists. * Called once in the constructor, before the inner component is built. */ private void applyMetadata() { - if (StringUtils.isNotBlank(fieldMetadata.getName())) { - setCaption(fieldMetadata.getName()); + String caption = resolveTranslation(CustomizableFieldMetadataDto.NAME, fieldMetadata.getName()); + if (StringUtils.isNotBlank(caption)) { + setCaption(caption); + } + String description = resolveTranslation(CustomizableFieldMetadataDto.DESCRIPTION, fieldMetadata.getDescription()); + if (StringUtils.isNotBlank(description)) { + setDescription(description); } setRequiredIndicatorVisible(fieldMetadata.isMandatory()); setReadOnly(fieldMetadata.isReadOnly()); } + + /** + * Looks up {@code key} (e.g. "name" or "description") in the metadata's translations map + * for the current user language. Tries the full locale string first (e.g. "de_DE"), then + * falls back to the language-only prefix (e.g. "de"), then to {@code fallback}. + */ + private String resolveTranslation(String key, String fallback) { + Map> translations = fieldMetadata.getTranslations(); + if (translations != null) { + Language userLanguage = I18nProperties.getUserLanguage(); + if (userLanguage != null) { + String localeStr = userLanguage.getLocale().toString(); + String translated = getTranslationFromMap(translations, localeStr, key); + if (translated != null) { + return translated; + } + // Try language-only prefix (e.g. "en" from "en_GB") + int underscore = localeStr.indexOf('_'); + if (underscore > 0) { + translated = getTranslationFromMap(translations, localeStr.substring(0, underscore), key); + if (translated != null) { + return translated; + } + } + } + } + return fallback; + } + + private static String getTranslationFromMap(Map> translations, String localeKey, String key) { + Map langMap = translations.get(localeKey); + if (langMap != null) { + String value = langMap.get(key); + if (StringUtils.isNotBlank(value)) { + return value; + } + } + return null; + } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputRadioButtonList.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputRadioButtonList.java index 9c58fff06ee..87cf101ed9e 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputRadioButtonList.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputRadioButtonList.java @@ -38,6 +38,10 @@ * The selected value is stored directly as a {@link String} in * {@link CustomizableFieldValueDto#getValue()}. */ +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress missing equals +}) public class CustomizableFieldInputRadioButtonList extends CustomizableFieldInput { private static final long serialVersionUID = 1L; diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputYesNoUnknown.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputYesNoUnknown.java index 1156f9f6220..5b32617e639 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputYesNoUnknown.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/customizablefield/CustomizableFieldInputYesNoUnknown.java @@ -15,36 +15,44 @@ package de.symeda.sormas.ui.utils.components.customizablefield; -import java.util.Arrays; +import java.util.EnumMap; +import java.util.Map; import com.vaadin.data.ValueProvider; import com.vaadin.server.Setter; -import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Button; import com.vaadin.ui.Component; +import com.vaadin.ui.HorizontalLayout; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.utils.YesNoUnknown; +import de.symeda.sormas.ui.utils.CssStyles; /** * Concrete {@link CustomizableFieldInput} for * {@link de.symeda.sormas.api.customizablefield.CustomizableFieldType#YES_NO_UNKNOWN}. *

- * Renders a Vaadin v8 {@link ComboBox} populated with all {@link YesNoUnknown} constants. - * The selected value is serialised to/from the DTO's {@code value} field as the enum name - * ({@code "YES"}, {@code "NO"}, {@code "UNKNOWN"}) via + * Renders three horizontal toggle {@link Button}s (Yes / No / Unknown). Clicking the active + * button deselects it (nullable); clicking another button selects it. The selected value is + * serialised to/from the DTO's {@code value} field as the enum name via * {@link CustomizableFieldValueDto#getValueAsYesNoUnknown()} and * {@link CustomizableFieldValueDto#setValueAsYesNoUnknown(YesNoUnknown)}. */ +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress missing equals +}) public class CustomizableFieldInputYesNoUnknown extends CustomizableFieldInput { private static final long serialVersionUID = 1L; - private ComboBox comboBox; + private final Map buttons = new EnumMap<>(YesNoUnknown.class); /** * Holds a value that was pushed via {@link #doSetValue(YesNoUnknown)} before - * {@link #buildInputComponent()} had a chance to create the {@link ComboBox}. - * Applied to the combo box on first render. + * {@link #buildInputComponent()} had a chance to create the buttons. + * Applied on first render. */ private YesNoUnknown pendingValue; @@ -68,36 +76,50 @@ public Class getType() { @Override protected Component buildInputComponent() { - comboBox = new ComboBox<>(); - comboBox.setWidth(100, Unit.PERCENTAGE); - comboBox.setItems(Arrays.asList(YesNoUnknown.values())); - comboBox.setItemCaptionGenerator(YesNoUnknown::toString); - comboBox.setEmptySelectionAllowed(true); + HorizontalLayout layout = new HorizontalLayout(); + layout.setSpacing(false); + layout.setMargin(false); + CssStyles.style(layout, CssStyles.YES_NO_UNKNOWN_GROUP); + + for (YesNoUnknown option : YesNoUnknown.values()) { + Button btn = new Button(I18nProperties.getEnumCaption(option)); + btn.addClickListener(e -> { + YesNoUnknown current = getValue(); + setValue(option == current ? null : option); + }); + buttons.put(option, btn); + layout.addComponent(btn); + } - // Apply any value that arrived before the component was first rendered. if (pendingValue != null) { - comboBox.setValue(pendingValue); + applyButtonStyles(pendingValue); pendingValue = null; } - // Propagate user edits back to the field's internal value state. - comboBox.addValueChangeListener(e -> setValue(e.getValue())); - - return comboBox; + return layout; } /** * Called by Vaadin when {@link #setValue(Object)} is invoked programmatically. - * Pushes the value into the {@link ComboBox}; {@code null} clears the selection. - * If the {@link ComboBox} has not been created yet (component not yet rendered), - * the value is stored as a pending value and applied in {@link #buildInputComponent()}. + * Updates button highlighting to reflect the new selection. + * If the buttons have not been created yet, stores the value as pending. */ @Override protected void applyValueToWidget(YesNoUnknown value) { - if (comboBox != null) { - comboBox.setValue(value); - } else { + if (buttons.isEmpty()) { pendingValue = value; + } else { + applyButtonStyles(value); + } + } + + private void applyButtonStyles(YesNoUnknown selected) { + for (Map.Entry entry : buttons.entrySet()) { + if (entry.getKey() == selected) { + entry.getValue().addStyleName(CssStyles.YES_NO_UNKNOWN_OPTION_SELECTED); + } else { + entry.getValue().removeStyleName(CssStyles.YES_NO_UNKNOWN_OPTION_SELECTED); + } } } } diff --git a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/button.scss b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/button.scss index 79bde226a5a..efeb984fbed 100644 --- a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/button.scss +++ b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/button.scss @@ -93,4 +93,54 @@ vertical-align: top; } } + + // Segmented button group used for YesNoUnknown fields — matches the NullableOptionGroup horizontal style. + .v-horizontallayout.yes-no-unknown-group { + line-height: 0; + + .v-slot { + margin-right: -2px; + } + + .v-button { + border: $v-border; + border-radius: 0; + background: $v-app-background-color; + box-shadow: none; + font-size: 11px; + text-transform: uppercase; + height: 28px; + padding: 0; + min-width: 0; + + .v-button-wrap { + padding: 4px 8px; + height: 28px; + line-height: 20px; + } + + &:first-child { + border-radius: 4px 0 0 4px; + } + &:last-child { + border-radius: 0 4px 4px 0; + } + + &.yes-no-unknown-selected { + background: $v-focus-color; + + .v-button-wrap { + padding: 4px 8px; + } + } + + &:hover:not(.v-disabled) { + background: darken($v-focus-color, 8%); + } + + &.v-disabled { + opacity: $v-disabled-opacity; + } + } + } } \ No newline at end of file From 508f0c3eb8ee215349e0db3b94629f40ee9ce21e Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Thu, 16 Apr 2026 09:33:47 +0200 Subject: [PATCH 48/55] Added better handling for customizable field deletionconfiguration - fixed history triggers - added customizable fields tables to export --- .../api/common/DeletableEntityType.java | 4 +- .../CustomizableFieldContext.java | 4 +- .../CustomizableFieldMetadataFacade.java | 52 +----- ...CustomizableFieldMetadataReferenceDto.java | 27 +++ .../CustomizableFieldValueCriteria.java | 23 +++ .../CustomizableFieldValueFacade.java | 22 +-- .../CustomizableFieldValueReferenceDto.java | 27 +++ .../api/importexport/DatabaseTable.java | 2 + sormas-api/src/main/resources/enum.properties | 3 + .../CustomizableFieldMetadata.java | 4 +- .../CustomizableFieldMetadataFacadeEjb.java | 158 ++++++++++++------ .../CustomizableFieldMetadataJoins.java | 27 +++ ...CustomizableFieldMetadataQueryContext.java | 35 ++++ .../CustomizableFieldMetadataService.java | 33 +++- .../CustomizableFieldValue.java | 8 +- .../CustomizableFieldValueFacadeEjb.java | 137 ++++++++++++--- .../CustomizableFieldValueJoins.java | 27 +++ .../CustomizableFieldValueQueryContext.java | 35 ++++ .../CustomizableFieldValueService.java | 52 +++++- .../CoreEntityDeletionService.java | 12 +- .../importexport/DatabaseExportService.java | 5 +- .../src/main/resources/sql/sormas_schema.sql | 8 +- .../sormas/backend/EntityMappingTest.java | 6 + .../CustomizableFieldFacadeEjbTest.java | 22 ++- .../CustomizableFieldMetadataResource.java | 4 +- 25 files changed, 571 insertions(+), 166 deletions(-) create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataReferenceDto.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueCriteria.java create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueReferenceDto.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataJoins.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataQueryContext.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueJoins.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueQueryContext.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/common/DeletableEntityType.java b/sormas-api/src/main/java/de/symeda/sormas/api/common/DeletableEntityType.java index 0e3a694ec31..fc5910a23c1 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/common/DeletableEntityType.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/common/DeletableEntityType.java @@ -30,5 +30,7 @@ public enum DeletableEntityType { PATHOGEN_TEST, ENVIRONMENT, ENVIRONMENT_SAMPLE, - SELF_REPORT; + SELF_REPORT, + CUSTOMIZABLE_FIELD_METADATA, + CUSTOMIZABLE_FIELD_VALUE; } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java index d4b903f484b..e5eb3e49db6 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java @@ -18,6 +18,7 @@ import de.symeda.sormas.api.EntityDto; import de.symeda.sormas.api.caze.CaseDataDto; import de.symeda.sormas.api.epidata.EpiDataDto; +import de.symeda.sormas.api.exposure.ExposureDto; /** * Defines supported customizable field contexts and links them to existing @@ -27,7 +28,8 @@ public enum CustomizableFieldContext { CASE(CaseDataDto.class), - EPIDATA(EpiDataDto.class); + EPIDATA(EpiDataDto.class), + EXPOSURE(ExposureDto.class); // add other contexts here diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java index 0be73ddf30f..375243f85ae 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java @@ -18,17 +18,16 @@ import java.util.List; import javax.ejb.Remote; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import de.symeda.sormas.api.common.DeletionDetails; -import de.symeda.sormas.api.utils.SortProperty; +import de.symeda.sormas.api.CoreFacade; /** * Facade interface for managing customizable field metadata. */ @Remote -public interface CustomizableFieldMetadataFacade { +public interface CustomizableFieldMetadataFacade + extends + CoreFacade { /** * Get all active custom fields for a specific context class @@ -56,51 +55,12 @@ public interface CustomizableFieldMetadataFacade { CustomizableFieldMetadataDto cloneField(String sourceUuid, String newName); /** - * Activate a field + * Set the active state of a field */ - void activateField(String uuid); - - /** - * Deactivate a field - */ - void deactivateField(String uuid); - - /** - * Get a field by UUID - */ - CustomizableFieldMetadataDto getByUuid(String uuid); + void setFieldActive(String uuid, boolean active); /** * Get all field metadata */ List getAll(); - - /** - * Get a paged and filtered list of field metadata for admin views. - */ - List getIndexList( - CustomizableFieldMetadataCriteria criteria, - Integer first, - Integer max, - List sortProperties); - - /** - * Count field metadata matching the given criteria. - */ - long count(CustomizableFieldMetadataCriteria criteria); - - /** - * Save a customizable field metadata - */ - CustomizableFieldMetadataDto save(@Valid @NotNull CustomizableFieldMetadataDto dto); - - /** - * Delete a customizable field metadata - */ - void delete(String uuid, DeletionDetails deletionDetails); - - /** - * Check if a field with the given UUID exists - */ - boolean exists(String uuid); } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataReferenceDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataReferenceDto.java new file mode 100644 index 00000000000..18bef8f5520 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataReferenceDto.java @@ -0,0 +1,27 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import de.symeda.sormas.api.ReferenceDto; + +public class CustomizableFieldMetadataReferenceDto extends ReferenceDto { + + private static final long serialVersionUID = 1L; + + public CustomizableFieldMetadataReferenceDto(String uuid) { + super(uuid); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueCriteria.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueCriteria.java new file mode 100644 index 00000000000..29c9de85b6b --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueCriteria.java @@ -0,0 +1,23 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import de.symeda.sormas.api.utils.criteria.BaseCriteria; + +public class CustomizableFieldValueCriteria extends BaseCriteria { + + private static final long serialVersionUID = 1L; +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java index 2c1590f9e26..ea08fb5f464 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java @@ -19,16 +19,15 @@ import java.util.Map; import javax.ejb.Remote; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import de.symeda.sormas.api.common.DeletionDetails; +import de.symeda.sormas.api.CoreFacade; /** * Facade interface for managing customizable field values. */ @Remote -public interface CustomizableFieldValueFacade { +public interface CustomizableFieldValueFacade + extends CoreFacade { /** * Load all custom field values for a specific entity @@ -58,23 +57,8 @@ default void saveEntityCustomFields( */ void deleteValuesForEntity(String entityUuid, CustomizableFieldContext contextClass); - /** - * Get a field value by UUID - */ - CustomizableFieldValueDto getByUuid(String uuid); - /** * Get all field values */ List getAll(); - - /** - * Save a single customizable field value - */ - CustomizableFieldValueDto save(@Valid @NotNull CustomizableFieldValueDto dto); - - /** - * Delete a customizable field value - */ - void delete(String uuid, DeletionDetails deletionDetails); } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueReferenceDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueReferenceDto.java new file mode 100644 index 00000000000..740a11a4e36 --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueReferenceDto.java @@ -0,0 +1,27 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.customizablefield; + +import de.symeda.sormas.api.ReferenceDto; + +public class CustomizableFieldValueReferenceDto extends ReferenceDto { + + private static final long serialVersionUID = 1L; + + public CustomizableFieldValueReferenceDto(String uuid) { + super(uuid); + } +} diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/importexport/DatabaseTable.java b/sormas-api/src/main/java/de/symeda/sormas/api/importexport/DatabaseTable.java index 69dcc8310c8..bd934480758 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/importexport/DatabaseTable.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/importexport/DatabaseTable.java @@ -105,6 +105,8 @@ public enum DatabaseTable { FACILITIES(DatabaseTableType.INFRASTRUCTURE, "facilities", null), POINTS_OF_ENTRY(DatabaseTableType.INFRASTRUCTURE, "points_of_entry", null), CUSTOMIZABLE_ENUM_VALUES(DatabaseTableType.CONFIGURATION, "customizable_enum_values", null), + CUSTOMIZABLE_FIELD_METADATA(DatabaseTableType.CONFIGURATION, "customizable_field_metadata", null), + CUSTOMIZABLE_FIELD_VALUE(DatabaseTableType.CONFIGURATION, CUSTOMIZABLE_FIELD_METADATA, "customizable_field_value"), CAMPAIGNS(DatabaseTableType.SORMAS, "campaigns", dependingOnFeature(FeatureType.CAMPAIGNS)), CAMPAIGN_CAMPAIGNFORMMETA(DatabaseTableType.SORMAS, CAMPAIGNS, "campaign_campaignformmeta"), diff --git a/sormas-api/src/main/resources/enum.properties b/sormas-api/src/main/resources/enum.properties index cbc929e9ca0..c52494fdb13 100644 --- a/sormas-api/src/main/resources/enum.properties +++ b/sormas-api/src/main/resources/enum.properties @@ -402,6 +402,8 @@ DatabaseTable.CAMPAIGN_CAMPAIGNFORMMETA = Campaigns → Campaign Form Meta DatabaseTable.CAMPAIGN_FORM_META = Campaign form meta DatabaseTable.CAMPAIGN_FORM_DATA = Campaign form data DatabaseTable.CAMPAIGN_DIAGRAM_DEFINITIONS = Campaign diagram definitions +DatabaseTable.CUSTOMIZABLE_FIELD_METADATA = Customizable field metadata +DatabaseTable.CUSTOMIZABLE_FIELD_VALUE = Customizable field value DatabaseTable.POPULATION_DATA = Population data DatabaseTable.SURVEILLANCE_REPORTS = Surveillance reports DatabaseTable.AGGREGATE_REPORTS = Aggregate reports @@ -1899,6 +1901,7 @@ UserRight.EXTERNAL_VISITS = External visits UserRight.SORMAS_UI = Access Sormas UI UserRight.DEV_MODE = Access developer options UserRight.CUSTOMIZABLE_ENUM_MANAGEMENT = Manage customizable enums +UserRight.CUSTOMIZABLE_FIELD_MANAGEMENT = Manage customizable fields UserRight.DISEASE_MANAGEMENT = Disease management UserRight.DOCUMENT_VIEW = View existing documents UserRight.DOCUMENT_UPLOAD = Upload documents diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java index 2ac66bb4ee6..a6df6186513 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java @@ -29,7 +29,7 @@ import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; import de.symeda.sormas.api.customizablefield.CustomizableFieldType; -import de.symeda.sormas.backend.common.DeletableAdo; +import de.symeda.sormas.backend.common.CoreAdo; /** * Entity class for customizable field metadata. @@ -41,7 +41,7 @@ "java:S1845", // suppress sonar field name clash warning "java:S2160" // suppress missing equals handled in AbstractDomainObject }) -public class CustomizableFieldMetadata extends DeletableAdo { +public class CustomizableFieldMetadata extends CoreAdo { private static final long serialVersionUID = 1L; diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java index 0e78a8ba917..04b5d98af3e 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataFacadeEjb.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -24,95 +25,95 @@ import javax.ejb.EJB; import javax.ejb.LocalBean; import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import de.symeda.sormas.api.common.DeletableEntityType; import de.symeda.sormas.api.common.DeletionDetails; +import de.symeda.sormas.api.common.progress.ProcessedEntity; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; import de.symeda.sormas.api.customizablefield.CustomizableFieldCustomProperties; import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataCriteria; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataReferenceDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityRestrictions; import de.symeda.sormas.api.utils.SortProperty; +import de.symeda.sormas.api.utils.ValidationRuntimeException; +import de.symeda.sormas.backend.common.AbstractCoreFacadeEjb; import de.symeda.sormas.backend.util.DtoHelper; +import de.symeda.sormas.backend.util.Pseudonymizer; /** * Facade EJB implementation for customizable field metadata. */ @Stateless(name = "CustomizableFieldMetadataFacade") -public class CustomizableFieldMetadataFacadeEjb implements CustomizableFieldMetadataFacade { +public class CustomizableFieldMetadataFacadeEjb + extends + AbstractCoreFacadeEjb + implements CustomizableFieldMetadataFacade { private static final ObjectMapper mapper = new ObjectMapper(); @EJB - private CustomizableFieldMetadataService customizableFieldMetadataService; + private CustomizableFieldValueService customizableFieldValueService; + + public CustomizableFieldMetadataFacadeEjb() { + } + + @Inject + public CustomizableFieldMetadataFacadeEjb(CustomizableFieldMetadataService service) { + super(CustomizableFieldMetadata.class, CustomizableFieldMetadataDto.class, service); + } @Override public List getActiveFieldsForContext(CustomizableFieldContext contextClass) { - return customizableFieldMetadataService.getActiveFieldsForContext(contextClass) - .stream() - .map(CustomizableFieldMetadataFacadeEjb::toDto) - .collect(Collectors.toList()); + return service.getActiveFieldsForContext(contextClass).stream().map(this::toDto).collect(Collectors.toList()); } @Override public List getFieldsForUIGroup(CustomizableFieldGroup uiGroup) { - return customizableFieldMetadataService.getFieldsForUIGroup(uiGroup) - .stream() - .map(CustomizableFieldMetadataFacadeEjb::toDto) - .collect(Collectors.toList()); + return service.getFieldsForUIGroup(uiGroup).stream().map(this::toDto).collect(Collectors.toList()); } @Override public List getFieldsOrderedByUIPosition(CustomizableFieldGroup uiGroup) { - return getFieldsForUIGroup(uiGroup); // Already ordered in service + return getFieldsForUIGroup(uiGroup); // Already ordered in service } @Override public CustomizableFieldMetadataDto getByNameAndContext(String name, CustomizableFieldContext contextClass) { - CustomizableFieldMetadata entity = customizableFieldMetadataService.getByNameAndContext(name, contextClass); + CustomizableFieldMetadata entity = service.getByNameAndContext(name, contextClass); return entity != null ? toDto(entity) : null; } @Override public CustomizableFieldMetadataDto cloneField(String sourceUuid, String newName) { - CustomizableFieldMetadata cloned = customizableFieldMetadataService.cloneField(sourceUuid, newName); + CustomizableFieldMetadata cloned = service.cloneField(sourceUuid, newName); return toDto(cloned); } @Override - public void activateField(String uuid) { - CustomizableFieldMetadata field = customizableFieldMetadataService.getByUuid(uuid); + public void setFieldActive(String uuid, boolean active) { + CustomizableFieldMetadata field = service.getByUuid(uuid); if (field != null) { - field.setActive(true); - customizableFieldMetadataService.ensurePersisted(field); + field.setActive(active); + service.ensurePersisted(field); } } - @Override - public void deactivateField(String uuid) { - CustomizableFieldMetadata field = customizableFieldMetadataService.getByUuid(uuid); - if (field != null) { - field.setActive(false); - customizableFieldMetadataService.ensurePersisted(field); - } - } - - @Override - public CustomizableFieldMetadataDto getByUuid(String uuid) { - CustomizableFieldMetadata entity = customizableFieldMetadataService.getByUuid(uuid); - return entity != null ? toDto(entity) : null; - } - @Override public List getAll() { - return customizableFieldMetadataService.getAll().stream().map(CustomizableFieldMetadataFacadeEjb::toDto).collect(Collectors.toList()); + return service.getAll().stream().map(this::toDto).collect(Collectors.toList()); } @Override @@ -122,40 +123,87 @@ public List getIndexList( Integer max, List sortProperties) { - return customizableFieldMetadataService.getIndexList(criteria, first, max, sortProperties) - .stream() - .map(CustomizableFieldMetadataFacadeEjb::toDto) - .collect(Collectors.toList()); + return service.getIndexList(criteria, first, max, sortProperties).stream().map(this::toDto).collect(Collectors.toList()); } @Override public long count(CustomizableFieldMetadataCriteria criteria) { - return customizableFieldMetadataService.count(criteria); + return service.count(criteria); } @Override - public CustomizableFieldMetadataDto save(CustomizableFieldMetadataDto dto) { - CustomizableFieldMetadata entity = customizableFieldMetadataService.getByUuid(dto.getUuid()); + public CustomizableFieldMetadataDto save(@Valid @NotNull CustomizableFieldMetadataDto dto) { + CustomizableFieldMetadata entity = service.getByUuid(dto.getUuid()); entity = fillOrBuildEntity(dto, entity, true); - customizableFieldMetadataService.ensurePersisted(entity); + service.ensurePersisted(entity); return toDto(entity); } @Override public void delete(String uuid, DeletionDetails deletionDetails) { - CustomizableFieldMetadata entity = customizableFieldMetadataService.getByUuid(uuid); + CustomizableFieldMetadata entity = service.getByUuid(uuid); if (entity != null) { - customizableFieldMetadataService.deletePermanent(entity); + customizableFieldValueService.softDeleteValuesForMetadata(uuid, deletionDetails); + service.delete(entity, deletionDetails); } } @Override - public boolean exists(String uuid) { - return customizableFieldMetadataService.exists(uuid); + public List delete(List uuids, DeletionDetails deletionDetails) { + throw new NotImplementedException(); + } + + @Override + public void restore(String uuid) { + super.restore(uuid); + } + + @Override + public List restore(List uuids) { + throw new NotImplementedException(); + } + + @Override + public List getArchivedUuidsSince(Date since) { + throw new NotImplementedException(); } - public CustomizableFieldMetadata fillOrBuildEntity( - CustomizableFieldMetadataDto source, + @Override + public void validate(@Valid CustomizableFieldMetadataDto dto) throws ValidationRuntimeException { + // no validation required for customizable field metadata + } + + @Override + protected DeletableEntityType getDeletableEntityType() { + return DeletableEntityType.CUSTOMIZABLE_FIELD_METADATA; + } + + @Override + protected CustomizableFieldMetadataReferenceDto toRefDto(CustomizableFieldMetadata entity) { + return new CustomizableFieldMetadataReferenceDto(entity.getUuid()); + } + + @Override + protected void pseudonymizeDto( + CustomizableFieldMetadata source, + CustomizableFieldMetadataDto dto, + Pseudonymizer pseudonymizer, + boolean inJurisdiction) { + // no sensitive fields to pseudonymize + } + + @Override + protected void restorePseudonymizedDto( + CustomizableFieldMetadataDto dto, + CustomizableFieldMetadataDto existingDto, + CustomizableFieldMetadata entity, + Pseudonymizer pseudonymizer) { + // no sensitive fields to restore + } + + @Override + protected CustomizableFieldMetadata fillOrBuildEntity( + @NotNull CustomizableFieldMetadataDto source, CustomizableFieldMetadata target, boolean checkChangeDate) { if (source == null) { @@ -196,6 +244,7 @@ public CustomizableFieldMetadata fillOrBuildEntity( } else { target.setCustomProperties(null); } + if (source.getTranslations() != null) { try { target.setTranslations(mapper.writeValueAsString(source.getTranslations())); @@ -209,7 +258,8 @@ public CustomizableFieldMetadata fillOrBuildEntity( return target; } - public static CustomizableFieldMetadataDto toDto(CustomizableFieldMetadata source) { + @Override + protected CustomizableFieldMetadataDto toDto(CustomizableFieldMetadata source) { if (source == null) { return null; } @@ -229,7 +279,7 @@ public static CustomizableFieldMetadataDto toDto(CustomizableFieldMetadata sourc target.setReadOnly(source.isReadOnly()); target.setDefaultValue(source.getDefaultValue()); - // Deserialize JSON to maps + // Deserialize JSON to objects target.setVisibilityRestrictions(parseVisibilityRestrictions(source.getVisibilityRestrictions())); target.setCustomProperties(parseCustomProperties(source.getCustomProperties())); target.setTranslations(parseTranslations(source.getTranslations())); @@ -261,7 +311,7 @@ private static CustomizableFieldCustomProperties parseCustomProperties(String js private static Map> parseTranslations(String json) { if (StringUtils.isBlank(json)) { - // we could return a empty map but it might be interpreted as no translaton and postgres might create a '{}' jsonb entry + // we could return an empty map but it might be interpreted as no translation and postgres might create a '{}' jsonb entry return Collections.emptyMap(); } try { @@ -275,5 +325,13 @@ private static Map> parseTranslations(String json) { @LocalBean @Stateless public static class CustomizableFieldMetadataFacadeEjbLocal extends CustomizableFieldMetadataFacadeEjb { + + public CustomizableFieldMetadataFacadeEjbLocal() { + } + + @Inject + public CustomizableFieldMetadataFacadeEjbLocal(CustomizableFieldMetadataService service) { + super(service); + } } } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataJoins.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataJoins.java new file mode 100644 index 00000000000..9f3201825c3 --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataJoins.java @@ -0,0 +1,27 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import javax.persistence.criteria.From; + +import de.symeda.sormas.backend.common.QueryJoins; + +public class CustomizableFieldMetadataJoins extends QueryJoins { + + public CustomizableFieldMetadataJoins(From root) { + super(root); + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataQueryContext.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataQueryContext.java new file mode 100644 index 00000000000..c24e0c9512f --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataQueryContext.java @@ -0,0 +1,35 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.From; + +import de.symeda.sormas.backend.common.QueryContext; + +public class CustomizableFieldMetadataQueryContext extends QueryContext { + + public CustomizableFieldMetadataQueryContext(CriteriaBuilder cb, CriteriaQuery query, From root) { + super(cb, query, root, new CustomizableFieldMetadataJoins(root)); + } + + @Override + protected Expression createExpression(String name) { + return null; + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java index f40c76669e7..251e2547675 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadataService.java @@ -30,30 +30,42 @@ import org.apache.commons.lang3.StringUtils; +import de.symeda.sormas.api.common.DeletableEntityType; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataCriteria; import de.symeda.sormas.api.utils.SortProperty; -import de.symeda.sormas.backend.common.AdoServiceWithUserFilterAndJurisdiction; +import de.symeda.sormas.backend.common.AbstractCoreAdoService; +import de.symeda.sormas.backend.common.DeletableAdo; /** * Service class for customizable field metadata. */ @Stateless @LocalBean -public class CustomizableFieldMetadataService extends AdoServiceWithUserFilterAndJurisdiction { +public class CustomizableFieldMetadataService extends AbstractCoreAdoService { public CustomizableFieldMetadataService() { - super(CustomizableFieldMetadata.class); + super(CustomizableFieldMetadata.class, DeletableEntityType.CUSTOMIZABLE_FIELD_METADATA); } @Override @SuppressWarnings("rawtypes") - public Predicate createUserFilter(CriteriaBuilder cb, CriteriaQuery cq, From from) { + protected Predicate createUserFilterInternal(CriteriaBuilder cb, CriteriaQuery cq, From from) { // No jurisdiction filtering for customizable fields return null; } + @Override + protected CustomizableFieldMetadataJoins toJoins(From adoPath) { + return new CustomizableFieldMetadataJoins(adoPath); + } + + @Override + public Predicate inJurisdictionOrOwned(CriteriaBuilder cb, CriteriaQuery query, From from) { + return cb.conjunction(); + } + public List getActiveFieldsForContext(CustomizableFieldContext contextClass) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(CustomizableFieldMetadata.class); @@ -61,7 +73,8 @@ public List getActiveFieldsForContext(CustomizableFie Predicate predicate = cb.and( cb.equal(root.get(CustomizableFieldMetadata.CONTEXT_CLASS), contextClass), - cb.equal(root.get(CustomizableFieldMetadata.ACTIVE), true)); + cb.equal(root.get(CustomizableFieldMetadata.ACTIVE), true), + cb.isFalse(root.get(DeletableAdo.DELETED))); cq.where(predicate); cq.orderBy(cb.asc(root.get(CustomizableFieldMetadata.UI_LINE_POSITION))); @@ -74,7 +87,7 @@ public List getFieldsForUIGroup(CustomizableFieldGrou CriteriaQuery cq = cb.createQuery(CustomizableFieldMetadata.class); Root root = cq.from(CustomizableFieldMetadata.class); - Predicate predicate = cb.equal(root.get(CustomizableFieldMetadata.UI_GROUP), uiGroup); + Predicate predicate = cb.and(cb.equal(root.get(CustomizableFieldMetadata.UI_GROUP), uiGroup), cb.isFalse(root.get(DeletableAdo.DELETED))); cq.where(predicate); cq.orderBy(cb.asc(root.get(CustomizableFieldMetadata.UI_LINE_POSITION))); @@ -87,8 +100,10 @@ public CustomizableFieldMetadata getByNameAndContext(String name, CustomizableFi CriteriaQuery cq = cb.createQuery(CustomizableFieldMetadata.class); Root root = cq.from(CustomizableFieldMetadata.class); - Predicate predicate = cb - .and(cb.equal(root.get(CustomizableFieldMetadata.NAME), name), cb.equal(root.get(CustomizableFieldMetadata.CONTEXT_CLASS), contextClass)); + Predicate predicate = cb.and( + cb.equal(root.get(CustomizableFieldMetadata.NAME), name), + cb.equal(root.get(CustomizableFieldMetadata.CONTEXT_CLASS), contextClass), + cb.isFalse(root.get(DeletableAdo.DELETED))); cq.where(predicate); @@ -196,6 +211,8 @@ private Predicate buildCriteriaFilter(CustomizableFieldMetadataCriteria criteria predicates.add(cb.equal(root.get(CustomizableFieldMetadata.ACTIVE), criteria.getActive())); } + predicates.add(cb.isFalse(root.get(DeletableAdo.DELETED))); + if (!StringUtils.isBlank(criteria.getFreeTextFilter())) { String likePattern = "%" + criteria.getFreeTextFilter().toLowerCase() + "%"; predicates.add( diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java index d4176b5681e..0eee398bbf3 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java @@ -23,14 +23,18 @@ import javax.persistence.ManyToOne; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; -import de.symeda.sormas.backend.common.DeletableAdo; +import de.symeda.sormas.backend.common.CoreAdo; /** * Entity class for customizable field values. * Stores the actual value for a custom field for a specific entity instance. */ @Entity(name = "customizablefieldvalue") -public class CustomizableFieldValue extends DeletableAdo { +@SuppressWarnings({ + "java:S1845", // suppress sonar field name clash warning + "java:S2160" // suppress missing equals handled in AbstractDomainObject +}) +public class CustomizableFieldValue extends CoreAdo { private static final long serialVersionUID = 1L; diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java index d6659ea648e..6116d9164ee 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java @@ -15,6 +15,7 @@ package de.symeda.sormas.backend.customizablefield; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -23,28 +24,48 @@ import javax.ejb.EJB; import javax.ejb.LocalBean; import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import org.apache.commons.lang3.NotImplementedException; + +import de.symeda.sormas.api.common.DeletableEntityType; import de.symeda.sormas.api.common.DeletionDetails; +import de.symeda.sormas.api.common.progress.ProcessedEntity; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueCriteria; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueReferenceDto; +import de.symeda.sormas.api.utils.ValidationRuntimeException; +import de.symeda.sormas.backend.common.AbstractCoreFacadeEjb; import de.symeda.sormas.backend.util.DtoHelper; +import de.symeda.sormas.backend.util.Pseudonymizer; /** * Facade EJB implementation for customizable field values. */ @Stateless(name = "CustomizableFieldValueFacade") -public class CustomizableFieldValueFacadeEjb implements CustomizableFieldValueFacade { - - @EJB - private CustomizableFieldValueService customizableFieldValueService; +public class CustomizableFieldValueFacadeEjb + extends + AbstractCoreFacadeEjb + implements CustomizableFieldValueFacade { @EJB private CustomizableFieldMetadataService customizableFieldMetadataService; + public CustomizableFieldValueFacadeEjb() { + } + + @Inject + public CustomizableFieldValueFacadeEjb(CustomizableFieldValueService service) { + super(CustomizableFieldValue.class, CustomizableFieldValueDto.class, service); + } + @Override public Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { - Map entities = customizableFieldValueService.getValuesForEntity(entityUuid, contextClass); + Map entities = service.getValuesForEntity(entityUuid, contextClass); Map result = new HashMap<>(); for (Map.Entry entry : entities.entrySet()) { @@ -55,42 +76,107 @@ public Map getValuesForEntity(String entityUu @Override public void saveEntityCustomFields(String entityUuid, Map> fieldsByContext) { - fieldsByContext.forEach((context, fields) -> customizableFieldValueService.saveEntityValues(entityUuid, context, fields)); + fieldsByContext.forEach((context, fields) -> service.saveEntityValues(entityUuid, context, fields)); } @Override public void deleteValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { - customizableFieldValueService.deleteValuesForEntity(entityUuid, contextClass); - } - - @Override - public CustomizableFieldValueDto getByUuid(String uuid) { - CustomizableFieldValue entity = customizableFieldValueService.getByUuid(uuid); - return entity != null ? toDto(entity) : null; + service.deleteValuesForEntity(entityUuid, contextClass); } @Override public List getAll() { - return customizableFieldValueService.getAll().stream().map(CustomizableFieldValueFacadeEjb::toDto).collect(Collectors.toList()); + return service.getAll().stream().map(this::toDto).collect(Collectors.toList()); } @Override - public CustomizableFieldValueDto save(CustomizableFieldValueDto dto) { - CustomizableFieldValue entity = customizableFieldValueService.getByUuid(dto.getUuid()); + public CustomizableFieldValueDto save(@Valid @NotNull CustomizableFieldValueDto dto) { + CustomizableFieldValue entity = service.getByUuid(dto.getUuid()); entity = fillOrBuildEntity(dto, entity, true); - customizableFieldValueService.ensurePersisted(entity); + service.ensurePersisted(entity); return toDto(entity); } @Override public void delete(String uuid, DeletionDetails deletionDetails) { - CustomizableFieldValue entity = customizableFieldValueService.getByUuid(uuid); + CustomizableFieldValue entity = service.getByUuid(uuid); if (entity != null) { - customizableFieldValueService.deletePermanent(entity); + service.delete(entity, deletionDetails); } } - public CustomizableFieldValue fillOrBuildEntity(CustomizableFieldValueDto source, CustomizableFieldValue target, boolean checkChangeDate) { + @Override + public List delete(List uuids, DeletionDetails deletionDetails) { + throw new NotImplementedException(); + } + + @Override + public void restore(String uuid) { + super.restore(uuid); + } + + @Override + public List restore(List uuids) { + throw new NotImplementedException(); + } + + @Override + public List getArchivedUuidsSince(Date since) { + throw new NotImplementedException(); + } + + @Override + public long count(CustomizableFieldValueCriteria criteria) { + throw new NotImplementedException(); + } + + @Override + public List getIndexList( + CustomizableFieldValueCriteria criteria, + Integer first, + Integer max, + List sortProperties) { + throw new NotImplementedException(); + } + + @Override + public void validate(@Valid CustomizableFieldValueDto dto) throws ValidationRuntimeException { + // no validation required for customizable field values + } + + @Override + protected DeletableEntityType getDeletableEntityType() { + return DeletableEntityType.CUSTOMIZABLE_FIELD_VALUE; + } + + @Override + protected CustomizableFieldValueReferenceDto toRefDto(CustomizableFieldValue entity) { + return new CustomizableFieldValueReferenceDto(entity.getUuid()); + } + + @Override + protected void pseudonymizeDto( + CustomizableFieldValue source, + CustomizableFieldValueDto dto, + Pseudonymizer pseudonymizer, + boolean inJurisdiction) { + // no sensitive fields to pseudonymize + } + + @Override + protected void restorePseudonymizedDto( + CustomizableFieldValueDto dto, + CustomizableFieldValueDto existingDto, + CustomizableFieldValue entity, + Pseudonymizer pseudonymizer) { + // no sensitive fields to restore + } + + @Override + protected CustomizableFieldValue fillOrBuildEntity( + @NotNull CustomizableFieldValueDto source, + CustomizableFieldValue target, + boolean checkChangeDate) { if (source == null) { return null; } @@ -111,7 +197,8 @@ public CustomizableFieldValue fillOrBuildEntity(CustomizableFieldValueDto source return target; } - public static CustomizableFieldValueDto toDto(CustomizableFieldValue source) { + @Override + protected CustomizableFieldValueDto toDto(CustomizableFieldValue source) { if (source == null) { return null; } @@ -130,5 +217,13 @@ public static CustomizableFieldValueDto toDto(CustomizableFieldValue source) { @LocalBean @Stateless public static class CustomizableFieldValueFacadeEjbLocal extends CustomizableFieldValueFacadeEjb { + + public CustomizableFieldValueFacadeEjbLocal() { + } + + @Inject + public CustomizableFieldValueFacadeEjbLocal(CustomizableFieldValueService service) { + super(service); + } } } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueJoins.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueJoins.java new file mode 100644 index 00000000000..2e0453c456a --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueJoins.java @@ -0,0 +1,27 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import javax.persistence.criteria.From; + +import de.symeda.sormas.backend.common.QueryJoins; + +public class CustomizableFieldValueJoins extends QueryJoins { + + public CustomizableFieldValueJoins(From root) { + super(root); + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueQueryContext.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueQueryContext.java new file mode 100644 index 00000000000..1ec4676f3a8 --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueQueryContext.java @@ -0,0 +1,35 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.From; + +import de.symeda.sormas.backend.common.QueryContext; + +public class CustomizableFieldValueQueryContext extends QueryContext { + + public CustomizableFieldValueQueryContext(CriteriaBuilder cb, CriteriaQuery query, From root) { + super(cb, query, root, new CustomizableFieldValueJoins(root)); + } + + @Override + protected Expression createExpression(String name) { + return null; + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java index bcf5d1f7e8f..9a1367de2c2 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java @@ -28,31 +28,45 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import de.symeda.sormas.api.common.DeletableEntityType; +import de.symeda.sormas.api.common.DeletionDetails; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; -import de.symeda.sormas.backend.common.AdoServiceWithUserFilterAndJurisdiction; +import de.symeda.sormas.backend.common.AbstractCoreAdoService; +import de.symeda.sormas.backend.common.AbstractDomainObject; +import de.symeda.sormas.backend.common.DeletableAdo; /** * Service class for customizable field values. */ @Stateless @LocalBean -public class CustomizableFieldValueService extends AdoServiceWithUserFilterAndJurisdiction { +public class CustomizableFieldValueService extends AbstractCoreAdoService { @EJB private CustomizableFieldMetadataService customizableFieldMetadataService; public CustomizableFieldValueService() { - super(CustomizableFieldValue.class); + super(CustomizableFieldValue.class, DeletableEntityType.CUSTOMIZABLE_FIELD_VALUE); } @Override @SuppressWarnings("rawtypes") - public Predicate createUserFilter(CriteriaBuilder cb, CriteriaQuery cq, From from) { + protected Predicate createUserFilterInternal(CriteriaBuilder cb, CriteriaQuery cq, From from) { // No jurisdiction filtering for customizable field values return null; } + @Override + protected CustomizableFieldValueJoins toJoins(From adoPath) { + return new CustomizableFieldValueJoins(adoPath); + } + + @Override + public Predicate inJurisdictionOrOwned(CriteriaBuilder cb, CriteriaQuery query, From from) { + return cb.conjunction(); + } + public Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(CustomizableFieldValue.class); @@ -60,7 +74,8 @@ public Map getValuesForEntity(String entityUuid, Predicate predicate = cb.and( cb.equal(root.get(CustomizableFieldValue.ENTITY_UUID), entityUuid), - cb.equal(root.get(CustomizableFieldValue.CONTEXT_CLASS), contextClass)); + cb.equal(root.get(CustomizableFieldValue.CONTEXT_CLASS), contextClass), + cb.isFalse(root.get(DeletableAdo.DELETED))); cq.where(predicate); @@ -100,6 +115,33 @@ public void deleteValuesForEntity(String entityUuid, CustomizableFieldContext co } } + public void softDeleteValuesForEntity(String entityUuid, CustomizableFieldContext contextClass, DeletionDetails deletionDetails) { + Map values = getValuesForEntity(entityUuid, contextClass); + for (CustomizableFieldValue value : values.values()) { + delete(value, deletionDetails); + } + } + + public List getValuesForMetadata(String metadataUuid) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(CustomizableFieldValue.class); + Root root = cq.from(CustomizableFieldValue.class); + + cq.where( + cb.and( + cb.equal(root.get(CustomizableFieldValue.CUSTOMIZABLE_FIELD_METADATA).get(AbstractDomainObject.UUID), metadataUuid), + cb.isFalse(root.get(DeletableAdo.DELETED)))); + + return em.createQuery(cq).getResultList(); + } + + public void softDeleteValuesForMetadata(String metadataUuid, DeletionDetails deletionDetails) { + List values = getValuesForMetadata(metadataUuid); + for (CustomizableFieldValue value : values) { + delete(value, deletionDetails); + } + } + private CustomizableFieldValue createNewValue(CustomizableFieldMetadata metadata, String entityUuid, CustomizableFieldContext contextClass) { CustomizableFieldValue value = new CustomizableFieldValue(); value.setCustomizableFieldMetadata(metadata); diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/deletionconfiguration/CoreEntityDeletionService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/deletionconfiguration/CoreEntityDeletionService.java index 97222dac771..374b131ed15 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/deletionconfiguration/CoreEntityDeletionService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/deletionconfiguration/CoreEntityDeletionService.java @@ -18,6 +18,8 @@ import de.symeda.sormas.backend.caze.CaseFacadeEjb; import de.symeda.sormas.backend.common.AbstractCoreFacadeEjb; import de.symeda.sormas.backend.contact.ContactFacadeEjb; +import de.symeda.sormas.backend.customizablefield.CustomizableFieldMetadataFacadeEjb.CustomizableFieldMetadataFacadeEjbLocal; +import de.symeda.sormas.backend.customizablefield.CustomizableFieldValueFacadeEjb.CustomizableFieldValueFacadeEjbLocal; import de.symeda.sormas.backend.event.EventFacadeEjb; import de.symeda.sormas.backend.event.EventParticipantFacadeEjb; import de.symeda.sormas.backend.feature.FeatureConfigurationFacadeEjb; @@ -70,7 +72,9 @@ public CoreEntityDeletionService( EventParticipantFacadeEjb.EventParticipantFacadeEjbLocal eventParticipantFacadeEjb, ImmunizationFacadeEjb.ImmunizationFacadeEjbLocal immunizationFacadeEjb, TravelEntryFacadeEjb.TravelEntryFacadeEjbLocal travelEntryFacadeEjb, - SelfReportFacadeEjb.SelfReportFacadeEjbLocal selfReportFacadeEjb) { + SelfReportFacadeEjb.SelfReportFacadeEjbLocal selfReportFacadeEjb, + CustomizableFieldMetadataFacadeEjbLocal customizableFieldMetadataFacadeEjb, + CustomizableFieldValueFacadeEjbLocal customizableFieldValueFacadeEjb) { coreEntityFacades.add(EntityTypeFacadePair.of(DeletableEntityType.CASE, caseFacadeEjb)); coreEntityFacades.add(EntityTypeFacadePair.of(DeletableEntityType.CONTACT, contactFacadeEjb)); coreEntityFacades.add(EntityTypeFacadePair.of(DeletableEntityType.EVENT, eventFacadeEjb)); @@ -78,6 +82,8 @@ public CoreEntityDeletionService( coreEntityFacades.add(EntityTypeFacadePair.of(DeletableEntityType.IMMUNIZATION, immunizationFacadeEjb)); coreEntityFacades.add(EntityTypeFacadePair.of(DeletableEntityType.TRAVEL_ENTRY, travelEntryFacadeEjb)); coreEntityFacades.add(EntityTypeFacadePair.of(DeletableEntityType.SELF_REPORT, selfReportFacadeEjb)); + coreEntityFacades.add(EntityTypeFacadePair.of(DeletableEntityType.CUSTOMIZABLE_FIELD_METADATA, customizableFieldMetadataFacadeEjb)); + coreEntityFacades.add(EntityTypeFacadePair.of(DeletableEntityType.CUSTOMIZABLE_FIELD_VALUE, customizableFieldValueFacadeEjb)); } @SuppressWarnings("unchecked") @@ -150,7 +156,9 @@ private boolean supportsPermanentDeletion(DeletableEntityType deletableEntityTyp || deletableEntityType == DeletableEntityType.CONTACT || deletableEntityType == DeletableEntityType.EVENT || deletableEntityType == DeletableEntityType.EVENT_PARTICIPANT - || deletableEntityType == DeletableEntityType.SELF_REPORT; + || deletableEntityType == DeletableEntityType.SELF_REPORT + || deletableEntityType == DeletableEntityType.CUSTOMIZABLE_FIELD_METADATA + || deletableEntityType == DeletableEntityType.CUSTOMIZABLE_FIELD_VALUE; } @SuppressWarnings("rawtypes") diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/importexport/DatabaseExportService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/importexport/DatabaseExportService.java index 757f8d28940..8844d90b608 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/importexport/DatabaseExportService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/importexport/DatabaseExportService.java @@ -42,7 +42,6 @@ import de.symeda.sormas.api.feature.FeatureConfigurationDto; import de.symeda.sormas.api.importexport.DatabaseTable; -import de.symeda.sormas.api.therapy.Drug; import de.symeda.sormas.backend.action.Action; import de.symeda.sormas.backend.activityascase.ActivityAsCase; import de.symeda.sormas.backend.campaign.Campaign; @@ -59,6 +58,8 @@ import de.symeda.sormas.backend.common.ConfigFacadeEjb.ConfigFacadeEjbLocal; import de.symeda.sormas.backend.contact.Contact; import de.symeda.sormas.backend.customizableenum.CustomizableEnumValue; +import de.symeda.sormas.backend.customizablefield.CustomizableFieldMetadata; +import de.symeda.sormas.backend.customizablefield.CustomizableFieldValue; import de.symeda.sormas.backend.deletionconfiguration.DeletionConfiguration; import de.symeda.sormas.backend.disease.DiseaseConfiguration; import de.symeda.sormas.backend.document.Document; @@ -179,6 +180,8 @@ public class DatabaseExportService { EXPORT_CONFIGS.put(DatabaseTable.POINTS_OF_ENTRY, PointOfEntry.TABLE_NAME); EXPORT_CONFIGS.put(DatabaseTable.OUTBREAKS, Outbreak.TABLE_NAME); EXPORT_CONFIGS.put(DatabaseTable.CUSTOMIZABLE_ENUM_VALUES, CustomizableEnumValue.TABLE_NAME); + EXPORT_CONFIGS.put(DatabaseTable.CUSTOMIZABLE_FIELD_METADATA, CustomizableFieldMetadata.TABLE_NAME); + EXPORT_CONFIGS.put(DatabaseTable.CUSTOMIZABLE_FIELD_VALUE, CustomizableFieldValue.TABLE_NAME); EXPORT_CONFIGS.put(DatabaseTable.SYMPTOMS, Symptoms.TABLE_NAME); EXPORT_CONFIGS.put(DatabaseTable.CAMPAIGNS, Campaign.TABLE_NAME); EXPORT_CONFIGS.put(DatabaseTable.CAMPAIGN_CAMPAIGNFORMMETA, Campaign.CAMPAIGN_CAMPAIGNFORMMETA_TABLE_NAME); diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index b9b2ceaf13b..09c4d629544 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15626,8 +15626,8 @@ BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldmetadata FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldmetadata_history', true); ALTER TABLE customizablefieldmetadata_history OWNER TO sormas_user; -DROP TRIGGER IF EXISTS delete_history_trigger_customizablefieldmetadata ON customizablefieldmetadata; -CREATE TRIGGER delete_history_trigger_customizablefieldmetadata +DROP TRIGGER IF EXISTS delete_history_trigger ON customizablefieldmetadata; +CREATE TRIGGER delete_history_trigger AFTER DELETE ON customizablefieldmetadata FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldmetadata_history', 'id'); @@ -15691,8 +15691,8 @@ CREATE TRIGGER versioning_trigger BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldvalue FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldvalue_history', true); -DROP TRIGGER IF EXISTS delete_history_trigger_customizablefieldvalue ON customizablefieldvalue; -CREATE TRIGGER delete_history_trigger_customizablefieldvalue +DROP TRIGGER IF EXISTS delete_history_trigger ON customizablefieldvalue; +CREATE TRIGGER delete_history_trigger AFTER DELETE ON customizablefieldvalue FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldvalue_history', 'id'); diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/EntityMappingTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/EntityMappingTest.java index ef78357b0bf..1af94ad9b68 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/EntityMappingTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/EntityMappingTest.java @@ -52,6 +52,8 @@ import de.symeda.sormas.api.clinicalcourse.HealthConditionsDto; import de.symeda.sormas.api.contact.ContactDto; import de.symeda.sormas.api.customizableenum.CustomizableEnumValueDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.disease.DiseaseConfigurationDto; import de.symeda.sormas.api.document.DocumentDto; import de.symeda.sormas.api.epidata.EpiDataDto; @@ -121,6 +123,8 @@ import de.symeda.sormas.backend.common.NotExposedToApi; import de.symeda.sormas.backend.contact.Contact; import de.symeda.sormas.backend.customizableenum.CustomizableEnumValue; +import de.symeda.sormas.backend.customizablefield.CustomizableFieldMetadata; +import de.symeda.sormas.backend.customizablefield.CustomizableFieldValue; import de.symeda.sormas.backend.disease.DiseaseConfiguration; import de.symeda.sormas.backend.document.Document; import de.symeda.sormas.backend.epidata.EpiData; @@ -197,6 +201,8 @@ public class EntityMappingTest { mappings.put(Continent.class, ContinentDto.class); mappings.put(Country.class, CountryDto.class); mappings.put(CustomizableEnumValue.class, CustomizableEnumValueDto.class); + mappings.put(CustomizableFieldMetadata.class, CustomizableFieldMetadataDto.class); + mappings.put(CustomizableFieldValue.class, CustomizableFieldValueDto.class); mappings.put(DiseaseConfiguration.class, DiseaseConfigurationDto.class); mappings.put(District.class, DistrictDto.class); mappings.put(Document.class, DocumentDto.class); diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java index ff0e79059f2..1916a217f62 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java @@ -39,6 +39,8 @@ import de.symeda.sormas.api.customizablefield.CustomizableFieldType; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade; +import de.symeda.sormas.api.exposure.ExposureDto; +import de.symeda.sormas.api.exposure.ExposureType; import de.symeda.sormas.api.person.PersonDto; import de.symeda.sormas.api.person.Sex; import de.symeda.sormas.api.user.UserDto; @@ -58,8 +60,12 @@ public void init() { } @ParameterizedTest - @EnumSource(CustomizableFieldContext.class) - void testRenderCustomizableFields(CustomizableFieldContext context) { + @EnumSource(value = CustomizableFieldContext.class, + names = { + "CASE", + "EPIDATA", + "EXPOSURE" }) + void testCaseRelatedCustomizableFields(CustomizableFieldContext context) { CustomizableFieldMetadataFacade metadataFacade = getBean(CustomizableFieldMetadataFacadeEjb.CustomizableFieldMetadataFacadeEjbLocal.class); CustomizableFieldValueFacade valueFacade = getBean(CustomizableFieldValueFacadeEjb.CustomizableFieldValueFacadeEjbLocal.class); @@ -83,7 +89,15 @@ void testRenderCustomizableFields(CustomizableFieldContext context) { new java.util.Date(), rdcf); + caze.getEpiData().getExposures().add(ExposureDto.build(ExposureType.TRAVEL)); + caze = getCaseFacade().save(caze); + String entityUuid = getEntityUuidForContext(context, caze); + + // in case other contexts are added and no corresponding case data is created this should fail + // in case of failure update the creation of the case and add the aditional necessary data + assertThat("Unexpected entity UUID null for context " + context, entityUuid, notNullValue()); + CustomizableFieldValueDto valueDto = new CustomizableFieldValueDto(); valueDto.setValue("custom-value"); Map fieldValues = Map.of(savedMetadata.getUuid(), valueDto); @@ -106,6 +120,10 @@ private String getEntityUuidForContext(CustomizableFieldContext context, CaseDat return caze.getUuid(); case EPIDATA: return caze.getEpiData().getUuid(); + case EXPOSURE: + return caze.getEpiData().getExposures() != null && !caze.getEpiData().getExposures().isEmpty() + ? caze.getEpiData().getExposures().get(0).getUuid() + : null; default: throw new IllegalArgumentException("Unhandled context: " + context); } diff --git a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java index 73fe49c5631..740376195ad 100644 --- a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java +++ b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldMetadataResource.java @@ -113,14 +113,14 @@ public CustomizableFieldMetadataDto cloneField(@PathParam("uuid") String uuid, @ @POST @Path("/{uuid}/activate") public Response activate(@PathParam("uuid") String uuid) { - getFacade().activateField(uuid); + getFacade().setFieldActive(uuid, true); return Response.ok().build(); } @POST @Path("/{uuid}/deactivate") public Response deactivate(@PathParam("uuid") String uuid) { - getFacade().deactivateField(uuid); + getFacade().setFieldActive(uuid, false); return Response.ok().build(); } From 87374b76fa47d9d1284144974c2cc985172bce2e Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Thu, 16 Apr 2026 13:10:47 +0200 Subject: [PATCH 49/55] Modified field value metadata uuid to strong typed --- .../CustomizableFieldValueFacade.java | 18 ++++----- .../sormas/backend/caze/CaseFacadeEjb.java | 9 ++++- .../CustomizableFieldValueFacadeEjb.java | 39 +++++++++++++++---- .../CustomizableFieldValueService.java | 36 +++++++---------- .../CustomizableFieldFacadeEjbTest.java | 8 ++-- .../CustomizableFieldValueResource.java | 16 +++++++- .../sormas/ui/utils/AbstractEditForm.java | 8 ++-- .../components/CustomizableFieldsGroup.java | 28 +++++++------ 8 files changed, 99 insertions(+), 63 deletions(-) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java index ea08fb5f464..3b411c1c0b0 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldValueFacade.java @@ -30,27 +30,25 @@ public interface CustomizableFieldValueFacade extends CoreFacade { /** - * Load all custom field values for a specific entity - * Returns a map of field metadata UUID -> field value DTO + * Load all custom field values for a specific entity. + * Returns a map of field metadata DTO -> field value DTO. */ - Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass); + Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass); /** * Save all custom field values for an entity in a single context. - * fieldValues: map of field metadata UUID -> value DTO (the raw string is read from {@link CustomizableFieldValueDto#getValue()}) + * fieldValues: map of field metadata DTO -> value DTO (the raw string is read from {@link CustomizableFieldValueDto#getValue()}) */ - default void saveEntityCustomFields( + void saveEntityCustomFields( String entityUuid, CustomizableFieldContext contextClass, - Map fieldValues) { - saveEntityCustomFields(entityUuid, Map.of(contextClass, fieldValues)); - } + Map fieldValues); /** * Save all custom field values for an entity across multiple contexts in one call. - * fieldsByContext: map of context -> (field metadata UUID -> value DTO) + * fieldsByContext: map of context -> (field metadata DTO -> value DTO) */ - void saveEntityCustomFields(String entityUuid, Map> fieldsByContext); + void saveEntityCustomFields(String entityUuid, Map> fieldsByContext); /** * Delete all custom field values for an entity diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/caze/CaseFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/caze/CaseFacadeEjb.java index d40289a68d4..66fe1227262 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/caze/CaseFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/caze/CaseFacadeEjb.java @@ -137,6 +137,7 @@ import de.symeda.sormas.api.contact.ContactDto; import de.symeda.sormas.api.contact.ContactReferenceDto; import de.symeda.sormas.api.customizableenum.CustomizableEnumType; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; import de.symeda.sormas.api.disease.DiseaseVariant; import de.symeda.sormas.api.document.DocumentRelatedEntityType; import de.symeda.sormas.api.epidata.EpiDataDto; @@ -254,6 +255,7 @@ import de.symeda.sormas.backend.contact.ContactService; import de.symeda.sormas.backend.contact.VisitSummaryExportDetails; import de.symeda.sormas.backend.customizableenum.CustomizableEnumFacadeEjb.CustomizableEnumFacadeEjbLocal; +import de.symeda.sormas.backend.customizablefield.CustomizableFieldValueService; import de.symeda.sormas.backend.disease.DiseaseConfigurationFacadeEjb.DiseaseConfigurationFacadeEjbLocal; import de.symeda.sormas.backend.document.Document; import de.symeda.sormas.backend.document.DocumentRelatedEntityService; @@ -491,6 +493,8 @@ public class CaseFacadeEjb extends AbstractCoreFacadeEjb tokens = surveyService.findBy(new SurveyTokenCriteria().caseAssignedTo(otherCase.toReference())); tokens.forEach(s -> { diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java index 6116d9164ee..23cbd69bf33 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java @@ -34,6 +34,8 @@ import de.symeda.sormas.api.common.DeletionDetails; import de.symeda.sormas.api.common.progress.ProcessedEntity; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueCriteria; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade; @@ -54,6 +56,8 @@ public class CustomizableFieldValueFacadeEjb @EJB private CustomizableFieldMetadataService customizableFieldMetadataService; + @EJB + private CustomizableFieldMetadataFacade customizableFieldMetadataFacade; public CustomizableFieldValueFacadeEjb() { } @@ -64,19 +68,40 @@ public CustomizableFieldValueFacadeEjb(CustomizableFieldValueService service) { } @Override - public Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { - Map entities = service.getValuesForEntity(entityUuid, contextClass); + public void saveEntityCustomFields( + String entityUuid, + CustomizableFieldContext contextClass, + Map fieldValues) { + saveEntityCustomFields(entityUuid, Map.of(contextClass, fieldValues)); + } + + @Override + public Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { + Map entities = service.getValuesForEntity(entityUuid, contextClass); - Map result = new HashMap<>(); - for (Map.Entry entry : entities.entrySet()) { - result.put(entry.getKey(), toDto(entry.getValue())); + Map result = new HashMap<>(); + for (Map.Entry entry : entities.entrySet()) { + CustomizableFieldMetadataDto metadataDto = customizableFieldMetadataFacade.getByUuid(entry.getKey().getUuid()); + result.put(metadataDto, toDto(entry.getValue())); } return result; } @Override - public void saveEntityCustomFields(String entityUuid, Map> fieldsByContext) { - fieldsByContext.forEach((context, fields) -> service.saveEntityValues(entityUuid, context, fields)); + public void saveEntityCustomFields( + String entityUuid, + Map> fieldsByContext) { + fieldsByContext.forEach((context, fields) -> { + Map entityFields = new HashMap<>(); + for (Map.Entry entry : fields.entrySet()) { + CustomizableFieldMetadata metadataEntity = customizableFieldMetadataService.getByUuid(entry.getKey().getUuid()); + if (metadataEntity == null) { + throw new IllegalArgumentException("Field metadata not found: " + entry.getKey().getUuid()); + } + entityFields.put(metadataEntity, entry.getValue()); + } + service.saveEntityValues(entityUuid, context, entityFields); + }); } @Override diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java index 9a1367de2c2..a4eaa9d834e 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueService.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.Map; -import javax.ejb.EJB; import javax.ejb.LocalBean; import javax.ejb.Stateless; import javax.persistence.criteria.CriteriaBuilder; @@ -43,9 +42,6 @@ @LocalBean public class CustomizableFieldValueService extends AbstractCoreAdoService { - @EJB - private CustomizableFieldMetadataService customizableFieldMetadataService; - public CustomizableFieldValueService() { super(CustomizableFieldValue.class, DeletableEntityType.CUSTOMIZABLE_FIELD_VALUE); } @@ -67,7 +63,7 @@ public Predicate inJurisdictionOrOwned(CriteriaBuilder cb, CriteriaQuery quer return cb.conjunction(); } - public Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { + public Map getValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(CustomizableFieldValue.class); Root root = cq.from(CustomizableFieldValue.class); @@ -81,42 +77,36 @@ public Map getValuesForEntity(String entityUuid, List values = em.createQuery(cq).getResultList(); - // Convert to map for easier access by field metadata UUID - Map result = new HashMap<>(); + Map result = new HashMap<>(); for (CustomizableFieldValue value : values) { - result.put(value.getCustomizableFieldMetadata().getUuid(), value); + result.put(value.getCustomizableFieldMetadata(), value); } return result; } - public void saveEntityValues(String entityUuid, CustomizableFieldContext contextClass, Map fieldUuidToValue) { - // Get existing values - Map existing = getValuesForEntity(entityUuid, contextClass); - - // Update or create values - for (Map.Entry entry : fieldUuidToValue.entrySet()) { - String fieldMetadataUuid = entry.getKey(); - CustomizableFieldMetadata metadata = customizableFieldMetadataService.getByUuid(fieldMetadataUuid); - - if (metadata == null) { - throw new IllegalArgumentException("Field metadata not found: " + fieldMetadataUuid); - } + public void saveEntityValues( + String entityUuid, + CustomizableFieldContext contextClass, + Map metadataToValue) { + Map existing = getValuesForEntity(entityUuid, contextClass); - CustomizableFieldValue value = existing.getOrDefault(fieldMetadataUuid, createNewValue(metadata, entityUuid, contextClass)); + for (Map.Entry entry : metadataToValue.entrySet()) { + CustomizableFieldMetadata metadata = entry.getKey(); + CustomizableFieldValue value = existing.getOrDefault(metadata, createNewValue(metadata, entityUuid, contextClass)); value.setValue(entry.getValue().getValue()); ensurePersisted(value); } } public void deleteValuesForEntity(String entityUuid, CustomizableFieldContext contextClass) { - Map values = getValuesForEntity(entityUuid, contextClass); + Map values = getValuesForEntity(entityUuid, contextClass); for (CustomizableFieldValue value : values.values()) { em.remove(value); } } public void softDeleteValuesForEntity(String entityUuid, CustomizableFieldContext contextClass, DeletionDetails deletionDetails) { - Map values = getValuesForEntity(entityUuid, contextClass); + Map values = getValuesForEntity(entityUuid, contextClass); for (CustomizableFieldValue value : values.values()) { delete(value, deletionDetails); } diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java index 1916a217f62..e7cd856b5b0 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldFacadeEjbTest.java @@ -100,18 +100,18 @@ void testCaseRelatedCustomizableFields(CustomizableFieldContext context) { CustomizableFieldValueDto valueDto = new CustomizableFieldValueDto(); valueDto.setValue("custom-value"); - Map fieldValues = Map.of(savedMetadata.getUuid(), valueDto); + Map fieldValues = Map.of(savedMetadata, valueDto); valueFacade.saveEntityCustomFields(entityUuid, context, fieldValues); List activeFields = metadataFacade.getActiveFieldsForContext(context); - Map values = valueFacade.getValuesForEntity(entityUuid, context); + Map values = valueFacade.getValuesForEntity(entityUuid, context); assertThat(activeFields, hasSize(1)); assertThat(activeFields.get(0).getUuid(), is(savedMetadata.getUuid())); assertThat(activeFields.get(0).getName(), is(equalTo("customField_" + context.name().toLowerCase()))); - assertThat(values, hasKey(savedMetadata.getUuid())); - assertThat(values.get(savedMetadata.getUuid()).getValue(), is(equalTo("custom-value"))); + assertThat(values, hasKey(savedMetadata)); + assertThat(values.get(savedMetadata).getValue(), is(equalTo("custom-value"))); } private String getEntityUuidForContext(CustomizableFieldContext context, CaseDataDto caze) { diff --git a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldValueResource.java b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldValueResource.java index 94fe4dc319a..62c371d5b96 100644 --- a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldValueResource.java +++ b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/CustomizableFieldValueResource.java @@ -15,6 +15,7 @@ package de.symeda.sormas.rest.resources; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,6 +34,7 @@ import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.common.DeletionDetails; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade; @@ -65,7 +67,10 @@ public CustomizableFieldValueDto getByUuid(@PathParam("uuid") String uuid) { public Map getValuesForEntity( @PathParam("entityUuid") String entityUuid, @QueryParam("contextClass") CustomizableFieldContext contextClass) { - return getFacade().getValuesForEntity(entityUuid, contextClass); + Map typed = getFacade().getValuesForEntity(entityUuid, contextClass); + Map result = new HashMap<>(); + typed.forEach((metadata, value) -> result.put(metadata.getUuid(), value)); + return result; } @POST @@ -74,7 +79,14 @@ public Response saveEntityCustomFields( @PathParam("entityUuid") String entityUuid, @QueryParam("contextClass") CustomizableFieldContext contextClass, Map fieldValues) { - getFacade().saveEntityCustomFields(entityUuid, contextClass, fieldValues); + Map typed = new HashMap<>(); + fieldValues.forEach((metadataUuid, value) -> { + CustomizableFieldMetadataDto metadataDto = FacadeProvider.getCustomizableFieldMetadataFacade().getByUuid(metadataUuid); + if (metadataDto != null) { + typed.put(metadataDto, value); + } + }); + getFacade().saveEntityCustomFields(entityUuid, contextClass, typed); return Response.ok().build(); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java index 55a0eeaff25..b771dd42186 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java @@ -75,7 +75,7 @@ public abstract class AbstractEditForm extends AbstractForm implements private boolean setServerDiseaseAsDefault; private List customizableFieldsMetadata; - private Map customizableFieldsValues; + private Map customizableFieldsValues; protected AbstractEditForm(Class type, String propertyI18nPrefix) { this(type, propertyI18nPrefix, true, null, null); @@ -168,9 +168,9 @@ protected List getCustomizableFieldsMetadata() { * The form will use this data to populate customizable field components. * * @param values - * Map of customizable field values keyed by field metadata UUID, pre-loaded by the controller + * Map of customizable field values keyed by field metadata DTO, pre-loaded by the controller */ - public void setCustomizableFieldsValues(Map values) { + public void setCustomizableFieldsValues(Map values) { this.customizableFieldsValues = values; } @@ -179,7 +179,7 @@ public void setCustomizableFieldsValues(Map v * * @return Map of customizable field values, or empty map if not set */ - protected Map getCustomizableFieldsValues() { + protected Map getCustomizableFieldsValues() { return customizableFieldsValues != null ? customizableFieldsValues : new HashMap<>(); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java index 6d59781eb55..be3e49c2da6 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java @@ -61,10 +61,10 @@ public class CustomizableFieldsGroup extends VerticalLayout { private static final String STYLE_NAME_CUSTOMIZABLE_FIELDS_GROUP = "customizable-fields-group"; private final CustomizableFieldGroup group; - private final Map> fieldComponents; + private final Map> fieldComponents; private final List> valueChangeListeners = new ArrayList<>(); private List fieldsMetadata; - private Map fieldsValues; + private Map fieldsValues; @SuppressWarnings("java:S1948") // CustomizableFieldVisibilityContext implements Serializable; Sonar can't verify it private CustomizableFieldVisibilityContext visibilityContext; @@ -147,7 +147,7 @@ public void setFieldsMetadata(List metadata) { * @param values * map of field values keyed by metadata UUID */ - public void setFieldsValues(Map values) { + public void setFieldsValues(Map values) { this.fieldsValues = values; } @@ -224,9 +224,8 @@ private void addLineRow(List lineFields) { for (CustomizableFieldMetadataDto metadata : lineFields) { CustomizableFieldInput field = CustomizableFieldInputFactory.create(metadata); - CustomizableFieldValueDto dto = (fieldsValues != null && fieldsValues.containsKey(metadata.getUuid())) - ? fieldsValues.get(metadata.getUuid()) - : new CustomizableFieldValueDto(); + CustomizableFieldValueDto dto = + (fieldsValues != null && fieldsValues.containsKey(metadata)) ? fieldsValues.get(metadata) : new CustomizableFieldValueDto(); field.setFieldValue(dto); field.setWidth(100, Unit.PERCENTAGE); @@ -239,7 +238,7 @@ private void addLineRow(List lineFields) { ((CustomizableFieldInput) field).addValueChangeListener(listener); } - fieldComponents.put(metadata.getUuid(), field); + fieldComponents.put(metadata, field); } addComponent(row); @@ -254,7 +253,12 @@ private void addLineRow(List lineFields) { */ @SuppressWarnings("java:S1452") // wildcard return type is intentional for CustomizableFieldInput public CustomizableFieldInput getFieldByMetadataUuid(String metadataUuid) { - return fieldComponents.get(metadataUuid); + return fieldComponents.entrySet() + .stream() + .filter(e -> metadataUuid.equals(e.getKey().getUuid())) + .map(Map.Entry::getValue) + .findFirst() + .orElse(null); } /** @@ -262,7 +266,7 @@ public CustomizableFieldInput getFieldByMetadataUuid(String metadataUuid) { * * @return map of field components keyed by metadata UUID */ - public Map> getAllFields() { + public Map> getAllFields() { return new HashMap<>(fieldComponents); } @@ -272,9 +276,9 @@ public Map> getAllFields() { * * @return map of all current field values */ - public Map getFieldsValues() { - Map result = new HashMap<>(); - for (Map.Entry> entry : fieldComponents.entrySet()) { + public Map getFieldsValues() { + Map result = new HashMap<>(); + for (Map.Entry> entry : fieldComponents.entrySet()) { CustomizableFieldValueDto value = entry.getValue().getFieldValue(); if (value != null && value.getValue() != null) { result.put(entry.getKey(), value); From 67d390e2c3fb1c7e242d3dce7eac52357eedfa21 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Fri, 17 Apr 2026 15:57:19 +0200 Subject: [PATCH 50/55] Tweeked enum handling, added CustomizableFieldValue table partitions --- .../CustomizableFieldContext.java | 2 - .../CustomizableFieldContextConverter.java | 35 ----------- .../CustomizableFieldGroupConverter.java | 35 ----------- .../CustomizableFieldMetadata.java | 5 +- .../CustomizableFieldValue.java | 5 +- .../src/main/resources/sql/sormas_schema.sql | 54 +++++++++++----- .../CustomizableFieldSchemaTest.java | 62 +++++++++++++++++++ 7 files changed, 105 insertions(+), 93 deletions(-) delete mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldContextConverter.java delete mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldGroupConverter.java create mode 100644 sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldSchemaTest.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java index e5eb3e49db6..76f1a6fb3c1 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldContext.java @@ -48,8 +48,6 @@ public static CustomizableFieldContext fromDtoClass(Class d } public static CustomizableFieldContext fromDtoClassName(String dtoClassName) { - // well had to write a converter for this let's let the agents know if someone uses AI - // @agents - update converter class CustomizableFieldContextConverter Class rawClass; try { rawClass = Class.forName(dtoClassName); diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldContextConverter.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldContextConverter.java deleted file mode 100644 index ff4c59920f9..00000000000 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldContextConverter.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SORMAS® - Surveillance Outbreak Response Management & Analysis System - * Copyright © 2016-2026 SORMAS Foundation gGmbH - * 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.backend.customizablefield; - -import javax.persistence.AttributeConverter; -import javax.persistence.Converter; - -import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; - -@Converter(autoApply = false) -public class CustomizableFieldContextConverter implements AttributeConverter { - - @Override - public String convertToDatabaseColumn(CustomizableFieldContext attribute) { - return attribute != null ? attribute.getContextClassName() : null; - } - - @Override - public CustomizableFieldContext convertToEntityAttribute(String dbData) { - return dbData != null ? CustomizableFieldContext.fromDtoClassName(dbData) : null; - } -} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldGroupConverter.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldGroupConverter.java deleted file mode 100644 index 13315860540..00000000000 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldGroupConverter.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SORMAS® - Surveillance Outbreak Response Management & Analysis System - * Copyright © 2016-2026 SORMAS Foundation gGmbH - * 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.backend.customizablefield; - -import javax.persistence.AttributeConverter; -import javax.persistence.Converter; - -import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; - -@Converter(autoApply = false) -public class CustomizableFieldGroupConverter implements AttributeConverter { - - @Override - public String convertToDatabaseColumn(CustomizableFieldGroup attribute) { - return attribute != null ? attribute.getKey() : null; - } - - @Override - public CustomizableFieldGroup convertToEntityAttribute(String dbData) { - return dbData != null ? CustomizableFieldGroup.fromKey(dbData) : null; - } -} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java index a6df6186513..70c49a28b8d 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java @@ -16,7 +16,6 @@ package de.symeda.sormas.backend.customizablefield; import javax.persistence.Column; -import javax.persistence.Convert; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -105,7 +104,7 @@ public void setFieldType(CustomizableFieldType fieldType) { } @Column(length = 256, nullable = false) - @Convert(converter = CustomizableFieldContextConverter.class) + @Enumerated(EnumType.STRING) public CustomizableFieldContext getContextClass() { return contextClass; } @@ -115,7 +114,7 @@ public void setContextClass(CustomizableFieldContext contextClass) { } @Column(length = 256) - @Convert(converter = CustomizableFieldGroupConverter.class) + @Enumerated(EnumType.STRING) public CustomizableFieldGroup getUiGroup() { return uiGroup; } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java index 0eee398bbf3..129af834315 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValue.java @@ -16,8 +16,9 @@ package de.symeda.sormas.backend.customizablefield; import javax.persistence.Column; -import javax.persistence.Convert; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @@ -70,7 +71,7 @@ public void setEntityUuid(String entityUuid) { } @Column(length = 256, nullable = false) - @Convert(converter = CustomizableFieldContextConverter.class) + @Enumerated(EnumType.STRING) public CustomizableFieldContext getContextClass() { return contextClass; } diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 09c4d629544..0eb47671f85 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15567,9 +15567,12 @@ CREATE TABLE IF NOT EXISTS customizablefieldmetadata ( uuid character varying(36) NOT NULL UNIQUE, changeDate timestamp without time zone NOT NULL DEFAULT NOW(), creationDate timestamp without time zone NOT NULL DEFAULT NOW(), - deleted boolean NOT NULL DEFAULT false, + deleted boolean DEFAULT false, deletionreason varchar(255), otherdeletionreason text, + archived boolean DEFAULT false, + archiveundonereason varchar(512), + endofprocessingdate timestamp without time zone, -- Core field metadata name character varying(512) NOT NULL, @@ -15621,28 +15624,36 @@ ALTER INDEX idx_customizablefieldmetadata_deleted OWNER TO sormas_user; -- CustomizableFieldMetadata history tables CREATE TABLE customizablefieldmetadata_history (LIKE customizablefieldmetadata); + +DROP TRIGGER IF EXISTS versioning_trigger ON customizablefieldmetadata; CREATE TRIGGER versioning_trigger BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldmetadata FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldmetadata_history', true); -ALTER TABLE customizablefieldmetadata_history OWNER TO sormas_user; DROP TRIGGER IF EXISTS delete_history_trigger ON customizablefieldmetadata; CREATE TRIGGER delete_history_trigger AFTER DELETE ON customizablefieldmetadata FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldmetadata_history', 'id'); +ALTER TABLE customizablefieldmetadata_history OWNER TO sormas_user; + -- Create CustomizableFieldValue table +-- Partitioned by contextClass (LIST) matching CustomizableFieldContext enum values. +-- A new partition must be added here whenever a new value is added to that enum. CREATE TABLE IF NOT EXISTS customizablefieldvalue ( id bigint NOT NULL, - uuid character varying(36) NOT NULL UNIQUE, + uuid character varying(36) NOT NULL, changeDate timestamp(3) NOT NULL DEFAULT NOW(), creationDate timestamp(3) NOT NULL DEFAULT NOW(), - deleted boolean NOT NULL DEFAULT false, + deleted boolean DEFAULT false, deletionreason varchar(255), otherdeletionreason text, + archived boolean DEFAULT false, + archiveundonereason varchar(512), + endofprocessingdate timestamp without time zone, -- Fkey to metadata - customizablefieldmetadata_id bigint NOT NULL REFERENCES customizablefieldmetadata(id) ON DELETE CASCADE, + customizablefieldmetadata_id bigint NOT NULL, -- Generic entity reference entityUuid character varying(36) NOT NULL, @@ -15653,10 +15664,22 @@ CREATE TABLE IF NOT EXISTS customizablefieldvalue ( change_user_id bigint, sys_period tstzrange NOT NULL, - - PRIMARY KEY (id), + + PRIMARY KEY (id, contextClass), + UNIQUE(uuid, contextClass), UNIQUE(customizablefieldmetadata_id, entityUuid, contextClass) -); +) PARTITION BY LIST (contextClass); + +-- One partition per CustomizableFieldContext enum value +CREATE TABLE customizablefieldvalue_case + PARTITION OF customizablefieldvalue + FOR VALUES IN ('CASE'); +CREATE TABLE customizablefieldvalue_epidata + PARTITION OF customizablefieldvalue + FOR VALUES IN ('EPIDATA'); +CREATE TABLE customizablefieldvalue_exposure + PARTITION OF customizablefieldvalue + FOR VALUES IN ('EXPOSURE'); CREATE INDEX idx_customizablefieldvalue_uuid ON customizablefieldvalue (uuid); @@ -15669,8 +15692,6 @@ CREATE INDEX idx_customizablefieldvalue_fieldMetadata CREATE INDEX idx_customizablefieldvalue_deleted ON customizablefieldvalue (deleted); -ALTER TABLE customizablefieldvalue OWNER TO sormas_user; - ALTER TABLE customizablefieldvalue ADD CONSTRAINT fk_customizablefieldvalue_metadata FOREIGN KEY (customizablefieldmetadata_id) @@ -15679,14 +15700,16 @@ ALTER TABLE customizablefieldvalue ALTER TABLE customizablefieldvalue ADD CONSTRAINT fk_change_user_id FOREIGN KEY (change_user_id) REFERENCES users (id); -ALTER INDEX idx_customizablefieldvalue_uuid OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldvalue_entityUuid OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldvalue_contextEntity OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldvalue_fieldMetadata OWNER TO sormas_user; -ALTER INDEX idx_customizablefieldvalue_deleted OWNER TO sormas_user; +ALTER TABLE customizablefieldvalue_case OWNER TO sormas_user; +ALTER TABLE customizablefieldvalue_epidata OWNER TO sormas_user; +ALTER TABLE customizablefieldvalue_exposure OWNER TO sormas_user; +ALTER TABLE customizablefieldvalue OWNER TO sormas_user; -- CustomizableFieldValue history tables CREATE TABLE customizablefieldvalue_history (LIKE customizablefieldvalue); + + +DROP TRIGGER IF EXISTS versioning_trigger ON customizablefieldvalue; CREATE TRIGGER versioning_trigger BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldvalue FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldvalue_history', true); @@ -15697,7 +15720,6 @@ CREATE TRIGGER delete_history_trigger FOR EACH ROW EXECUTE PROCEDURE delete_history_trigger('customizablefieldvalue_history', 'id'); ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; - INSERT INTO schema_version (version_number, comment) VALUES (619, '#13828 - Add history tables for customizable fields'); -- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** \ No newline at end of file diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldSchemaTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldSchemaTest.java new file mode 100644 index 00000000000..4dafcc4ae63 --- /dev/null +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldSchemaTest.java @@ -0,0 +1,62 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.backend.customizablefield; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Objects; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; + +/** + * Verifies that for every {@link CustomizableFieldContext} enum value there is a + * corresponding LIST partition of {@code customizablefieldvalue} declared in + * {@code sormas_schema.sql}. + *

+ * Convention: the partition table is named + * {@code customizablefieldvalue_}. + *

+ * When a new context is added to the enum, this test will fail until the matching + * {@code CREATE TABLE customizablefieldvalue_ PARTITION OF customizablefieldvalue + * FOR VALUES IN ('')} migration is added to the schema. + */ +class CustomizableFieldSchemaTest { + + private static final String PARTITION_TABLE_PREFIX = "customizablefieldvalue_"; + + @ParameterizedTest + @EnumSource(CustomizableFieldContext.class) + void testPartitionTableExistsForContext(CustomizableFieldContext context) throws IOException, URISyntaxException { + String schema = new String( + Files.readAllBytes(Paths.get(Objects.requireNonNull(getClass().getClassLoader().getResource("sql/sormas_schema.sql")).toURI()))); + + String expectedTableName = PARTITION_TABLE_PREFIX + context.name().toLowerCase(); + String expectedStatement = "CREATE TABLE " + expectedTableName; + + assertTrue( + schema.contains(expectedStatement), + "Missing partition table declaration in sormas_schema.sql for CustomizableFieldContext." + context.name() + ": expected to find '" + + expectedStatement + "'. Add a PARTITION OF customizablefieldvalue FOR VALUES IN ('" + context.name() + + "') migration alongside the new enum value."); + } +} From 8cd56099823e6a03cad2c3ab6ffa1388eac4e69d Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Fri, 17 Apr 2026 16:23:57 +0200 Subject: [PATCH 51/55] Hide customizable field group on UI when no fields --- .../components/CustomizableFieldsGroup.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java index be3e49c2da6..a6082e5545b 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/components/CustomizableFieldsGroup.java @@ -24,6 +24,7 @@ import com.vaadin.data.HasValue; import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; @@ -170,6 +171,7 @@ public void updateFieldsDisplay() { fieldComponents.clear(); if (fieldsMetadata == null || fieldsMetadata.isEmpty()) { + setVisible(false); return; } @@ -185,9 +187,12 @@ public void updateFieldsDisplay() { } if (groupFields.isEmpty()) { + setVisible(false); return; } + setVisible(true); + // Sort: non-null positions first (ascending), null positions last (stable) groupFields.sort(Comparator.comparing(CustomizableFieldMetadataDto::getUiLinePosition, Comparator.nullsLast(Comparator.naturalOrder()))); @@ -241,6 +246,17 @@ private void addLineRow(List lineFields) { fieldComponents.put(metadata, field); } + // When a single field has a weight < 1.0, expand ratio has no effect on a lone component. + // Add an invisible filler with the complementary ratio so the field takes only its proportional share. + if (lineFields.size() == 1) { + CustomizableFieldMetadataDto sole = lineFields.get(0); + if (sole.getUiLineWeight() != null && sole.getUiLineWeight() < 1.0f) { + Label filler = new Label(); + row.addComponent(filler); + row.setExpandRatio(filler, 1.0f - sole.getUiLineWeight()); + } + } + addComponent(row); } From f1f9653d593b6b2a29908ba625bf85cdd9f2b1b2 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Mon, 20 Apr 2026 10:12:42 +0200 Subject: [PATCH 52/55] Added customizable field admin UI and Case Form integration --- .../CustomizableFieldGroup.java | 27 +- .../de/symeda/sormas/api/i18n/Captions.java | 44 ++ .../de/symeda/sormas/api/i18n/Strings.java | 19 + .../sormas/api/user/DefaultUserRole.java | 1 + .../de/symeda/sormas/api/user/UserRight.java | 2 + .../src/main/resources/captions.properties | 49 +++ .../src/main/resources/strings.properties | 19 + .../backend/contact/ContactFacadeEjb.java | 1 + .../backend/epidata/EpiDataFacadeEjb.java | 15 + .../resources/META-INF/glassfish-ejb-jar.xml | 21 +- .../src/main/resources/sql/sormas_schema.sql | 5 + .../src/main/webapp/WEB-INF/glassfish-web.xml | 13 +- sormas-rest/src/main/webapp/WEB-INF/web.xml | 138 +++--- .../symeda/sormas/ui/ControllerProvider.java | 7 + .../symeda/sormas/ui/caze/CaseController.java | 163 ++++--- .../symeda/sormas/ui/caze/CaseDataForm.java | 349 ++++++++++----- .../AbstractConfigurationView.java | 19 +- .../CustomizableFieldEditForm.java | 407 ++++++++++++++++++ .../CustomizableFieldOptionsComponent.java | 195 +++++++++ ...ustomizableFieldTranslationsComponent.java | 228 ++++++++++ .../CustomizableFieldsController.java | 148 +++++++ .../CustomizableFieldsGrid.java | 94 ++++ .../CustomizableFieldsView.java | 185 ++++++++ .../sormas/ui/contact/ContactController.java | 70 +-- .../symeda/sormas/ui/epidata/EpiDataForm.java | 125 +++++- .../sormas/ui/exposure/ExposureForm.java | 69 ++- .../sormas/ui/exposure/ExposuresField.java | 30 +- .../themes/sormas/components/button.scss | 92 ++-- .../sormas/components/customizablefields.scss | 15 + .../webapp/VAADIN/themes/sormas/sormas.scss | 23 +- .../src/main/webapp/WEB-INF/glassfish-web.xml | 13 +- sormas-ui/src/main/webapp/WEB-INF/web.xml | 104 ++--- 32 files changed, 2292 insertions(+), 398 deletions(-) create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldEditForm.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldOptionsComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldTranslationsComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsController.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsGrid.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsView.java create mode 100644 sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/customizablefields.scss diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldGroup.java b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldGroup.java index 17cba99cd2d..5dc6cf6db97 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldGroup.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldGroup.java @@ -33,13 +33,34 @@ public enum CustomizableFieldGroup { // ---- CASE groups -------------------------------------------------------- - CASE_TEST_GROUP_1(CustomizableFieldContext.CASE, "caseTestGroup1"), - CASE_TEST_GROUP_2(CustomizableFieldContext.CASE, "caseTestGroup2"), + CASE_DATA_GENERAL(CustomizableFieldContext.CASE, "caseDataGeneral"), + CASE_DATA_CLASSIFICATION(CustomizableFieldContext.CASE, "caseDataClassification"), + CASE_DATA_INVESTIGATION(CustomizableFieldContext.CASE, "caseDataInvestigation"), + CASE_DATA_IDENTIFIERS(CustomizableFieldContext.CASE, "caseDataIdentifiers"), + CASE_DATA_DISEASE(CustomizableFieldContext.CASE, "caseDataDisease"), + CASE_DATA_REINFECTION(CustomizableFieldContext.CASE, "caseDataReinfection"), + CASE_DATA_OUTCOME(CustomizableFieldContext.CASE, "caseDataOutcome"), + CASE_DATA_SEQUELAE(CustomizableFieldContext.CASE, "caseDataSequelae"), + CASE_DATA_JURISDICTION(CustomizableFieldContext.CASE, "caseDataJurisdiction"), + CASE_DATA_PLACE_OF_STAY(CustomizableFieldContext.CASE, "caseDataPlaceOfStay"), + CASE_DATA_QUARANTINE(CustomizableFieldContext.CASE, "caseDataQuarantine"), + CASE_DATA_REPORT_GEO(CustomizableFieldContext.CASE, "caseDataReportGeo"), + CASE_DATA_HEALTH_CONDITIONS(CustomizableFieldContext.CASE, "caseDataHealthConditions"), + CASE_DATA_DIAGNOSTIC(CustomizableFieldContext.CASE, "caseDataDiagnostic"), + CASE_DATA_MEDICAL_INFORMATION(CustomizableFieldContext.CASE, "caseDataMedicalInformation"), + CASE_DATA_VACCINATION(CustomizableFieldContext.CASE, "caseDataVaccination"), + CASE_DATA_CLINICIAN_NOTIFICATION(CustomizableFieldContext.CASE, "caseDataClinicianNotification"), + CASE_DATA_CONTACT_TRACING(CustomizableFieldContext.CASE, "caseDataContactTracing"), // ---- EPIDATA groups ----------------------------------------------------- EPIDATA_EXPOSURE_INVESTIGATION(CustomizableFieldContext.EPIDATA, "exposureInvestigation"), EPIDATA_ACTIVITY_AS_CASE(CustomizableFieldContext.EPIDATA, "activityAsCase"), - EPIDATA_CONTACT_WITH_SOURCE_CASE(CustomizableFieldContext.EPIDATA, "contactWithSourceCase"); + EPIDATA_CONTACT_WITH_SOURCE_CASE(CustomizableFieldContext.EPIDATA, "contactWithSourceCase"), + + // ---- EXPOSURE groups ---------------------------------------------------- + EXPOSURE_DETAILS(CustomizableFieldContext.EXPOSURE, "exposureDetails"), + EXPOSURES_GENERAL(CustomizableFieldContext.EXPOSURE, "exposuresGeneral"), + LOCATION_GENERAL(CustomizableFieldContext.EXPOSURE, "locationGeneral"); private final CustomizableFieldContext context; /** diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java index 1efeef0a8a1..9b238b2e334 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java @@ -53,6 +53,7 @@ public interface Captions { String actionCancel = "actionCancel"; String actionClear = "actionClear"; String actionClearAll = "actionClearAll"; + String actionClone = "actionClone"; String actionClose = "actionClose"; String actionCompare = "actionCompare"; String actionConfirm = "actionConfirm"; @@ -1214,6 +1215,46 @@ public interface Captions { String customizableEnumValueDiseaseCount = "customizableEnumValueDiseaseCount"; String customizableEnumValueInactiveValues = "customizableEnumValueInactiveValues"; String customizableEnumValueNoProperties = "customizableEnumValueNoProperties"; + String CustomizableFieldContext_CASE = "CustomizableFieldContext.CASE"; + String CustomizableFieldContext_EPIDATA = "CustomizableFieldContext.EPIDATA"; + String CustomizableFieldGroup_CASE_DATA_CLASSIFICATION = "CustomizableFieldGroup.CASE_DATA_CLASSIFICATION"; + String CustomizableFieldGroup_CASE_DATA_CLINICIAN_NOTIFICATION = "CustomizableFieldGroup.CASE_DATA_CLINICIAN_NOTIFICATION"; + String CustomizableFieldGroup_CASE_DATA_CONTACT_TRACING = "CustomizableFieldGroup.CASE_DATA_CONTACT_TRACING"; + String CustomizableFieldGroup_CASE_DATA_DIAGNOSTIC = "CustomizableFieldGroup.CASE_DATA_DIAGNOSTIC"; + String CustomizableFieldGroup_CASE_DATA_DISEASE = "CustomizableFieldGroup.CASE_DATA_DISEASE"; + String CustomizableFieldGroup_CASE_DATA_GENERAL = "CustomizableFieldGroup.CASE_DATA_GENERAL"; + String CustomizableFieldGroup_CASE_DATA_HEALTH_CONDITIONS = "CustomizableFieldGroup.CASE_DATA_HEALTH_CONDITIONS"; + String CustomizableFieldGroup_CASE_DATA_IDENTIFIERS = "CustomizableFieldGroup.CASE_DATA_IDENTIFIERS"; + String CustomizableFieldGroup_CASE_DATA_INVESTIGATION = "CustomizableFieldGroup.CASE_DATA_INVESTIGATION"; + String CustomizableFieldGroup_CASE_DATA_JURISDICTION = "CustomizableFieldGroup.CASE_DATA_JURISDICTION"; + String CustomizableFieldGroup_CASE_DATA_MEDICAL_INFORMATION = "CustomizableFieldGroup.CASE_DATA_MEDICAL_INFORMATION"; + String CustomizableFieldGroup_CASE_DATA_OUTCOME = "CustomizableFieldGroup.CASE_DATA_OUTCOME"; + String CustomizableFieldGroup_CASE_DATA_PLACE_OF_STAY = "CustomizableFieldGroup.CASE_DATA_PLACE_OF_STAY"; + String CustomizableFieldGroup_CASE_DATA_QUARANTINE = "CustomizableFieldGroup.CASE_DATA_QUARANTINE"; + String CustomizableFieldGroup_CASE_DATA_REINFECTION = "CustomizableFieldGroup.CASE_DATA_REINFECTION"; + String CustomizableFieldGroup_CASE_DATA_REPORT_GEO = "CustomizableFieldGroup.CASE_DATA_REPORT_GEO"; + String CustomizableFieldGroup_CASE_DATA_SEQUELAE = "CustomizableFieldGroup.CASE_DATA_SEQUELAE"; + String CustomizableFieldGroup_CASE_DATA_VACCINATION = "CustomizableFieldGroup.CASE_DATA_VACCINATION"; + String CustomizableFieldGroup_EPIDATA_ACTIVITY_AS_CASE = "CustomizableFieldGroup.EPIDATA_ACTIVITY_AS_CASE"; + String CustomizableFieldGroup_EPIDATA_CONTACT_WITH_SOURCE_CASE = "CustomizableFieldGroup.EPIDATA_CONTACT_WITH_SOURCE_CASE"; + String CustomizableFieldGroup_EPIDATA_EXPOSURE_INVESTIGATION = "CustomizableFieldGroup.EPIDATA_EXPOSURE_INVESTIGATION"; + String CustomizableFieldMetadata_active = "CustomizableFieldMetadata.active"; + String CustomizableFieldMetadata_contextClass = "CustomizableFieldMetadata.contextClass"; + String CustomizableFieldMetadata_defaultValue = "CustomizableFieldMetadata.defaultValue"; + String CustomizableFieldMetadata_description = "CustomizableFieldMetadata.description"; + String CustomizableFieldMetadata_fieldType = "CustomizableFieldMetadata.fieldType"; + String CustomizableFieldMetadata_mandatory = "CustomizableFieldMetadata.mandatory"; + String CustomizableFieldMetadata_name = "CustomizableFieldMetadata.name"; + String CustomizableFieldMetadata_readOnly = "CustomizableFieldMetadata.readOnly"; + String CustomizableFieldMetadata_uiGroup = "CustomizableFieldMetadata.uiGroup"; + String CustomizableFieldMetadata_uiLinePosition = "CustomizableFieldMetadata.uiLinePosition"; + String CustomizableFieldMetadata_uiLineWeight = "CustomizableFieldMetadata.uiLineWeight"; + String CustomizableFieldMetadata_visibilityRestrictions = "CustomizableFieldMetadata.visibilityRestrictions"; + String CustomizableFieldMetadata_visibilityRestrictionsDiseases = "CustomizableFieldMetadata.visibilityRestrictionsDiseases"; + String customizableFieldsActiveOnly = "customizableFieldsActiveOnly"; + String customizableFieldsAllActive = "customizableFieldsAllActive"; + String customizableFieldsInactiveOnly = "customizableFieldsInactiveOnly"; + String customizableFieldsNewLabel = "customizableFieldsNewLabel"; String dashboardAggregatedNumber = "dashboardAggregatedNumber"; String dashboardAlive = "dashboardAlive"; String dashboardApplyCustomFilter = "dashboardApplyCustomFilter"; @@ -1841,6 +1882,7 @@ public interface Captions { String Exposure_animalVaccinated = "Exposure.animalVaccinated"; String Exposure_bodyOfWater = "Exposure.bodyOfWater"; String Exposure_childcareFacilityDetails = "Exposure.childcareFacilityDetails"; + String Exposure_conditionOfAnimal = "Exposure.conditionOfAnimal"; String Exposure_connectionNumber = "Exposure.connectionNumber"; String Exposure_contactFactorDetails = "Exposure.contactFactorDetails"; String Exposure_contactFactors = "Exposure.contactFactors"; @@ -3490,6 +3532,8 @@ public interface Captions { String View_configuration_countries_short = "View.configuration.countries.short"; String View_configuration_customizableEnums = "View.configuration.customizableEnums"; String View_configuration_customizableEnums_short = "View.configuration.customizableEnums.short"; + String View_configuration_customizableFields = "View.configuration.customizableFields"; + String View_configuration_customizableFields_short = "View.configuration.customizableFields.short"; String View_configuration_devMode = "View.configuration.devMode"; String View_configuration_devMode_short = "View.configuration.devMode.short"; String View_configuration_diseaseconfiguration = "View.configuration.diseaseconfiguration"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java index 2d7df0d4e69..ebc166eab95 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java @@ -506,6 +506,7 @@ public interface Strings { String headingClinicalMeasurements = "headingClinicalMeasurements"; String headingClinicalPresentation = "headingClinicalPresentation"; String headingClinicalVisitsDeleted = "headingClinicalVisitsDeleted"; + String headingCloneCustomizableField = "headingCloneCustomizableField"; String headingClusterType = "headingClusterType"; String headingComparisonCase = "headingComparisonCase"; String headingCompleteness = "headingCompleteness"; @@ -514,6 +515,7 @@ public interface Strings { String headingConfirmBulkGrantSpecialAccess = "headingConfirmBulkGrantSpecialAccess"; String headingConfirmChoice = "headingConfirmChoice"; String headingConfirmDearchiving = "headingConfirmDearchiving"; + String headingConfirmDeleteCustomizableField = "headingConfirmDeleteCustomizableField"; String headingConfirmDeletion = "headingConfirmDeletion"; String headingConfirmDisabling = "headingConfirmDisabling"; String headingConfirmEnabling = "headingConfirmEnabling"; @@ -546,6 +548,7 @@ public interface Strings { String headingCorrectSample = "headingCorrectSample"; String headingCreateAdditionalTest = "headingCreateAdditionalTest"; String headingCreateCampaignDataForm = "headingCreateCampaignDataForm"; + String headingCreateCustomizableField = "headingCreateCustomizableField"; String headingCreateEntry = "headingCreateEntry"; String headingCreateNewAction = "headingCreateNewAction"; String headingCreateNewAggregateReport = "headingCreateNewAggregateReport"; @@ -579,6 +582,11 @@ public interface Strings { String headingCreateSurveillanceReport = "headingCreateSurveillanceReport"; String headingCurrentHospitalization = "headingCurrentHospitalization"; String headingCustomizableEnumConfigurationInfo = "headingCustomizableEnumConfigurationInfo"; + String headingCustomizableFieldBasics = "headingCustomizableFieldBasics"; + String headingCustomizableFieldBehavior = "headingCustomizableFieldBehavior"; + String headingCustomizableFieldPlacement = "headingCustomizableFieldPlacement"; + String headingCustomizableFieldTranslations = "headingCustomizableFieldTranslations"; + String headingCustomizableFieldVisibility = "headingCustomizableFieldVisibility"; String headingDatabaseExportFailed = "headingDatabaseExportFailed"; String headingDataImport = "headingDataImport"; String headingDearchiveAdverseEvent = "headingDearchiveAdverseEvent"; @@ -620,6 +628,7 @@ public interface Strings { String headingEditContacts = "headingEditContacts"; String headingEditContinent = "headingEditContinent"; String headingEditCountry = "headingEditCountry"; + String headingEditCustomizableField = "headingEditCustomizableField"; String headingEditEventParticipant = "headingEditEventParticipant"; String headingEditEvents = "headingEditEvents"; String headingEditLineListing = "headingEditLineListing"; @@ -1086,6 +1095,8 @@ public interface Strings { String infoNoAefiInvestigations = "infoNoAefiInvestigations"; String infoNoCasesFoundStatistics = "infoNoCasesFoundStatistics"; String infoNoCustomizableEnumTranslations = "infoNoCustomizableEnumTranslations"; + String infoNoCustomizableFieldOptions = "infoNoCustomizableFieldOptions"; + String infoNoCustomizableFieldTranslations = "infoNoCustomizableFieldTranslations"; String infoNoDiseaseConfigurationAgeGroups = "infoNoDiseaseConfigurationAgeGroups"; String infoNoDiseaseSelected = "infoNoDiseaseSelected"; String infoNoEnvironmentSamples = "infoNoEnvironmentSamples"; @@ -1175,6 +1186,7 @@ public interface Strings { String infoVaccinationDoseCount = "infoVaccinationDoseCount"; String infoWeeklyReportsView = "infoWeeklyReportsView"; String labelActualLongSeed = "labelActualLongSeed"; + String labelCustomizableFieldOptions = "labelCustomizableFieldOptions"; String labelNoVaccinationDate = "labelNoVaccinationDate"; String labelNoVaccineName = "labelNoVaccineName"; String labelNumberOfAreas = "labelNumberOfAreas"; @@ -1383,6 +1395,10 @@ public interface Strings { String messageCountVisitsNotSetToLostAccessDeniedReason = "messageCountVisitsNotSetToLostAccessDeniedReason"; String messageCreateCollectionTask = "messageCreateCollectionTask"; String messageCustomizableEnumValueSaved = "messageCustomizableEnumValueSaved"; + String messageCustomizableFieldCloned = "messageCustomizableFieldCloned"; + String messageCustomizableFieldCreated = "messageCustomizableFieldCreated"; + String messageCustomizableFieldDeleted = "messageCustomizableFieldDeleted"; + String messageCustomizableFieldSaved = "messageCustomizableFieldSaved"; String messageDatabaseExportFailed = "messageDatabaseExportFailed"; String messageDeleteImmunizationVaccinations = "messageDeleteImmunizationVaccinations"; String messageDeleteReasonNotFilled = "messageDeleteReasonNotFilled"; @@ -1797,6 +1813,7 @@ public interface Strings { String promptCasesEpiWeekTo = "promptCasesEpiWeekTo"; String promptCaseSex = "promptCaseSex"; String promptCasesSearchField = "promptCasesSearchField"; + String promptConfirmDeleteCustomizableField = "promptConfirmDeleteCustomizableField"; String promptContactDateFrom = "promptContactDateFrom"; String promptContactDateTo = "promptContactDateTo"; String promptContactDateType = "promptContactDateType"; @@ -1807,6 +1824,8 @@ public interface Strings { String promptCustomizableEnumSearchField = "promptCustomizableEnumSearchField"; String promptCustomizableEnumTranslationCaption = "promptCustomizableEnumTranslationCaption"; String promptCustomizableEnumTranslationLanguage = "promptCustomizableEnumTranslationLanguage"; + String promptCustomizableFieldOption = "promptCustomizableFieldOption"; + String promptCustomizableFieldSearchField = "promptCustomizableFieldSearchField"; String promptDateTo = "promptDateTo"; String promptDisease = "promptDisease"; String promptDiseaseConfigurationAgeFrom = "promptDiseaseConfigurationAgeFrom"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/DefaultUserRole.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/DefaultUserRole.java index dfe51b3d861..2951456bcb2 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/user/DefaultUserRole.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/DefaultUserRole.java @@ -484,6 +484,7 @@ public Set getDefaultUserRights() { SORMAS_UI, DEV_MODE, CUSTOMIZABLE_ENUM_MANAGEMENT, + CUSTOMIZABLE_FIELD_MANAGEMENT, SYSTEM_CONFIGURATION)); break; case ADMIN_SUPERVISOR: diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java index d86c6fc3ebd..e2ab2daf826 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java @@ -321,6 +321,7 @@ public enum UserRight { EXTERNAL_EMAIL_SEND(UserRightGroup.EXTERNAL_EMAILS), EXTERNAL_EMAIL_ATTACH_DOCUMENTS(UserRightGroup.EXTERNAL_EMAILS, UserRight._EXTERNAL_EMAIL_SEND), CUSTOMIZABLE_ENUM_MANAGEMENT(UserRightGroup.CONFIGURATION), + CUSTOMIZABLE_FIELD_MANAGEMENT(UserRightGroup.CONFIGURATION), SYSTEM_CONFIGURATION(UserRightGroup.CONFIGURATION), DISEASE_MANAGEMENT(UserRightGroup.CONFIGURATION), EPIPULSE_EXPORT_VIEW(UserRightGroup.EPIPULSE), @@ -572,6 +573,7 @@ public enum UserRight { public static final String _EXTERNAL_EMAIL_SEND = "EXTERNAL_EMAIL_SEND"; public static final String _EXTERNAL_EMAIL_ATTACH_DOCUMENTS = "EXTERNAL_EMAIL_ATTACH_DOCUMENTS"; public static final String _CUSTOMIZABLE_ENUM_MANAGEMENT = "CUSTOMIZABLE_ENUM_MANAGEMENT"; + public static final String _CUSTOMIZABLE_FIELD_MANAGEMENT = "CUSTOMIZABLE_FIELD_MANAGEMENT"; public static final String _SYSTEM_CONFIGURATION = "SYSTEM_CONFIGURATION"; public static final String _DISEASE_MANAGEMENT = "DISEASE_MANAGEMENT"; diff --git a/sormas-api/src/main/resources/captions.properties b/sormas-api/src/main/resources/captions.properties index f9ed2b2e7a3..0477e6e5986 100644 --- a/sormas-api/src/main/resources/captions.properties +++ b/sormas-api/src/main/resources/captions.properties @@ -144,6 +144,7 @@ actionImportAllSubcontinents=Import default subcontinents actionLogout=Logout actionNewEntry=New entry actionOkay=Okay +actionClone=Clone actionConfirmFilters=Confirm filters actionResetFilters=Reset filters actionApplyFilters=Apply filters @@ -3334,6 +3335,8 @@ View.configuration.emailTemplates=Email Templates View.configuration.emailTemplates.short=Email Templates View.configuration.customizableEnums=Customizable Enum Configuration View.configuration.customizableEnums.short=Customizable Enums +View.configuration.customizableFields=Customizable Fields +View.configuration.customizableFields.short=Customizable Fields View.configuration.diseaseconfiguration=Disease Configuration View.configuration.diseaseconfiguration.short=Disease Configuration View.contacts=Contact Directory @@ -3709,6 +3712,52 @@ SystemConfigurationValue.pattern = Pattern SystemConfigurationValue.value = Value SystemConfigurationValue.General = General +# CustomizableFieldMetadata +CustomizableFieldMetadata.name = Internal Name +CustomizableFieldMetadata.description = Description +CustomizableFieldMetadata.fieldType = Field Type +CustomizableFieldMetadata.contextClass = Context / Entity +CustomizableFieldMetadata.uiGroup = UI Group +CustomizableFieldMetadata.uiLinePosition = Line Position +CustomizableFieldMetadata.uiLineWeight = Line Weight +CustomizableFieldMetadata.active = Active +CustomizableFieldMetadata.mandatory = Mandatory +CustomizableFieldMetadata.readOnly = Read Only +CustomizableFieldMetadata.defaultValue = Default Value +CustomizableFieldMetadata.visibilityRestrictions = Visibility Restrictions +CustomizableFieldMetadata.visibilityRestrictionsDiseases = Visible for Diseases +customizableFieldsAllActive = All +customizableFieldsActiveOnly = Active only +customizableFieldsInactiveOnly = Inactive only +customizableFieldsNewLabel = New name for clone + +# CustomizableFieldGroup +CustomizableFieldGroup.CASE_DATA_GENERAL = Case Data General +CustomizableFieldGroup.CASE_DATA_CLASSIFICATION = Case Data Classification +CustomizableFieldGroup.CASE_DATA_INVESTIGATION = Case Data Investigation +CustomizableFieldGroup.CASE_DATA_IDENTIFIERS = Case Data Identifiers +CustomizableFieldGroup.CASE_DATA_DISEASE = Case Data Disease +CustomizableFieldGroup.CASE_DATA_REINFECTION = Case Data Reinfection +CustomizableFieldGroup.CASE_DATA_OUTCOME = Case Data Outcome +CustomizableFieldGroup.CASE_DATA_SEQUELAE = Case Data Sequelae +CustomizableFieldGroup.CASE_DATA_JURISDICTION = Case Data Jurisdiction +CustomizableFieldGroup.CASE_DATA_PLACE_OF_STAY = Case Data Place Of Stay +CustomizableFieldGroup.CASE_DATA_QUARANTINE = Case Data Quarantine +CustomizableFieldGroup.CASE_DATA_REPORT_GEO = Case Data Report Geo +CustomizableFieldGroup.CASE_DATA_HEALTH_CONDITIONS = Case Data Health Conditions +CustomizableFieldGroup.CASE_DATA_DIAGNOSTIC = Case Data Diagnostic +CustomizableFieldGroup.CASE_DATA_MEDICAL_INFORMATION = Case Data Medical Information +CustomizableFieldGroup.CASE_DATA_VACCINATION = Case Data Vaccination +CustomizableFieldGroup.CASE_DATA_CLINICIAN_NOTIFICATION = Case Data Clinician Notification +CustomizableFieldGroup.CASE_DATA_CONTACT_TRACING = Case Data Contact Tracing +CustomizableFieldGroup.EPIDATA_EXPOSURE_INVESTIGATION = Exposure Investigation +CustomizableFieldGroup.EPIDATA_ACTIVITY_AS_CASE = Activity as Case +CustomizableFieldGroup.EPIDATA_CONTACT_WITH_SOURCE_CASE = Contact with Source Case + +# CustomizableFieldContext +CustomizableFieldContext.CASE = Case +CustomizableFieldContext.EPIDATA = Epidemiological Data + # Notifier Notifier.notification = Notification Notification.dateOfNotification = Date of notification diff --git a/sormas-api/src/main/resources/strings.properties b/sormas-api/src/main/resources/strings.properties index 811a9abee24..3d394e17124 100644 --- a/sormas-api/src/main/resources/strings.properties +++ b/sormas-api/src/main/resources/strings.properties @@ -511,6 +511,15 @@ headingContactsNotLinked = None of the contacts were linked headingCasesNotLinked = None of the cases were linked headingContactsNotRestored = = None of the contacts were restored headingCreateAdditionalTest = Create new additional test results +headingCreateCustomizableField = Create Customizable Field +headingEditCustomizableField = Edit Customizable Field +headingCloneCustomizableField = Clone Customizable Field +headingConfirmDeleteCustomizableField = Confirm Delete +headingCustomizableFieldBasics = Basics +headingCustomizableFieldPlacement = Placement +headingCustomizableFieldBehavior = Behavior +headingCustomizableFieldVisibility = Visibility +headingCustomizableFieldTranslations = Translations headingCreateEntry = Create entry headingCreateNewAction = Create new action headingCreateNewAggregateReport = Create a new aggregated report @@ -1163,6 +1172,8 @@ infoNoEnvironmentSamples = No samples have been created for this environment infoRecoveryNaturalImmunity = Natural immunity from recovering from the disease infoRestrictDiseasesDescription=Mark all diseases that the user is supposed to have access to infoNoCustomizableEnumTranslations = Click on the + button below to add translations to this customizable enum value. +infoNoCustomizableFieldTranslations = Click on the + button below to add translations for this field. +infoNoCustomizableFieldOptions = Click on the + button below to add options for this field. infoCustomizableEnumConfigurationInfo = Customizable enums are value sets that can be customized in order to react to the individual needs of your country or a specific epidemiological situation. The table on this screen contains all customizable enum values in the database. Each value is associated with a data type, e.g. disease variants or occupation types. Some of these data types have default values that are automatically added to the database when SORMAS is set up or new data types are added to the system.

You can add new enum values or edit existing ones, add translations for languages supported by SORMAS, select the diseases that the value should be visible for (by default, customizable enum values are visible for all diseases), and configure additional properties.

Properties are used to further control the behaviour of customizable enum values. E.g. the "has details" property that is supported by most enum values toggles whether selecting this enum value would bring up an additional text field that users can add more information to. infoNoImmunizationAdverseEvents = No adverse events have been created for this immunization infoAefiSelectPrimarySuspectVaccine = The list below contains all vaccinations of the immunization. Please select the suspect vaccination related to this adverse event. @@ -1539,6 +1550,11 @@ messageContactCaseRemoved = The source case has been removed from this contact messageContactCaseChanged = The source case of the contact has been changed messageSampleOpened = Opened sample found for search string messageSystemConfigurationValueSaved = System configuration value saved +messageCustomizableFieldSaved = Customizable field saved +messageCustomizableFieldCreated = Customizable field created +messageCustomizableFieldCloned = Customizable field cloned +messageCustomizableFieldDeleted = Customizable field deleted +promptCustomizableFieldSearchField = Search by name, description or group messageSystemFollowUpCanceled = [System] Follow-up automatically canceled because contact was converted to a case messageSystemFollowUpCanceledByDropping = [System] Follow-up automatically canceled because contact was dropped messageSetContactRegionAndDistrict = Please choose a responsible region and responsible district and save the contact before removing the source case. @@ -1707,6 +1723,7 @@ labelNumberOfTemplates = No. of Templates labelNoVaccinationDate = No vaccination date labelNoVaccineName = No vaccine name labelNumberOfDiseaseConfigurations = No. of diseases +labelCustomizableFieldOptions = Field options # Numbers numberEight = eight @@ -1840,6 +1857,7 @@ promptDiseaseConfigurationStartAgeType = Start age unit promptDiseaseConfigurationEndAge = End age promptDiseaseConfigurationEndAgeType = End age unit promptCaseSex=Case sex +promptConfirmDeleteCustomizableField = Are you sure you want to delete this customizable field? All field values linked to it will also be deleted. #DiseaseNetworkDiagram DiseaseNetworkDiagram.Classification.HEALTHY = Healthy @@ -1886,6 +1904,7 @@ promptEnvironmentSampleLonTo= ... to promptCustomizableEnumTranslationLanguage = Language promptCustomizableEnumTranslationCaption = Translated caption promptCustomizableEnumSearchField = Search by value or caption... +promptCustomizableFieldOption = Option value promptSurveyFreeTextSearch = Name promptSurveyTokenFreeTextSearch = Token diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/contact/ContactFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/contact/ContactFacadeEjb.java index b16b793b29b..6cf38ade92a 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/contact/ContactFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/contact/ContactFacadeEjb.java @@ -659,6 +659,7 @@ private void deleteContact(Contact contact, DeletionDetails deletionDetails) { } service.delete(contact, deletionDetails); + epiDataFacade.softDeleteCustomizableFieldValues(contact.getEpiData(), deletionDetails); if (contact.getCaze() != null) { caseFacade.onCaseChanged(caseFacade.toDto(contact.getCaze()), contact.getCaze()); } 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 8c031994b65..2305c2de11e 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 @@ -29,6 +29,8 @@ import javax.ejb.Stateless; import de.symeda.sormas.api.activityascase.ActivityAsCaseDto; +import de.symeda.sormas.api.common.DeletionDetails; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; import de.symeda.sormas.api.epidata.EpiDataDto; import de.symeda.sormas.api.epidata.EpiDataFacade; import de.symeda.sormas.api.exposure.ExposureDto; @@ -38,6 +40,7 @@ import de.symeda.sormas.backend.activityascase.ActivityAsCaseService; import de.symeda.sormas.backend.contact.ContactFacadeEjb; import de.symeda.sormas.backend.contact.ContactService; +import de.symeda.sormas.backend.customizablefield.CustomizableFieldValueService; import de.symeda.sormas.backend.exposure.Exposure; import de.symeda.sormas.backend.exposure.ExposureService; import de.symeda.sormas.backend.infrastructure.country.CountryFacadeEjb; @@ -63,6 +66,18 @@ public class EpiDataFacadeEjb implements EpiDataFacade { private UserService userService; @EJB private CountryService countryService; + @EJB + private CustomizableFieldValueService customizableFieldValueService; + + public void softDeleteCustomizableFieldValues(EpiData epiData, DeletionDetails deletionDetails) { + if (epiData == null || epiData.getUuid() == null) { + return; + } + customizableFieldValueService.softDeleteValuesForEntity(epiData.getUuid(), CustomizableFieldContext.EPIDATA, deletionDetails); + for (Exposure exposure : epiData.getExposures()) { + customizableFieldValueService.softDeleteValuesForEntity(exposure.getUuid(), CustomizableFieldContext.EXPOSURE, deletionDetails); + } + } public EpiData fillOrBuildEntity(EpiDataDto source, EpiData target, boolean checkChangeDate) { if (source == null) { diff --git a/sormas-backend/src/main/resources/META-INF/glassfish-ejb-jar.xml b/sormas-backend/src/main/resources/META-INF/glassfish-ejb-jar.xml index 063e3e46c57..1590d53287f 100644 --- a/sormas-backend/src/main/resources/META-INF/glassfish-ejb-jar.xml +++ b/sormas-backend/src/main/resources/META-INF/glassfish-ejb-jar.xml @@ -1193,6 +1193,11 @@ CUSTOMIZABLE_ENUM_MANAGEMENT + + CUSTOMIZABLE_FIELD_MANAGEMENT + CUSTOMIZABLE_FIELD_MANAGEMENT + + SYSTEM_CONFIGURATION SYSTEM_CONFIGURATION @@ -1218,12 +1223,12 @@ EPIPULSE_EXPORT_DELETE - - - SYSTEM - SYSTEM - + + + SYSTEM + SYSTEM + - - true - + + true + \ No newline at end of file diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 0eb47671f85..5d999a2eded 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15722,4 +15722,9 @@ CREATE TRIGGER delete_history_trigger ALTER TABLE customizablefieldvalue_history OWNER TO sormas_user; INSERT INTO schema_version (version_number, comment) VALUES (619, '#13828 - Add history tables for customizable fields'); +-- #13828 - Customizable Fields - Admin user rights +INSERT INTO userroles_userrights (userrole_id, userright) SELECT id, 'CUSTOMIZABLE_FIELD_MANAGEMENT' FROM public.userroles WHERE userroles.linkeddefaultuserrole in ('ADMIN'); + +INSERT INTO schema_version (version_number, comment) VALUES (620, '#13828 - Add system configuration rights for admin user'); + -- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** \ No newline at end of file diff --git a/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml b/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml index 1db1a1dd272..6097973ad7e 100644 --- a/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml +++ b/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml @@ -1,9 +1,7 @@ - + - + CASE_CREATE @@ -1222,4 +1220,9 @@ EPIPULSE_EXPORT_DELETE - + + CUSTOMIZABLE_FIELD_MANAGEMENT + CUSTOMIZABLE_FIELD_MANAGEMENT + + + \ No newline at end of file diff --git a/sormas-rest/src/main/webapp/WEB-INF/web.xml b/sormas-rest/src/main/webapp/WEB-INF/web.xml index 3bbfb22034a..36e8b47bf06 100644 --- a/sormas-rest/src/main/webapp/WEB-INF/web.xml +++ b/sormas-rest/src/main/webapp/WEB-INF/web.xml @@ -1,9 +1,9 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + version="3.1"> SORMAS Web Interface @@ -20,9 +20,9 @@ CASE_VIEW - - CASE_VIEW_ARCHIVED - + + CASE_VIEW_ARCHIVED + CASE_EDIT @@ -84,9 +84,9 @@ IMMUNIZATION_VIEW - - IMMUNIZATION_VIEW_ARCHIVED - + + IMMUNIZATION_VIEW_ARCHIVED + IMMUNIZATION_CREATE @@ -220,11 +220,11 @@ CONTACT_VIEW - - CONTACT_VIEW_ARCHIVED - + + CONTACT_VIEW_ARCHIVED + - + CONTACT_ARCHIVE @@ -300,9 +300,9 @@ TASK_ARCHIVE - - TASK_VIEW_ARCHIVED - + + TASK_VIEW_ARCHIVED + ACTION_CREATE @@ -324,11 +324,11 @@ EVENT_VIEW - - EVENT_VIEW_ARCHIVED - + + EVENT_VIEW_ARCHIVED + - + EVENT_EDIT @@ -372,9 +372,9 @@ EVENTPARTICIPANT_VIEW - - EVENTPARTICIPANT_VIEW_ARCHIVED - + + EVENTPARTICIPANT_VIEW_ARCHIVED + EVENTGROUP_CREATE @@ -396,9 +396,9 @@ EVENTGROUP_DELETE - - EVENTGROUP_VIEW_ARCHIVED - + + EVENTGROUP_VIEW_ARCHIVED + WEEKLYREPORT_CREATE @@ -428,11 +428,11 @@ USER_ROLE_DELETE - - USER_ROLE_VIEW - + + USER_ROLE_VIEW + - + SEND_MANUAL_EXTERNAL_MESSAGES @@ -472,9 +472,9 @@ INFRASTRUCTURE_VIEW - - INFRASTRUCTURE_VIEW_ARCHIVED - + + INFRASTRUCTURE_VIEW_ARCHIVED + INFRASTRUCTURE_EXPORT @@ -620,11 +620,11 @@ CAMPAIGN_VIEW - - CAMPAIGN_VIEW_ARCHIVED - + + CAMPAIGN_VIEW_ARCHIVED + - + CAMPAIGN_EDIT @@ -640,9 +640,9 @@ CAMPAIGN_FORM_DATA_VIEW - - CAMPAIGN_FORM_DATA_VIEW_ARCHIVED - + + CAMPAIGN_FORM_DATA_VIEW_ARCHIVED + CAMPAIGN_FORM_DATA_EDIT @@ -720,11 +720,11 @@ TRAVEL_ENTRY_VIEW - - TRAVEL_ENTRY_VIEW_ARCHIVED - + + TRAVEL_ENTRY_VIEW_ARCHIVED + - + TRAVEL_ENTRY_CREATE @@ -752,9 +752,9 @@ ENVIRONMENT_ARCHIVE - - ENVIRONMENT_VIEW_ARCHIVED - + + ENVIRONMENT_VIEW_ARCHIVED + ENVIRONMENT_DELETE @@ -832,13 +832,13 @@ SELF_REPORT_DELETE - - SELF_REPORT_EXPORT - + + SELF_REPORT_EXPORT + - - SELF_REPORT_IMPORT - + + SELF_REPORT_IMPORT + SELF_REPORT_ARCHIVE @@ -968,20 +968,24 @@ SYSTEM_CONFIGURATION - - EPIPULSE_EXPORT_VIEW - + + EPIPULSE_EXPORT_VIEW + - - EPIPULSE_EXPORT_CREATE - + + EPIPULSE_EXPORT_CREATE + - - EPIPULSE_EXPORT_DOWNLOAD - + + EPIPULSE_EXPORT_DOWNLOAD + - - EPIPULSE_EXPORT_DELETE - + + EPIPULSE_EXPORT_DELETE + + + + CUSTOMIZABLE_FIELD_MANAGEMENT + - + \ No newline at end of file diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/ControllerProvider.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/ControllerProvider.java index aeb174772a2..62be04b6384 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/ControllerProvider.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/ControllerProvider.java @@ -26,6 +26,7 @@ import de.symeda.sormas.ui.caze.surveillancereport.SurveillanceReportController; import de.symeda.sormas.ui.clinicalcourse.ClinicalCourseController; import de.symeda.sormas.ui.configuration.customizableenum.CustomizableEnumController; +import de.symeda.sormas.ui.configuration.customizablefield.CustomizableFieldsController; import de.symeda.sormas.ui.configuration.disease.DiseaseConfigurationController; import de.symeda.sormas.ui.configuration.infrastructure.InfrastructureController; import de.symeda.sormas.ui.configuration.outbreak.OutbreakController; @@ -108,6 +109,7 @@ public class ControllerProvider extends BaseControllerProvider { private final EnvironmentSampleController environmentSampleController; private final ExternalEmailController externalEmailController; private final CustomizableEnumController customizableEnumController; + private final CustomizableFieldsController customizableFieldsController; private final DiseaseConfigurationController diseaseConfigurationController; private final SpecialCaseAccessController specialCaseAccessController; private final SelfReportController selfReportController; @@ -160,6 +162,7 @@ public ControllerProvider() { environmentSampleController = new EnvironmentSampleController(); externalEmailController = new ExternalEmailController(); customizableEnumController = new CustomizableEnumController(); + customizableFieldsController = new CustomizableFieldsController(); diseaseConfigurationController = new DiseaseConfigurationController(); specialCaseAccessController = new SpecialCaseAccessController(); selfReportController = new SelfReportController(); @@ -331,6 +334,10 @@ public static CustomizableEnumController getCustomizableEnumController() { return get().customizableEnumController; } + public static CustomizableFieldsController getCustomizableFieldsController() { + return get().customizableFieldsController; + } + public static DiseaseConfigurationController getDiseaseConfirgurationController() { return get().diseaseConfigurationController; } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java index 8a442f49e00..78d1a39b0dc 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseController.java @@ -21,7 +21,9 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -77,6 +79,9 @@ import de.symeda.sormas.api.contact.ContactSimilarityCriteria; import de.symeda.sormas.api.contact.ContactStatus; import de.symeda.sormas.api.contact.SimilarContactDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.deletionconfiguration.DeletionInfoDto; import de.symeda.sormas.api.docgeneneration.DocumentWorkflow; import de.symeda.sormas.api.docgeneneration.RootEntityType; @@ -112,6 +117,7 @@ import de.symeda.sormas.api.user.UserReferenceDto; import de.symeda.sormas.api.user.UserRight; import de.symeda.sormas.api.utils.DataHelper; +import de.symeda.sormas.api.utils.DateFormatHelper; import de.symeda.sormas.api.utils.DateHelper; import de.symeda.sormas.api.utils.ValidationRuntimeException; import de.symeda.sormas.api.utils.YesNoUnknown; @@ -145,7 +151,6 @@ import de.symeda.sormas.ui.utils.ButtonHelper; import de.symeda.sormas.ui.utils.CommitDiscardWrapperComponent; import de.symeda.sormas.ui.utils.CssStyles; -import de.symeda.sormas.ui.utils.DateFormatHelper; import de.symeda.sormas.ui.utils.DeleteRestoreHandlers; import de.symeda.sormas.ui.utils.DetailSubComponentWrapper; import de.symeda.sormas.ui.utils.FieldAccessHelper; @@ -160,7 +165,6 @@ public class CaseController { private CommitDiscardWrapperComponent caseCreateComponent; - private boolean caseSaveTriggered; public CaseController() { @@ -222,9 +226,9 @@ public void createFromEventParticipant(EventParticipantDto eventParticipant) { selectOrCreateCase(dto, FacadeProvider.getPersonFacade().getByUuid(eventParticipant.getPerson().getUuid()), uuid -> { if (uuid == null) { - CommitDiscardWrapperComponent caseCreateComponent = + CommitDiscardWrapperComponent caseCreateComp = getCaseCreateComponent(null, eventParticipant, null, null, null, false); - caseCreateComponent.addCommitListener(() -> { + caseCreateComp.addCommitListener(() -> { EventParticipantDto updatedEventparticipant = FacadeProvider.getEventParticipantFacade().getByUuid(eventParticipant.getUuid()); if (updatedEventparticipant.getResultingCase() != null) { String caseUuid = updatedEventparticipant.getResultingCase().getUuid(); @@ -232,7 +236,7 @@ public void createFromEventParticipant(EventParticipantDto eventParticipant) { convertSamePersonContactsAndEventParticipants(caze, null); } }); - VaadinUiUtil.showModalPopupWindow(caseCreateComponent, I18nProperties.getString(Strings.headingCreateNewCase)); + VaadinUiUtil.showModalPopupWindow(caseCreateComp, I18nProperties.getString(Strings.headingCreateNewCase)); } else { CaseDataDto selectedCase = FacadeProvider.getCaseFacade().getCaseDataByUuid(uuid); EventParticipantDto updatedEventParticipant = FacadeProvider.getEventParticipantFacade().getByUuid(eventParticipant.getUuid()); @@ -258,9 +262,8 @@ public void createFromEventParticipantDifferentDisease(EventParticipantDto event return; } - CommitDiscardWrapperComponent caseCreateComponent = - getCaseCreateComponent(null, eventParticipant, null, null, disease, false); - VaadinUiUtil.showModalPopupWindow(caseCreateComponent, I18nProperties.getString(Strings.headingCreateNewCase)); + CommitDiscardWrapperComponent caseCreateComp = getCaseCreateComponent(null, eventParticipant, null, null, disease, false); + VaadinUiUtil.showModalPopupWindow(caseCreateComp, I18nProperties.getString(Strings.headingCreateNewCase)); } public void createFromContact(ContactDto contact) { @@ -276,8 +279,8 @@ public void createFromContact(ContactDto contact) { selectOrCreateCase(dto, FacadeProvider.getPersonFacade().getByUuid(selectedPerson.getUuid()), uuid -> { if (uuid == null) { - CommitDiscardWrapperComponent caseCreateComponent = getCaseCreateComponent(contact, null, null, null, null, false); - caseCreateComponent.addCommitListener(() -> { + CommitDiscardWrapperComponent caseCreateComp = getCaseCreateComponent(contact, null, null, null, null, false); + caseCreateComp.addCommitListener(() -> { ContactDto contactDto = FacadeProvider.getContactFacade().getByUuid(contact.getUuid()); if (contactDto.getResultingCase() != null) { String caseUuid = contactDto.getResultingCase().getUuid(); @@ -285,8 +288,8 @@ public void createFromContact(ContactDto contact) { convertSamePersonContactsAndEventParticipants(caze, null); } }); - caseCreateComponent.addDiscardListener(() -> SormasUI.refreshView()); - VaadinUiUtil.showModalPopupWindow(caseCreateComponent, I18nProperties.getString(Strings.headingCreateNewCase)); + caseCreateComp.addDiscardListener(SormasUI::refreshView); + VaadinUiUtil.showModalPopupWindow(caseCreateComp, I18nProperties.getString(Strings.headingCreateNewCase)); } else { CaseDataDto selectedCase = FacadeProvider.getCaseFacade().getCaseDataByUuid(uuid); selectedCase.getEpiData().setContactWithSourceCaseKnown(YesNoUnknown.YES); @@ -308,8 +311,8 @@ public void createFromContact(ContactDto contact) { } public void createFromUnrelatedContact(ContactDto contact, Disease disease) { - CommitDiscardWrapperComponent caseCreateComponent = getCaseCreateComponent(contact, null, null, null, disease, false); - VaadinUiUtil.showModalPopupWindow(caseCreateComponent, I18nProperties.getString(Strings.headingCreateNewCase)); + CommitDiscardWrapperComponent caseCreateComp = getCaseCreateComponent(contact, null, null, null, disease, false); + VaadinUiUtil.showModalPopupWindow(caseCreateComp, I18nProperties.getString(Strings.headingCreateNewCase)); } public void createFromTravelEntry(TravelEntryDto travelEntryDto) { @@ -320,9 +323,8 @@ public void createFromTravelEntry(TravelEntryDto travelEntryDto) { selectOrCreateCase(dto, FacadeProvider.getPersonFacade().getByUuid(selectedPerson.getUuid()), uuid -> { if (uuid == null) { - CommitDiscardWrapperComponent caseCreateComponent = - getCaseCreateComponent(null, null, travelEntryDto, null, null, false); - VaadinUiUtil.showModalPopupWindow(caseCreateComponent, I18nProperties.getString(Strings.headingCreateNewCase)); + CommitDiscardWrapperComponent caseCreateComp = getCaseCreateComponent(null, null, travelEntryDto, null, null, false); + VaadinUiUtil.showModalPopupWindow(caseCreateComp, I18nProperties.getString(Strings.headingCreateNewCase)); } else { TravelEntryDto updatedTravelEntry = FacadeProvider.getTravelEntryFacade().getByUuid(travelEntryDto.getUuid()); updatedTravelEntry.setResultingCase(FacadeProvider.getCaseFacade().getCaseDataByUuid(uuid).toReference()); @@ -338,9 +340,9 @@ public void createFromPersonReference(PersonReferenceDto personReference) { dto.setReportingUser(UiUtil.getUserReference()); - CommitDiscardWrapperComponent caseCreateComponent = getCaseCreateComponent(null, null, null, person, null, false); - caseCreateComponent.getWrappedComponent().setSearchedPerson(person); - VaadinUiUtil.showModalPopupWindow(caseCreateComponent, I18nProperties.getString(Strings.headingCreateNewCase)); + CommitDiscardWrapperComponent caseCreateComp = getCaseCreateComponent(null, null, null, person, null, false); + caseCreateComp.getWrappedComponent().setSearchedPerson(person); + VaadinUiUtil.showModalPopupWindow(caseCreateComp, I18nProperties.getString(Strings.headingCreateNewCase)); } public void convertSamePersonContactsAndEventParticipants(CaseDataDto caze, Runnable callback) { @@ -369,7 +371,7 @@ public void convertSamePersonContactsAndEventParticipants(CaseDataDto caze, Runn matchingEventParticipants = Collections.emptyList(); } - if (matchingContacts.size() > 0 || matchingEventParticipants.size() > 0) { + if (!matchingContacts.isEmpty() || !matchingEventParticipants.isEmpty()) { String infoText = matchingEventParticipants.isEmpty() ? String.format(I18nProperties.getString(Strings.infoConvertToCaseContacts), matchingContacts.size(), caze.getDisease()) : (matchingContacts.isEmpty() @@ -744,12 +746,12 @@ public CommitDiscardWrapperComponent getCaseCreateComponent( } final CommitDiscardWrapperComponent editView = - new CommitDiscardWrapperComponent(createForm, UiUtil.permitted(UserRight.CASE_CREATE), createForm.getFieldGroup()); + new CommitDiscardWrapperComponent<>(createForm, UiUtil.permitted(UserRight.CASE_CREATE), createForm.getFieldGroup()); if (createForm.getHomeAddressForm() != null) { editView.addFieldGroups(createForm.getHomeAddressForm().getFieldGroup()); } - caseSaveTriggered = false; + final AtomicBoolean caseSaveTriggered = new AtomicBoolean(false); editView.addCommitListener(() -> { if (!createForm.getFieldGroup().isModified()) { final CaseDataDto dto = createForm.getValue(); @@ -869,6 +871,7 @@ public CommitDiscardWrapperComponent getCaseCreateComponent( final PersonDto duplicatePerson = PersonDto.build(); if (createForm.getWarningSimilarPersons() != null) { + @SuppressWarnings("unchecked") CommitDiscardWrapperComponent warningPopUpDuplicatePerson = (CommitDiscardWrapperComponent) editView.getWrappedComponent() .getWarningSimilarPersons() @@ -877,12 +880,12 @@ public CommitDiscardWrapperComponent getCaseCreateComponent( warningPopUpDuplicatePerson.getDiscardButton().setVisible(true); warningPopUpDuplicatePerson.getCommitButton().setCaption(I18nProperties.getCaption(Captions.actionContinue)); warningPopUpDuplicatePerson.addDoneListener(() -> { - if (!caseSaveTriggered) { + if (!caseSaveTriggered.get()) { VaadinUiUtil.showModalPopupWindow(caseCreateComponent, I18nProperties.getString(Strings.headingCreateNewCase)); } }); warningPopUpDuplicatePerson.addCommitListener(() -> { - caseSaveTriggered = true; + caseSaveTriggered.set(true); transferDataToPerson(createForm, duplicatePerson); ControllerProvider.getPersonController() .selectOrCreatePerson( @@ -946,7 +949,7 @@ public void selectOrCreateCase(CaseDataDto caseDto, PersonDto person, Consumer similarCases = FacadeProvider.getCaseFacade().getSimilarCases(criteria); - if (similarCases.size() > 0) { + if (!similarCases.isEmpty()) { CasePickOrCreateField pickOrCreateField = new CasePickOrCreateField(caseDto, person, similarCases); pickOrCreateField.setWidth(1280, Unit.PIXELS); @@ -962,9 +965,7 @@ public void selectOrCreateCase(CaseDataDto caseDto, PersonDto person, Consumer { - component.getCommitButton().setEnabled(commitAllowed); - }); + pickOrCreateField.setSelectionChangeCallback(commitAllowed -> component.getCommitButton().setEnabled(commitAllowed)); VaadinUiUtil.showModalPopupWindow(component, I18nProperties.getString(Strings.headingPickOrCreateCase)); } else { @@ -978,6 +979,10 @@ public CommitDiscardWrapperComponent getCaseDataEditComponent(fina DeletionInfoDto automaticDeletionInfoDto = FacadeProvider.getCaseFacade().getAutomaticDeletionInfo(caseUuid); DeletionInfoDto manuallyDeletionInfoDto = FacadeProvider.getCaseFacade().getManuallyDeletionInfo(caseUuid); + List caseMetadata = + FacadeProvider.getCustomizableFieldMetadataFacade().getActiveFieldsForContext(CustomizableFieldContext.CASE); + Map caseFieldValues = + FacadeProvider.getCustomizableFieldValueFacade().getValuesForEntity(caze.getUuid(), CustomizableFieldContext.CASE); CaseDataForm caseEditForm = new CaseDataForm( caseUuid, FacadeProvider.getPersonFacade().getByUuid(caze.getPerson().getUuid()), @@ -985,11 +990,15 @@ public CommitDiscardWrapperComponent getCaseDataEditComponent(fina caze.getSymptoms(), viewMode, caze.isPseudonymized(), - caze.isInJurisdiction()); + caze.isInJurisdiction(), + caseMetadata, + caseFieldValues); caseEditForm.setValue(caze); - CommitDiscardWrapperComponent editView = - new CommitDiscardWrapperComponent(caseEditForm, true, caseEditForm.getFieldGroup()); + CommitDiscardWrapperComponent editView = new CommitDiscardWrapperComponent<>(caseEditForm, true, caseEditForm.getFieldGroup()); + + caseEditForm.addCustomizableFieldValueChangeListener(e -> editView.setDirty(true)); + editView.addDiscardListener(caseEditForm::resetCustomizableFieldValues); editView.getButtonsPanel() .addComponentAsFirst(new DeletionLabel(automaticDeletionInfoDto, manuallyDeletionInfoDto, caze.isDeleted(), CaseDataDto.I18N_PREFIX)); @@ -1004,10 +1013,15 @@ public CommitDiscardWrapperComponent getCaseDataEditComponent(fina editView.addCommitListener(() -> { CaseDataDto oldCase = findCase(caseUuid); CaseDataDto cazeDto = caseEditForm.getValue(); - saveCaseWithFacilityChangedPrompt(cazeDto, oldCase); + Map updatedCaseFieldValues = caseEditForm.collectCurrentFieldValues(); + saveCaseWithFacilityChangedPrompt( + cazeDto, + oldCase, + () -> FacadeProvider.getCustomizableFieldValueFacade() + .saveEntityCustomFields(cazeDto.getUuid(), CustomizableFieldContext.CASE, updatedCaseFieldValues)); }); - editView.addDiscardListener(() -> caseEditForm.onDiscard()); + editView.addDiscardListener(caseEditForm::onDiscard); if (UiUtil.permitted(UserRight.CASE_REFER_FROM_POE) && caze.checkIsUnreferredPortHealthCase()) { @@ -1163,7 +1177,7 @@ public Consumer> bulkOperationCallback(Abstract private void appendSpecialCommands(CaseDataDto caze, CommitDiscardWrapperComponent editView) { if (UiUtil.permitted(UserRight.CASE_DELETE)) { - editView.addDeleteWithReasonOrRestoreListener((deleteDetails) -> { + editView.addDeleteWithReasonOrRestoreListener(deleteDetails -> { if (UiUtil.permitted(UserRight.CONTACT_VIEW)) { long contactCount = FacadeProvider.getContactFacade().getContactCount(caze.toReference()); if (contactCount > 0) { @@ -1188,7 +1202,7 @@ private void appendSpecialCommands(CaseDataDto caze, CommitDiscardWrapperCompone } else { deleteCase(caze, false, deleteDetails); } - }, getDeleteConfirmationDetails(Collections.singletonList(caze.getUuid())), (deleteDetails) -> { + }, getDeleteConfirmationDetails(Collections.singletonList(caze.getUuid())), deleteDetails -> { FacadeProvider.getCaseFacade().restore(caze.getUuid()); UI.getCurrent().getNavigator().navigateTo(CasesView.VIEW_NAME); }, I18nProperties.getString(Strings.entityCase), caze.getUuid(), FacadeProvider.getCaseFacade()); @@ -1246,7 +1260,7 @@ public CommitDiscardWrapperComponent getHospitalizationComp hospitalizationForm.setValue(caze.getHospitalization()); final CommitDiscardWrapperComponent editView = - new CommitDiscardWrapperComponent(hospitalizationForm, hospitalizationForm.getFieldGroup()); + new CommitDiscardWrapperComponent<>(hospitalizationForm, hospitalizationForm.getFieldGroup()); final JurisdictionValues jurisdictionValues = new JurisdictionValues(); @@ -1280,7 +1294,7 @@ public CommitDiscardWrapperComponent getHospitalizationComp VaadinUiUtil .showModalPopupWindow(wrapperComponent, I18nProperties.getString(Strings.headingPlaceOfStayInHospital), preCommitSuccessful -> { - if (preCommitSuccessful) { + if (Boolean.TRUE.equals(preCommitSuccessful)) { successCallback.run(); } }); @@ -1315,7 +1329,7 @@ public CommitDiscardWrapperComponent getMaternalHistoryComp form.setValue(maternalHistory); final CommitDiscardWrapperComponent component = - new CommitDiscardWrapperComponent(form, UiUtil.permitted(UserRight.CASE_EDIT), form.getFieldGroup()); + new CommitDiscardWrapperComponent<>(form, UiUtil.permitted(UserRight.CASE_EDIT), form.getFieldGroup()); component.addCommitListener(() -> { CaseDataDto caze1 = FacadeProvider.getCaseFacade().getCaseDataByUuid(caseUuid); caze1.setMaternalHistory(form.getValue()); @@ -1339,7 +1353,7 @@ public CommitDiscardWrapperComponent getPortHealthInfoCompon form.setValue(getPortHealthInfo(caze)); final CommitDiscardWrapperComponent component = - new CommitDiscardWrapperComponent(form, UiUtil.permitted(UserRight.PORT_HEALTH_INFO_EDIT), form.getFieldGroup()); + new CommitDiscardWrapperComponent<>(form, UiUtil.permitted(UserRight.PORT_HEALTH_INFO_EDIT), form.getFieldGroup()); component.addCommitListener(() -> { CaseDataDto caze1 = FacadeProvider.getCaseFacade().getCaseDataByUuid(caseUuid); caze1.setPortHealthInfo(form.getValue()); @@ -1369,7 +1383,7 @@ public CommitDiscardWrapperComponent getSymptomsEditComponent( symptomsForm.setValue(caseDataDto.getSymptoms()); CommitDiscardWrapperComponent editView = - new CommitDiscardWrapperComponent(symptomsForm, UiUtil.permitted(UserRight.CASE_EDIT), symptomsForm.getFieldGroup()); + new CommitDiscardWrapperComponent<>(symptomsForm, UiUtil.permitted(UserRight.CASE_EDIT), symptomsForm.getFieldGroup()); editView.addCommitListener(() -> { CaseDataDto cazeDto = FacadeProvider.getCaseFacade().getCaseDataByUuid(caseUuid); @@ -1393,6 +1407,11 @@ public CommitDiscardWrapperComponent getEpiDataComponent( boolean isEditAllowed) { CaseDataDto caze = findCase(caseUuid); + + List epiDataMetadata = + FacadeProvider.getCustomizableFieldMetadataFacade().getActiveFieldsForContext(CustomizableFieldContext.EPIDATA); + Map epiDataFieldValues = + FacadeProvider.getCustomizableFieldValueFacade().getValuesForEntity(caze.getEpiData().getUuid(), CustomizableFieldContext.EPIDATA); EpiDataDto epiDataDto = caze.getEpiData(); // Exposure start date and end date should be calculated based on symptom onsetDate and incubation start periods Date symptomOnsetDate = caze.getSymptoms().getOnsetDate(); @@ -1404,16 +1423,26 @@ public CommitDiscardWrapperComponent getEpiDataComponent( caze.isInJurisdiction(), sourceContactsToggleCallback, isEditAllowed, - symptomOnsetDate); + symptomOnsetDate, + epiDataMetadata, + epiDataFieldValues); epiDataForm.setValue(epiDataDto); - final CommitDiscardWrapperComponent editView = - new CommitDiscardWrapperComponent(epiDataForm, epiDataForm.getFieldGroup()); + final CommitDiscardWrapperComponent editView = new CommitDiscardWrapperComponent<>(epiDataForm, epiDataForm.getFieldGroup()); + + epiDataForm.addCustomizableFieldValueChangeListener(e -> editView.setDirty(true)); + editView.addDiscardListener(epiDataForm::resetCustomizableFieldValues); editView.addCommitListener(() -> { CaseDataDto cazeDto = FacadeProvider.getCaseFacade().getCaseDataByUuid(caseUuid); cazeDto.setEpiData(epiDataForm.getValue()); saveCase(cazeDto); + FacadeProvider.getCustomizableFieldValueFacade() + .saveEntityCustomFields(cazeDto.getEpiData().getUuid(), CustomizableFieldContext.EPIDATA, epiDataForm.collectCurrentFieldValues()); + epiDataForm.collectExposureCustomizableFieldValues() + .forEach( + (exposureUuid, fields) -> FacadeProvider.getCustomizableFieldValueFacade() + .saveEntityCustomFields(exposureUuid, CustomizableFieldContext.EXPOSURE, fields)); }); if (UiUtil.permitted(UserRight.CONTACT_VIEW)) { @@ -1480,7 +1509,7 @@ public DetailSubComponentWrapper getExternalDataComponent(final String caseUuid, return wrapper; } - public void saveCaseWithFacilityChangedPrompt(CaseDataDto caze, CaseDataDto oldCase) { + public void saveCaseWithFacilityChangedPrompt(CaseDataDto caze, CaseDataDto oldCase, Runnable afterSave) { if (FacilityType.HOSPITAL == caze.getFacilityType() && oldCase.getFacilityType() != FacilityType.HOSPITAL) { CurrentHospitalizationForm currentHospitalizationForm = new CurrentHospitalizationForm(); @@ -1498,13 +1527,13 @@ public void saveCaseWithFacilityChangedPrompt(CaseDataDto caze, CaseDataDto oldC switch (option) { case OPTION1: { caze.getHospitalization().setAdmittedToHealthFacility((YesNoUnknown) admittedToHealthFacilityField.getNullableValue()); - saveCaseWithOutcomeChangedWarning(caze, oldCase); + saveCaseWithOutcomeChangedWarning(caze, oldCase, afterSave); ControllerProvider.getCaseController().navigateToView(HospitalizationView.VIEW_NAME, caze.getUuid(), null); } break; case OPTION2: { caze.getHospitalization().setAdmittedToHealthFacility((YesNoUnknown) admittedToHealthFacilityField.getNullableValue()); - saveCaseWithOutcomeChangedWarning(caze, oldCase); + saveCaseWithOutcomeChangedWarning(caze, oldCase, afterSave); ControllerProvider.getCaseController().navigateToView(CaseDataView.VIEW_NAME, caze.getUuid(), null); } break; @@ -1524,15 +1553,19 @@ public void saveCaseWithFacilityChangedPrompt(CaseDataDto caze, CaseDataDto oldC 500, e -> { CaseLogic.handleHospitalization(caze, oldCase, e.booleanValue()); - saveCaseWithOutcomeChangedWarning(caze, oldCase); + saveCaseWithOutcomeChangedWarning(caze, oldCase, afterSave); SormasUI.refreshView(); }); } else { - saveCaseWithOutcomeChangedWarning(caze, oldCase); + saveCaseWithOutcomeChangedWarning(caze, oldCase, afterSave); } } - private void saveCaseWithOutcomeChangedWarning(CaseDataDto caze, CaseDataDto oldCase) { + public void saveCaseWithFacilityChangedPrompt(CaseDataDto caze, CaseDataDto oldCase) { + saveCaseWithFacilityChangedPrompt(caze, oldCase, null); + } + + private void saveCaseWithOutcomeChangedWarning(CaseDataDto caze, CaseDataDto oldCase, Runnable afterSave) { PersonDto person = FacadeProvider.getPersonFacade().getByUuid(caze.getPerson().getUuid()); PresentCondition presentCondition = person.getPresentCondition(); if (caze.getOutcome() == CaseOutcome.DECEASED @@ -1605,9 +1638,11 @@ private void saveCaseWithOutcomeChangedWarning(CaseDataDto caze, CaseDataDto old // actions warningComponent.addCommitListener(() -> { saveCase(caze); + if (afterSave != null) + afterSave.run(); popupWindow.close(); }); - warningComponent.addDiscardListener(() -> popupWindow.close()); + warningComponent.addDiscardListener(popupWindow::close); // popup configuration popupWindow.addCloseListener(e -> popupWindow.close()); @@ -1617,6 +1652,8 @@ private void saveCaseWithOutcomeChangedWarning(CaseDataDto caze, CaseDataDto old } } saveCase(caze); + if (afterSave != null) + afterSave.run(); } private PresentCondition getNewPresentConditionFromOutcome(CaseOutcome outcome) { @@ -1640,7 +1677,7 @@ public void referFromPointOfEntry(CaseDataDto caze) { CaseFacilityChangeForm form = new CaseFacilityChangeForm(); form.setValue(caze); CommitDiscardWrapperComponent view = - new CommitDiscardWrapperComponent(form, UiUtil.permitted(UserRight.CASE_REFER_FROM_POE), form.getFieldGroup()); + new CommitDiscardWrapperComponent<>(form, UiUtil.permitted(UserRight.CASE_REFER_FROM_POE), form.getFieldGroup()); view.getCommitButton().setCaption(I18nProperties.getCaption(Captions.caseReferFromPointOfEntry)); Window window = VaadinUiUtil.showPopupWindow(view); @@ -1657,9 +1694,7 @@ public void referFromPointOfEntry(CaseDataDto caze) { } }); - Button btnCancel = ButtonHelper.createButton(Captions.actionCancel, e -> { - window.close(); - }); + Button btnCancel = ButtonHelper.createButton(Captions.actionCancel, e -> window.close()); view.getButtonsPanel().replaceComponent(view.getDiscardButton(), btnCancel); } @@ -1722,12 +1757,12 @@ public void openClassificationRulesPopup(DiseaseClassificationCriteriaDto diseas } Window popupWindow = VaadinUiUtil.showPopupWindow(classificationRulesLayout); - popupWindow.addCloseListener(e -> { - popupWindow.close(); - }); + popupWindow.addCloseListener(e -> popupWindow.close()); popupWindow.setWidth(860, Unit.PIXELS); popupWindow.setHeight(80, Unit.PERCENTAGE); - popupWindow.setCaption(I18nProperties.getString(Strings.classificationRulesFor) + " " + diseaseCriteria.getDisease().toString()); + popupWindow.setCaption( + I18nProperties.getString(Strings.classificationRulesFor) + " " + + (diseaseCriteria != null ? diseaseCriteria.getDisease().toString() : "")); } public void deleteAllSelectedItems(Collection selectedRows, AbstractCaseGrid caseGrid) { @@ -1743,14 +1778,14 @@ public void restoreSelectedCases(Collection selectedRows public void sendSmsToAllSelectedItems(Collection selectedRows, Runnable callback) { - if (selectedRows.size() == 0) { + if (selectedRows.isEmpty()) { new Notification( I18nProperties.getString(Strings.headingNoCasesSelected), I18nProperties.getString(Strings.messageNoCasesSelected), Type.WARNING_MESSAGE, false).show(Page.getCurrent()); } else { - final List caseUuids = selectedRows.stream().map(caseIndexDto -> caseIndexDto.getUuid()).collect(Collectors.toList()); + final List caseUuids = selectedRows.stream().map(HasUuid::getUuid).collect(Collectors.toList()); final SmsComponent smsComponent = new SmsComponent(FacadeProvider.getCaseFacade().countCasesWithMissingContactInformation(caseUuids, MessageType.SMS)); VaadinUiUtil.showConfirmationPopup( @@ -1769,7 +1804,7 @@ public void sendSmsToAllSelectedItems(Collection selecte } public void sendEmailsToAllSelectedItems(Collection selectedRows, AbstractCaseGrid caseGrid) { - if (selectedRows.size() == 0) { + if (selectedRows.isEmpty()) { new Notification( I18nProperties.getString(Strings.headingNoCasesSelected), I18nProperties.getString(Strings.messageNoCasesSelected), @@ -1905,8 +1940,8 @@ public void sendCasesToExternalSurveillanceTool(Collect selectedCases.size()), I18nProperties.getCaption(Captions.actionCancel), 800, - (confirmed) -> { - if (confirmed) { + confirmed -> { + if (Boolean.TRUE.equals(confirmed)) { ExternalSurveillanceServiceGateway .sendCasesToExternalSurveillanceTool(withoutNotShareable, false, bulkOperationCallback(caseGrid, null)); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java index 16c549d30a5..a78072d44da 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java @@ -36,7 +36,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -97,6 +99,10 @@ import de.symeda.sormas.api.contact.FollowUpStatus; import de.symeda.sormas.api.contact.QuarantineType; import de.symeda.sormas.api.customizableenum.CustomizableEnumType; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityContext; import de.symeda.sormas.api.disease.DiseaseVariant; import de.symeda.sormas.api.event.TypeOfPlace; import de.symeda.sormas.api.feature.FeatureType; @@ -154,12 +160,36 @@ import de.symeda.sormas.ui.utils.VaadinUiUtil; import de.symeda.sormas.ui.utils.ValidationUtils; import de.symeda.sormas.ui.utils.ViewMode; +import de.symeda.sormas.ui.utils.components.CustomizableFieldsGroup; +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress missing equals not relevant for Vaadin components +}) public class CaseDataForm extends AbstractEditForm { private static final long serialVersionUID = 1L; private static final String CASE_DATA_HEADING_LOC = "caseDataHeadingLoc"; + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_GENERAL = CustomizableFieldGroup.CASE_DATA_GENERAL.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_CLASSIFICATION = CustomizableFieldGroup.CASE_DATA_CLASSIFICATION.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_IDENTIFIERS = CustomizableFieldGroup.CASE_DATA_IDENTIFIERS.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_INVESTIGATION = CustomizableFieldGroup.CASE_DATA_INVESTIGATION.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_DISEASE = CustomizableFieldGroup.CASE_DATA_DISEASE.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_REINFECTION = CustomizableFieldGroup.CASE_DATA_REINFECTION.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_OUTCOME = CustomizableFieldGroup.CASE_DATA_OUTCOME.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_SEQUELAE = CustomizableFieldGroup.CASE_DATA_SEQUELAE.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_JURISDICTION = CustomizableFieldGroup.CASE_DATA_JURISDICTION.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_PLACE_OF_STAY = CustomizableFieldGroup.CASE_DATA_PLACE_OF_STAY.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_QUARANTINE = CustomizableFieldGroup.CASE_DATA_QUARANTINE.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_REPORT_GEO = CustomizableFieldGroup.CASE_DATA_REPORT_GEO.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_HEALTH_CONDITIONS = CustomizableFieldGroup.CASE_DATA_HEALTH_CONDITIONS.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_DIAGNOSTIC = CustomizableFieldGroup.CASE_DATA_DIAGNOSTIC.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_MEDICAL_INFORMATION = CustomizableFieldGroup.CASE_DATA_MEDICAL_INFORMATION.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_VACCINATION = CustomizableFieldGroup.CASE_DATA_VACCINATION.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_CLINICIAN_NOTIFICATION = + CustomizableFieldGroup.CASE_DATA_CLINICIAN_NOTIFICATION.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_CONTACT_TRACING = CustomizableFieldGroup.CASE_DATA_CONTACT_TRACING.getKey(); private static final String MEDICAL_INFORMATION_LOC = "medicalInformationLoc"; private static final String PAPER_FORM_DATES_LOC = "paperFormDatesLoc"; private static final String SMALLPOX_VACCINATION_SCAR_IMG = "smallpoxVaccinationScarImg"; @@ -183,8 +213,6 @@ public class CaseDataForm extends AbstractEditForm { private static final String DONT_SHARE_WARNING_LOC = "dontShareWarning"; private static final String CASE_CLASSIFICATION_CALCULATE_BTN_LOC = "caseClassificationCalculateBtnLoc"; private static final String REINFECTION_INFO_LOC = "reinfectionInfoLoc"; - private static final String REINFECTION_DETAILS_COL_1_LOC = "reinfectionDetailsCol1Loc"; - private static final String REINFECTION_DETAILS_COL_2_LOC = "reinfectionDetailsCol2Loc"; private static final String VACCINATION_STATUS_INFO_LOC = "vaccinationStatusInfoLoc"; private static final String VACCINATION_STATUS_DETAILS_LOC = "vaccinationStatusDetailsLoc"; public static final String CASE_REFER_POINT_OF_ENTRY_BTN_LOC = "caseReferFromPointOfEntryBtnLoc"; @@ -196,6 +224,7 @@ public class CaseDataForm extends AbstractEditForm { private static final String MAIN_HTML_LAYOUT = loc(CASE_DATA_HEADING_LOC) + fluidRowLocs(4, CaseDataDto.UUID, 3, CaseDataDto.REPORT_DATE, 3, CaseDataDto.REPORTING_USER, 2, "") + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_GENERAL) + inlineLocs(CaseDataDto.CASE_CLASSIFICATION, CLASSIFICATION_RULES_LOC, CASE_CONFIRMATION_BASIS, CASE_CLASSIFICATION_CALCULATE_BTN_LOC) + fluidRow(fluidColumnLoc(3, 0, CaseDataDto.CASE_REFERENCE_DEFINITION)) + fluidRowLocs(4, CaseDataDto.CLINICAL_CONFIRMATION, 4, CaseDataDto.EPIDEMIOLOGICAL_CONFIRMATION, 4, CaseDataDto.LABORATORY_DIAGNOSTIC_CONFIRMATION) + @@ -206,13 +235,16 @@ public class CaseDataForm extends AbstractEditForm { fluidColumnLoc(3, 0, CaseDataDto.CLASSIFICATION_DATE), fluidColumnLocCss(LAYOUT_COL_HIDE_INVSIBLE, 5, 0, CaseDataDto.CLASSIFICATION_USER), fluidColumnLocCss(LAYOUT_COL_HIDE_INVSIBLE, 4, 0, CLASSIFIED_BY_SYSTEM_LOC)) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_CLASSIFICATION) + fluidRowLocs(9, CaseDataDto.INVESTIGATION_STATUS, 3, CaseDataDto.INVESTIGATED_DATE) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_INVESTIGATION) + fluidRowLocs(6, CaseDataDto.EPID_NUMBER, 3, ASSIGN_NEW_EPID_NUMBER_LOC) + loc(EPID_NUMBER_WARNING_LOC) + fluidRowLocs(CaseDataDto.EXTERNAL_ID, CaseDataDto.EXTERNAL_TOKEN) + fluidRowLocs("", EXTERNAL_TOKEN_WARNING_LOC) + fluidRowLocs(6, CaseDataDto.CASE_ID_ISM, 6, CaseDataDto.INTERNAL_TOKEN) + fluidRowLocs(CaseDataDto.CASE_REFERENCE_NUMBER, "") + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_IDENTIFIERS) + fluidRow( fluidColumnLoc(6, 0, CaseDataDto.DISEASE), fluidColumn(6, 0, locs( @@ -221,6 +253,7 @@ public class CaseDataForm extends AbstractEditForm { CaseDataDto.DENGUE_FEVER_TYPE, CaseDataDto.RABIES_TYPE))) + fluidRowLocs(CaseDataDto.DISEASE_VARIANT, CaseDataDto.DISEASE_VARIANT_DETAILS) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_DISEASE) + fluidRow( fluidColumnLoc(4, 0, CaseDataDto.RE_INFECTION), fluidColumnLoc(1, 0, REINFECTION_INFO_LOC), @@ -228,12 +261,16 @@ public class CaseDataForm extends AbstractEditForm { fluidColumnLoc(4, 0, CaseDataDto.PREVIOUS_INFECTION_DATE) ) + fluidRowLocs(CaseDataDto.REINFECTION_DETAILS) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_REINFECTION) + fluidRowLocs(6, CaseDataDto.OUTCOME, 3, CaseDataDto.OUTCOME_DATE, 3, CaseDataDto.POST_MORTEM) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_OUTCOME) + fluidRowLocs(3, CaseDataDto.SEQUELAE, 9, CaseDataDto.SEQUELAE_DETAILS) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_SEQUELAE) + fluidRowLocs(CaseDataDto.CASE_IDENTIFICATION_SOURCE, CaseDataDto.SCREENING_TYPE) + fluidRowLocs(CaseDataDto.CASE_ORIGIN, "") + fluidRowLocs(RESPONSIBLE_JURISDICTION_HEADING_LOC) + fluidRowLocs(CaseDataDto.RESPONSIBLE_REGION, CaseDataDto.RESPONSIBLE_DISTRICT, CaseDataDto.RESPONSIBLE_COMMUNITY) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_JURISDICTION) + fluidRowLocs(CaseDataDto.DONT_SHARE_WITH_REPORTING_TOOL) + fluidRowLocs(DONT_SHARE_WARNING_LOC) + fluidRowLocs(DIFFERENT_PLACE_OF_STAY_JURISDICTION) + @@ -243,6 +280,7 @@ public class CaseDataForm extends AbstractEditForm { fluidRowLocs(TYPE_GROUP_LOC, CaseDataDto.FACILITY_TYPE) + fluidRowLocs(CaseDataDto.HEALTH_FACILITY, CaseDataDto.HEALTH_FACILITY_DETAILS) + fluidRow(fluidColumnLoc(6, 0,CaseDataDto.DEPARTMENT)) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_PLACE_OF_STAY) + inlineLocs(CaseDataDto.POINT_OF_ENTRY, CaseDataDto.POINT_OF_ENTRY_DETAILS, CASE_REFER_POINT_OF_ENTRY_BTN_LOC) + fluidRowLocs(CaseDataDto.NOSOCOMIAL_OUTBREAK, CaseDataDto.INFECTION_SETTING) + locCss(VSPACE_3, CaseDataDto.SHARED_TO_COUNTRY) + @@ -261,26 +299,35 @@ public class CaseDataForm extends AbstractEditForm { fluidRowLocs(CaseDataDto.WAS_IN_QUARANTINE_BEFORE_ISOLATION) + fluidRowLocs(CaseDataDto.QUARANTINE_REASON_BEFORE_ISOLATION, CaseDataDto.QUARANTINE_REASON_BEFORE_ISOLATION_DETAILS) + fluidRowLocs(CaseDataDto.END_OF_ISOLATION_REASON, CaseDataDto.END_OF_ISOLATION_REASON_DETAILS) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_QUARANTINE) + fluidRowLocs(CaseDataDto.REPORT_LAT, CaseDataDto.REPORT_LON, CaseDataDto.REPORT_LAT_LON_ACCURACY) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_REPORT_GEO) + fluidRowLocs(CaseDataDto.HEALTH_CONDITIONS) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_HEALTH_CONDITIONS) + loc(DIAGNOSIS_CRITERIA_HEADING_LOC) + loc(DIAGNOSIS_CRITERIA_SUBHEADING_LOC) + fluidRowLocs(DIAGNOSIS_CRITERIA_LAB_TEST_PANEL_LOC) + fluidRowLocs(8, CaseDataDto.RADIOGRAPHY_COMPATIBILITY) + fluidRowLocs(CaseDataDto.OTHER_DIAGNOSTIC_CRITERIA) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_DIAGNOSTIC) + loc(MEDICAL_INFORMATION_LOC) + fluidRowLocs(CaseDataDto.BLOOD_ORGAN_OR_TISSUE_DONATED) + fluidRowLocs(CaseDataDto.PREGNANT, CaseDataDto.POSTPARTUM) + fluidRowLocs(CaseDataDto.TRIMESTER, "") + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_MEDICAL_INFORMATION) + inlineLocs(CaseDataDto.VACCINATION_STATUS, VACCINATION_STATUS_INFO_LOC) + fluidRowLocs(VACCINATION_STATUS_DETAILS_LOC) + fluidRowLocs(CaseDataDto.SMALLPOX_VACCINATION_RECEIVED, CaseDataDto.SMALLPOX_VACCINATION_SCAR) + fluidRowLocs(CaseDataDto.SMALLPOX_LAST_VACCINATION_DATE, "") + fluidRowLocs(SMALLPOX_VACCINATION_SCAR_IMG) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_VACCINATION) + fluidRowLocs(6, CaseDataDto.CLINICIAN_NAME) + fluidRowLocs(CaseDataDto.NOTIFYING_CLINIC, CaseDataDto.NOTIFYING_CLINIC_DETAILS) + fluidRowLocs(CaseDataDto.CLINICIAN_PHONE, CaseDataDto.CLINICIAN_EMAIL) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_CLINICIAN_NOTIFICATION) + loc(CONTACT_TRACING_FIRST_CONTACT_HEADER_LOC) + - fluidRowLocs(CaseDataDto.CONTACT_TRACING_FIRST_CONTACT_TYPE, CaseDataDto.CONTACT_TRACING_FIRST_CONTACT_DATE); + fluidRowLocs(CaseDataDto.CONTACT_TRACING_FIRST_CONTACT_TYPE, CaseDataDto.CONTACT_TRACING_FIRST_CONTACT_DATE) + + loc(LOC_CUSTOMIZABLE_FIELDS_CASE_DATA_CONTACT_TRACING); + private static final String FOLLOWUP_LAYOUT = loc(FOLLOW_UP_STATUS_HEADING_LOC) + @@ -298,6 +345,25 @@ public class CaseDataForm extends AbstractEditForm { fluidRowLocs(CaseDataDto.OTHER_DELETION_REASON); //@formatter:on + private CustomizableFieldsGroup caseDataGeneralPanel; + private CustomizableFieldsGroup caseDataClassificationPanel; + private CustomizableFieldsGroup caseDataIdentifiersPanel; + private CustomizableFieldsGroup caseDataInvestigationPanel; + private CustomizableFieldsGroup caseDataDiseasePanel; + private CustomizableFieldsGroup caseDataReinfectionPanel; + private CustomizableFieldsGroup caseDataOutcomePanel; + private CustomizableFieldsGroup caseDataSequelaePanel; + private CustomizableFieldsGroup caseDataJurisdictionPanel; + private CustomizableFieldsGroup caseDataPlaceOfStayPanel; + private CustomizableFieldsGroup caseDataQuarantinePanel; + private CustomizableFieldsGroup caseDataReportGeoPanel; + private CustomizableFieldsGroup caseDataHealthConditionsPanel; + private CustomizableFieldsGroup caseDataDiagnosticPanel; + private CustomizableFieldsGroup caseDataMedicalInformationPanel; + private CustomizableFieldsGroup caseDataVaccinationPanel; + private CustomizableFieldsGroup caseDataClinicianNotificationPanel; + private CustomizableFieldsGroup caseDataContactTracingPanel; + private final String caseUuid; private final PersonDto person; private final Disease disease; @@ -312,8 +378,6 @@ public class CaseDataForm extends AbstractEditForm { private DateField dfPreviousQuarantineTo; private CheckBox cbQuarantineExtended; private CheckBox cbQuarantineReduced; - private CheckBox quarantineOrderedVerbally; - private CheckBox quarantineOrderedOfficialDocument; private CheckBox differentPlaceOfStayJurisdiction; private ComboBox responsibleRegion; private ComboBox responsibleDistrict; @@ -326,7 +390,6 @@ public class CaseDataForm extends AbstractEditForm { private ComboBoxWithPlaceholder facilityTypeCombo; private ComboBox facilityCombo; private TextField facilityDetails; - private TextField tfDepartment; private boolean quarantineChangedByFollowUpUntilChange = false; private TextField tfExpectedFollowUpUntilDate; private FollowUpPeriodDto expectedFollowUpPeriodDto; @@ -334,6 +397,7 @@ public class CaseDataForm extends AbstractEditForm { private CheckBox postMortemCB; private Label vaccinationStatusInfoLabel; + @SuppressWarnings("java:S107") // sonar: constructor too many parameters public CaseDataForm( String caseUuid, PersonDto person, @@ -341,7 +405,9 @@ public CaseDataForm( SymptomsDto symptoms, ViewMode viewMode, boolean isPseudonymized, - boolean inJurisdiction) { + boolean inJurisdiction, + List customizableFieldsMetadata, + Map customizableFieldsValues) { super( CaseDataDto.class, @@ -359,6 +425,8 @@ public CaseDataForm( this.disease = disease; this.symptoms = symptoms; this.caseFollowUpEnabled = UiUtil.enabled(FeatureType.CASE_FOLLOWUP); + setCustomizableFieldsMetadata(customizableFieldsMetadata); + setCustomizableFieldsValues(customizableFieldsValues); addFields(); } @@ -388,7 +456,6 @@ public static void updateFacilityDetails(ComboBox cbFacility, TextField tfFacili } } - @SuppressWarnings("deprecation") @Override protected void addFields() { @@ -458,7 +525,7 @@ protected void addFields() { externalTokenWarningLabel.addStyleNames(VSPACE_3, LABEL_WHITE_SPACE_NORMAL); getContent().addComponent(externalTokenWarningLabel, EXTERNAL_TOKEN_WARNING_LOC); - tfDepartment = addField(CaseDataDto.DEPARTMENT, TextField.class); + TextField tfDepartment = addField(CaseDataDto.DEPARTMENT, TextField.class); tfDepartment.setCaption(I18nProperties.getPrefixCaption(CaseDataDto.I18N_PREFIX, CaseDataDto.DEPARTMENT)); addField(CaseDataDto.INTERNAL_TOKEN, TextField.class); addField(CaseDataDto.CASE_REFERENCE_NUMBER, TextField.class); @@ -469,6 +536,7 @@ protected void addFields() { addField(CaseDataDto.SEQUELAE, NullableOptionGroup.class); addFields(CaseDataDto.INVESTIGATED_DATE, CaseDataDto.OUTCOME_DATE, CaseDataDto.SEQUELAE_DETAILS); + postMortemCB = addField(CaseDataDto.POST_MORTEM, CheckBox.class); postMortemCB.setValue(false); addField(CaseDataDto.CASE_IDENTIFICATION_SOURCE); @@ -605,10 +673,10 @@ protected void addFields() { CaseDataDto.LABORATORY_DIAGNOSTIC_CONFIRMATION); } - quarantineOrderedVerbally = addField(CaseDataDto.QUARANTINE_ORDERED_VERBALLY, CheckBox.class); + CheckBox quarantineOrderedVerbally = addField(CaseDataDto.QUARANTINE_ORDERED_VERBALLY, CheckBox.class); CssStyles.style(quarantineOrderedVerbally, CssStyles.FORCE_CAPTION); addField(CaseDataDto.QUARANTINE_ORDERED_VERBALLY_DATE, DateField.class); - quarantineOrderedOfficialDocument = addField(CaseDataDto.QUARANTINE_ORDERED_OFFICIAL_DOCUMENT, CheckBox.class); + CheckBox quarantineOrderedOfficialDocument = addField(CaseDataDto.QUARANTINE_ORDERED_OFFICIAL_DOCUMENT, CheckBox.class); CssStyles.style(quarantineOrderedOfficialDocument, CssStyles.FORCE_CAPTION); addField(CaseDataDto.QUARANTINE_ORDERED_OFFICIAL_DOCUMENT_DATE, DateField.class); @@ -642,68 +710,7 @@ protected void addFields() { FieldHelper.setVisibleWhen(getFieldGroup(), CaseDataDto.INFECTION_SETTING, CaseDataDto.NOSOCOMIAL_OUTBREAK, true, true); // Reinfection - { - NullableOptionGroup ogReinfection = addField(CaseDataDto.RE_INFECTION, NullableOptionGroup.class); - - addField(CaseDataDto.PREVIOUS_INFECTION_DATE); - ComboBox tfReinfectionStatus = addField(CaseDataDto.REINFECTION_STATUS, ComboBox.class); - tfReinfectionStatus.setReadOnly(true); - FieldHelper.setVisibleWhen(getFieldGroup(), CaseDataDto.PREVIOUS_INFECTION_DATE, CaseDataDto.RE_INFECTION, YesNoUnknown.YES, false); - FieldHelper.setVisibleWhen(getFieldGroup(), CaseDataDto.REINFECTION_STATUS, CaseDataDto.RE_INFECTION, YesNoUnknown.YES, false); - - final Label reinfectionInfoLabel = new Label(VaadinIcons.EYE.getHtml(), ContentMode.HTML); - CssStyles.style(reinfectionInfoLabel, CssStyles.LABEL_XLARGE, CssStyles.VSPACE_TOP_3); - getContent().addComponent(reinfectionInfoLabel, REINFECTION_INFO_LOC); - reinfectionInfoLabel.setVisible(false); - - CheckBoxTree reinfectionDetailGroupCheckBoxTree = addField(CaseDataDto.REINFECTION_DETAILS, CheckBoxTree.class); - reinfectionDetailGroupCheckBoxTree.setEnumType(ReinfectionDetail.class, ReinfectionDetail::getGroup, ReinfectionDetailGroup.class, 2); - - tfReinfectionStatus.setReadOnly(false); - tfReinfectionStatus.setValue(CaseLogic.calculateReinfectionStatus(reinfectionDetailGroupCheckBoxTree.getValue())); - tfReinfectionStatus.setReadOnly(true); - - reinfectionDetailGroupCheckBoxTree.addValueChangeListener(e -> { - tfReinfectionStatus.setReadOnly(false); - tfReinfectionStatus.setValue(CaseLogic.calculateReinfectionStatus(reinfectionDetailGroupCheckBoxTree.getValue())); - tfReinfectionStatus.setReadOnly(true); - }); - - ogReinfection.addValueChangeListener(e -> { - if (((NullableOptionGroup) e.getProperty()).getNullableValue() == YesNoUnknown.YES) { - PreviousCaseDto previousCase = FacadeProvider.getCaseFacade() - .getMostRecentPreviousCase(getValue().getPerson(), getValue().getDisease(), CaseLogic.getStartDate(getValue())); - - if (previousCase != null) { - String reinfectionInfoTemplate = "Previous case:

%s: %s
%s: %s
%s: %s
%s: %s
%s: %s"; - String reinfectionInfo = String.format( - reinfectionInfoTemplate, - I18nProperties.getPrefixCaption(CaseDataDto.I18N_PREFIX, EntityDto.UUID), - DataHelper.getShortUuid(previousCase.getUuid()), - I18nProperties.getPrefixCaption(CaseDataDto.I18N_PREFIX, CaseDataDto.REPORT_DATE), - DateHelper.formatLocalDate(previousCase.getReportDate(), I18nProperties.getUserLanguage()), - I18nProperties.getPrefixCaption(CaseDataDto.I18N_PREFIX, CaseDataDto.EXTERNAL_TOKEN), - DataHelper.toStringNullable(previousCase.getExternalToken()), - I18nProperties.getPrefixCaption(CaseDataDto.I18N_PREFIX, CaseDataDto.DISEASE_VARIANT), - DataHelper.toStringNullable(previousCase.getDiseaseVariant()), - I18nProperties.getPrefixCaption(SymptomsDto.I18N_PREFIX, SymptomsDto.ONSET_DATE), - previousCase.getOnsetDate() != null - ? DateHelper.formatLocalDate(previousCase.getOnsetDate(), I18nProperties.getUserLanguage()) - : ""); - reinfectionInfoLabel.setDescription(reinfectionInfo, ContentMode.HTML); - reinfectionInfoLabel.setVisible(isVisibleAllowed(CaseDataDto.RE_INFECTION)); - } else { - reinfectionInfoLabel.setDescription(null); - reinfectionInfoLabel.setVisible(false); - } - reinfectionDetailGroupCheckBoxTree.setVisible(isVisibleAllowed(CaseDataDto.RE_INFECTION)); - } else { - reinfectionInfoLabel.setDescription(null); - reinfectionInfoLabel.setVisible(false); - reinfectionDetailGroupCheckBoxTree.setVisible(false); - } - }); - } + addReinfectionFields(); addField(CaseDataDto.QUARANTINE_HOME_POSSIBLE, NullableOptionGroup.class); addField(CaseDataDto.QUARANTINE_HOME_POSSIBLE_COMMENT, TextField.class); @@ -1093,7 +1100,7 @@ protected void addFields() { updateFacility(); } }); - responsibleCommunity.addValueChangeListener((e) -> { + responsibleCommunity.addValueChangeListener(e -> { Boolean differentPlaceOfStay = differentPlaceOfStayJurisdiction.getValue(); if (differentPlaceOfStay == null || Boolean.FALSE.equals(differentPlaceOfStay)) { updateFacility(); @@ -1113,8 +1120,7 @@ protected void addFields() { FieldVisibilityCheckers.withDisease(disease) .add(new CountryFieldVisibilityChecker(FacadeProvider.getConfigFacade().getCountryLocale())), UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale()), - new PersonReferenceDto(person.getUuid()))) - .setCaption(null); + new PersonReferenceDto(person.getUuid()))).setCaption(null); //diagnosis criteria if ((FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) && disease == Disease.TUBERCULOSIS) { @@ -1158,6 +1164,7 @@ protected void addFields() { if (diseaseClassificationExists() && FacadeProvider.getConfigFacade().getCaseClassificationCalculationMode(disease).isManualEnabled() && isVisibleAllowed(CaseDataDto.CASE_CLASSIFICATION)) { + @SuppressWarnings("unchecked") Button caseClassificationCalculationButton = ButtonHelper.createButton(Captions.caseClassificationCalculationButton, e -> { CaseClassification classification = FacadeProvider.getCaseClassificationFacade().getClassification(getValue()); ((Field) getField(CaseDataDto.CASE_CLASSIFICATION)).setValue(classification); @@ -1262,15 +1269,13 @@ && isVisibleAllowed(CaseDataDto.CASE_CLASSIFICATION)) { true); } - if (isVisibleAllowed(CaseDataDto.SMALLPOX_LAST_VACCINATION_DATE)) { - if (isVisibleAllowed(CaseDataDto.SMALLPOX_VACCINATION_RECEIVED)) { - FieldHelper.setVisibleWhen( - getFieldGroup(), - CaseDataDto.SMALLPOX_LAST_VACCINATION_DATE, - CaseDataDto.SMALLPOX_VACCINATION_RECEIVED, - Collections.singletonList(YesNoUnknown.YES), - true); - } + if (isVisibleAllowed(CaseDataDto.SMALLPOX_LAST_VACCINATION_DATE) && isVisibleAllowed(CaseDataDto.SMALLPOX_VACCINATION_RECEIVED)) { + FieldHelper.setVisibleWhen( + getFieldGroup(), + CaseDataDto.SMALLPOX_LAST_VACCINATION_DATE, + CaseDataDto.SMALLPOX_VACCINATION_RECEIVED, + Collections.singletonList(YesNoUnknown.YES), + true); } // Sync visibility of info label with vaccination status field @@ -1409,16 +1414,15 @@ && diseaseClassificationExists()) { setEpidNumberError(epidField, assignNewEpidNumberButton, epidNumberWarningLabel, getValue().getEpidNumber()); - epidField.addValueChangeListener(f -> { - setEpidNumberError(epidField, assignNewEpidNumberButton, epidNumberWarningLabel, (String) f.getProperty().getValue()); - }); + epidField.addValueChangeListener( + f -> setEpidNumberError(epidField, assignNewEpidNumberButton, epidNumberWarningLabel, (String) f.getProperty().getValue())); ValidationUtils.initComponentErrorValidator( externalTokenField, getValue().getExternalToken(), Validations.duplicateExternalToken, externalTokenWarningLabel, - (externalToken) -> FacadeProvider.getCaseFacade().doesExternalTokenExist(externalToken, getValue().getUuid())); + externalToken -> FacadeProvider.getCaseFacade().doesExternalTokenExist(externalToken, getValue().getUuid())); updateFacilityOrHome(); @@ -1536,6 +1540,73 @@ public String getFormattedHtmlMessage() { CaseDataDto.CLINICIAN_PHONE, CaseDataDto.CLINICIAN_EMAIL, CaseDataDto.ADDITIONAL_DETAILS); + + // Customizable fields group panels + initializeCustomizableFieldPanels(); + } + + private void addReinfectionFields() { + NullableOptionGroup ogReinfection = addField(CaseDataDto.RE_INFECTION, NullableOptionGroup.class); + + addField(CaseDataDto.PREVIOUS_INFECTION_DATE); + ComboBox tfReinfectionStatus = addField(CaseDataDto.REINFECTION_STATUS, ComboBox.class); + tfReinfectionStatus.setReadOnly(true); + FieldHelper.setVisibleWhen(getFieldGroup(), CaseDataDto.PREVIOUS_INFECTION_DATE, CaseDataDto.RE_INFECTION, YesNoUnknown.YES, false); + FieldHelper.setVisibleWhen(getFieldGroup(), CaseDataDto.REINFECTION_STATUS, CaseDataDto.RE_INFECTION, YesNoUnknown.YES, false); + + final Label reinfectionInfoLabel = new Label(VaadinIcons.EYE.getHtml(), ContentMode.HTML); + CssStyles.style(reinfectionInfoLabel, CssStyles.LABEL_XLARGE, CssStyles.VSPACE_TOP_3); + getContent().addComponent(reinfectionInfoLabel, REINFECTION_INFO_LOC); + reinfectionInfoLabel.setVisible(false); + + @SuppressWarnings("unchecked") + CheckBoxTree reinfectionDetailGroupCheckBoxTree = addField(CaseDataDto.REINFECTION_DETAILS, CheckBoxTree.class); + reinfectionDetailGroupCheckBoxTree.setEnumType(ReinfectionDetail.class, ReinfectionDetail::getGroup, ReinfectionDetailGroup.class, 2); + + tfReinfectionStatus.setReadOnly(false); + tfReinfectionStatus.setValue(CaseLogic.calculateReinfectionStatus(reinfectionDetailGroupCheckBoxTree.getValue())); + tfReinfectionStatus.setReadOnly(true); + + reinfectionDetailGroupCheckBoxTree.addValueChangeListener(e -> { + tfReinfectionStatus.setReadOnly(false); + tfReinfectionStatus.setValue(CaseLogic.calculateReinfectionStatus(reinfectionDetailGroupCheckBoxTree.getValue())); + tfReinfectionStatus.setReadOnly(true); + }); + + ogReinfection.addValueChangeListener(e -> { + if (((NullableOptionGroup) e.getProperty()).getNullableValue() == YesNoUnknown.YES) { + PreviousCaseDto previousCase = FacadeProvider.getCaseFacade() + .getMostRecentPreviousCase(getValue().getPerson(), getValue().getDisease(), CaseLogic.getStartDate(getValue())); + + if (previousCase != null) { + String reinfectionInfoTemplate = "Previous case:

%s: %s
%s: %s
%s: %s
%s: %s
%s: %s"; + String reinfectionInfo = String.format( + reinfectionInfoTemplate, + I18nProperties.getPrefixCaption(CaseDataDto.I18N_PREFIX, EntityDto.UUID), + DataHelper.getShortUuid(previousCase.getUuid()), + I18nProperties.getPrefixCaption(CaseDataDto.I18N_PREFIX, CaseDataDto.REPORT_DATE), + DateHelper.formatLocalDate(previousCase.getReportDate(), I18nProperties.getUserLanguage()), + I18nProperties.getPrefixCaption(CaseDataDto.I18N_PREFIX, CaseDataDto.EXTERNAL_TOKEN), + DataHelper.toStringNullable(previousCase.getExternalToken()), + I18nProperties.getPrefixCaption(CaseDataDto.I18N_PREFIX, CaseDataDto.DISEASE_VARIANT), + DataHelper.toStringNullable(previousCase.getDiseaseVariant()), + I18nProperties.getPrefixCaption(SymptomsDto.I18N_PREFIX, SymptomsDto.ONSET_DATE), + previousCase.getOnsetDate() != null + ? DateHelper.formatLocalDate(previousCase.getOnsetDate(), I18nProperties.getUserLanguage()) + : ""); + reinfectionInfoLabel.setDescription(reinfectionInfo, ContentMode.HTML); + reinfectionInfoLabel.setVisible(isVisibleAllowed(CaseDataDto.RE_INFECTION)); + } else { + reinfectionInfoLabel.setDescription(null); + reinfectionInfoLabel.setVisible(false); + } + reinfectionDetailGroupCheckBoxTree.setVisible(isVisibleAllowed(CaseDataDto.RE_INFECTION)); + } else { + reinfectionInfoLabel.setDescription(null); + reinfectionInfoLabel.setVisible(false); + reinfectionDetailGroupCheckBoxTree.setVisible(false); + } + }); } /** @@ -1558,9 +1629,7 @@ private void getManualCaseDefinition() { suspectContent.setValue(caseDefinitionText); classificationRulesLayout.addComponent(suspectContent); Window popupWindow = VaadinUiUtil.showPopupWindow(classificationRulesLayout); - popupWindow.addCloseListener(e1 -> { - popupWindow.close(); - }); + popupWindow.addCloseListener(e1 -> popupWindow.close()); popupWindow.setWidth(860, Unit.PIXELS); popupWindow.setHeight(80, Unit.PERCENTAGE); popupWindow.setCaption(I18nProperties.getString(Strings.caseDefinitionForDisease) + " " + disease); @@ -1642,7 +1711,7 @@ private void onFollowUpUntilChanged() { I18nProperties.getString(Strings.no), 640, confirmed -> { - if (confirmed) { + if (Boolean.TRUE.equals(confirmed)) { quarantineChangedByFollowUpUntilChange = true; dfQuarantineTo.setValue(newFollowUpUntil); if (oldQuarantineEnd != null) { @@ -1707,7 +1776,7 @@ private void confirmQuarantineEndChanged(ExtendedReduced changeType, CaseDataDto 640, confirmed -> { Date quarantineTo = originalCase.getQuarantineTo(); - if (confirmed) { + if (Boolean.TRUE.equals(confirmed)) { dfPreviousQuarantineTo.setReadOnly(false); dfPreviousQuarantineTo.setValue(quarantineTo); dfPreviousQuarantineTo.setReadOnly(true); @@ -1751,7 +1820,7 @@ private void confirmExtendFollowUpPeriod(CaseDataDto originalCase) { I18nProperties.getString(Strings.no), 640, confirmed -> { - if (confirmed) { + if (Boolean.TRUE.equals(confirmed)) { cbOverwriteFollowUpUntil.setValue(true); dfFollowUpUntil.setValue(quarantineEnd); } @@ -1923,6 +1992,84 @@ protected String createHtmlLayout() { return MAIN_HTML_LAYOUT + (caseFollowUpEnabled ? FOLLOWUP_LAYOUT : "") + PAPER_FORM_DATES_AND_HEALTH_CONDITIONS_HTML_LAYOUT; } + private CustomizableFieldsGroup createAndAddCustomizablePanel(CustomizableFieldGroup group) { + CustomizableFieldsGroup panel = new CustomizableFieldsGroup(group); + panel.setVisibilityContext(new CustomizableFieldVisibilityContext().withDisease(disease)); + panel.setFieldsMetadata(getCustomizableFieldsMetadata()); + panel.setFieldsValues(getCustomizableFieldsValues()); + panel.updateFieldsDisplay(); + getContent().addComponent(panel, group.getKey()); + return panel; + } + + private void initializeCustomizableFieldPanels() { + caseDataGeneralPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_GENERAL); + caseDataClassificationPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_CLASSIFICATION); + caseDataInvestigationPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_INVESTIGATION); + caseDataIdentifiersPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_IDENTIFIERS); + caseDataDiseasePanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_DISEASE); + caseDataReinfectionPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_REINFECTION); + caseDataOutcomePanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_OUTCOME); + caseDataSequelaePanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_SEQUELAE); + caseDataJurisdictionPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_JURISDICTION); + caseDataPlaceOfStayPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_PLACE_OF_STAY); + caseDataQuarantinePanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_QUARANTINE); + caseDataReportGeoPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_REPORT_GEO); + caseDataHealthConditionsPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_HEALTH_CONDITIONS); + caseDataDiagnosticPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_DIAGNOSTIC); + caseDataMedicalInformationPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_MEDICAL_INFORMATION); + caseDataVaccinationPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_VACCINATION); + caseDataClinicianNotificationPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_CLINICIAN_NOTIFICATION); + caseDataContactTracingPanel = createAndAddCustomizablePanel(CustomizableFieldGroup.CASE_DATA_CONTACT_TRACING); + } + + private List getCustomizableFieldPanels() { + return Arrays + .asList( + caseDataGeneralPanel, + caseDataClassificationPanel, + caseDataInvestigationPanel, + caseDataIdentifiersPanel, + caseDataDiseasePanel, + caseDataReinfectionPanel, + caseDataOutcomePanel, + caseDataSequelaePanel, + caseDataJurisdictionPanel, + caseDataPlaceOfStayPanel, + caseDataQuarantinePanel, + caseDataReportGeoPanel, + caseDataHealthConditionsPanel, + caseDataDiagnosticPanel, + caseDataMedicalInformationPanel, + caseDataVaccinationPanel, + caseDataClinicianNotificationPanel, + caseDataContactTracingPanel) + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + public Map collectCurrentFieldValues() { + Map result = new HashMap<>(); + getCustomizableFieldPanels().forEach(panel -> panel.getFieldsValues().forEach((metadata, valueDto) -> { + if (valueDto != null) { + result.put(metadata, valueDto); + } + })); + return result; + } + + public void addCustomizableFieldValueChangeListener(com.vaadin.data.HasValue.ValueChangeListener listener) { + getCustomizableFieldPanels().forEach(panel -> panel.addValueChangeListener(listener)); + } + + public void resetCustomizableFieldValues() { + getCustomizableFieldPanels().forEach(panel -> { + panel.setFieldsValues(getCustomizableFieldsValues()); + panel.updateFieldsDisplay(); + }); + } + public void addButtonListener(String componentId, Button.ClickListener listener) { Button button = (Button) getContent().getComponent(componentId); button.addClickListener(listener); @@ -1963,9 +2110,9 @@ private static class DiseaseChangeListener implements ValueChangeListener { private final AbstractSelect diseaseField; private final Disease currentDisease; - private final List fields; + private final List> fields; - DiseaseChangeListener(AbstractSelect diseaseField, Disease currentDisease, Field... fields) { + DiseaseChangeListener(AbstractSelect diseaseField, Disease currentDisease, Field... fields) { this.diseaseField = diseaseField; this.currentDisease = currentDisease; this.fields = Arrays.asList(fields); @@ -1984,11 +2131,9 @@ protected void onConfirm() { diseaseField.removeValueChangeListener(DiseaseChangeListener.this); fields.stream().forEach(field -> { if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - if (diseaseField.getValue().equals(Disease.TUBERCULOSIS) && field.getId().equals(CaseDataDto.POST_MORTEM)) { - field.setVisible(true); - } else { - field.setVisible(false); - } + final boolean isTuberculosisPostMortem = + diseaseField.getValue().equals(Disease.TUBERCULOSIS) && field.getId().equals(CaseDataDto.POST_MORTEM); + field.setVisible(isTuberculosisPostMortem); } }); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/AbstractConfigurationView.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/AbstractConfigurationView.java index 6b7391e6764..0fa78b1c6c4 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/AbstractConfigurationView.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/AbstractConfigurationView.java @@ -30,6 +30,7 @@ import de.symeda.sormas.ui.SubMenu; import de.symeda.sormas.ui.UiUtil; import de.symeda.sormas.ui.configuration.customizableenum.CustomizableEnumValuesView; +import de.symeda.sormas.ui.configuration.customizablefield.CustomizableFieldsView; import de.symeda.sormas.ui.configuration.disease.DiseaseConfigurationView; import de.symeda.sormas.ui.configuration.docgeneration.DocumentTemplatesView; import de.symeda.sormas.ui.configuration.docgeneration.emailtemplate.EmailTemplatesView; @@ -52,6 +53,7 @@ import de.symeda.sormas.ui.utils.DirtyStateComponent; import de.symeda.sormas.ui.utils.FieldHelper; +@SuppressWarnings("java:S110") // suppress sonar too many parents warning public abstract class AbstractConfigurationView extends AbstractSubNavigationView { private static final long serialVersionUID = 3193505016439327054L; @@ -123,12 +125,17 @@ public static Class registerViews(Navigator firstAccessibleView = firstAccessibleView != null ? firstAccessibleView : CustomizableEnumValuesView.class; } + if (UiUtil.permitted(UserRight.CUSTOMIZABLE_FIELD_MANAGEMENT)) { + navigator.addView(CustomizableFieldsView.VIEW_NAME, CustomizableFieldsView.class); + firstAccessibleView = firstAccessibleView != null ? firstAccessibleView : CustomizableFieldsView.class; + } + if (UiUtil.permitted(UserRight.DISEASE_MANAGEMENT)) { navigator.addView(DiseaseConfigurationView.VIEW_NAME, DiseaseConfigurationView.class); firstAccessibleView = firstAccessibleView != null ? firstAccessibleView : DiseaseConfigurationView.class; } - if(UiUtil.permitted(UserRight.SYSTEM_CONFIGURATION)) { + if (UiUtil.permitted(UserRight.SYSTEM_CONFIGURATION)) { navigator.addView(SystemConfigurationView.VIEW_NAME, SystemConfigurationView.class); firstAccessibleView = firstAccessibleView != null ? firstAccessibleView : SystemConfigurationView.class; } @@ -256,6 +263,14 @@ public void refreshMenu(SubMenu menu, String params) { false); } + if (UiUtil.permitted(UserRight.CUSTOMIZABLE_FIELD_MANAGEMENT)) { + menu.addView( + CustomizableFieldsView.VIEW_NAME, + I18nProperties.getPrefixCaption("View", CustomizableFieldsView.VIEW_NAME.replaceAll("/", ".") + ".short", ""), + null, + false); + } + if (UiUtil.permitted(UserRight.DISEASE_MANAGEMENT)) { menu.addView( DiseaseConfigurationView.VIEW_NAME, @@ -288,7 +303,7 @@ protected ComboBox addCountryFilter(Layout layout, Consumer changeHandler.accept(country); if (regionFilter != null) { - if (isServerCountry) { + if (Boolean.TRUE.equals(isServerCountry)) { FieldHelper.updateItems(regionFilter, FacadeProvider.getRegionFacade().getAllActiveByServerCountry()); } else { FieldHelper.updateItems(regionFilter, FacadeProvider.getRegionFacade().getAllActiveByCountry(country.getUuid())); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldEditForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldEditForm.java new file mode 100644 index 00000000000..e7c346365ce --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldEditForm.java @@ -0,0 +1,407 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.configuration.customizablefield; + +import static de.symeda.sormas.ui.utils.CssStyles.H3; +import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; +import static de.symeda.sormas.ui.utils.LayoutUtil.loc; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.vaadin.data.Binder; +import com.vaadin.data.BinderValidationStatus; +import com.vaadin.data.Converter; +import com.vaadin.data.Result; +import com.vaadin.data.ValidationException; +import com.vaadin.data.converter.StringToIntegerConverter; +import com.vaadin.data.validator.StringLengthValidator; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.CustomLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldCustomProperties; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldType; +import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityRestrictions; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Strings; +import de.symeda.sormas.api.i18n.Validations; +import de.symeda.sormas.ui.utils.CssStyles; +import de.symeda.sormas.ui.utils.components.CheckboxSet; + +/** + * Edit form for creating and editing customizable field metadata. + */ +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress missing equals +}) +public class CustomizableFieldEditForm extends CustomLayout { + + private static final long serialVersionUID = 1L; + + private static final String DISEASES_LOC = "diseasesLoc"; + private static final String BASICS_HEADING_LOC = "basicsHeadingLoc"; + private static final String PLACEMENT_HEADING_LOC = "placementHeadingLoc"; + private static final String BEHAVIOR_HEADING_LOC = "behaviorHeadingLoc"; + private static final String VISIBILITY_HEADING_LOC = "visibilityHeadingLoc"; + private static final String OPTIONS_HEADING_LOC = "optionsHeadingLoc"; + private static final String OPTIONS_LOC = "optionsLoc"; + private static final String TRANSLATIONS_HEADING_LOC = "translationsHeadingLoc"; + private static final String TRANSLATIONS_LOC = "translationsLoc"; + + private static final Set OPTIONS_TYPES = + EnumSet.of(CustomizableFieldType.COMBOBOX, CustomizableFieldType.CHECKBOX_LIST, CustomizableFieldType.RADIO_BUTTON_LIST); + + //@formatter:off + private static final String HTML_LAYOUT = + loc(BASICS_HEADING_LOC) + + fluidRowLocs(CustomizableFieldMetadataDto.NAME, CustomizableFieldMetadataDto.FIELD_TYPE) + + fluidRowLocs(CustomizableFieldMetadataDto.DESCRIPTION) + + fluidRowLocs(CustomizableFieldMetadataDto.DEFAULT_VALUE) + + loc(OPTIONS_HEADING_LOC) + + fluidRowLocs(OPTIONS_LOC) + + loc(PLACEMENT_HEADING_LOC) + + fluidRowLocs(CustomizableFieldMetadataDto.CONTEXT_CLASS, CustomizableFieldMetadataDto.UI_GROUP) + + fluidRowLocs(CustomizableFieldMetadataDto.UI_LINE_POSITION, CustomizableFieldMetadataDto.UI_LINE_WEIGHT) + + loc(BEHAVIOR_HEADING_LOC) + + fluidRowLocs(CustomizableFieldMetadataDto.ACTIVE, CustomizableFieldMetadataDto.READ_ONLY) + + fluidRowLocs(CustomizableFieldMetadataDto.MANDATORY) + + loc(VISIBILITY_HEADING_LOC) + + fluidRowLocs(DISEASES_LOC) + + loc(TRANSLATIONS_HEADING_LOC) + + fluidRowLocs(TRANSLATIONS_LOC); + //@formatter:on + + private final Binder binder = new Binder<>(CustomizableFieldMetadataDto.class); + private final ComboBox groupCombo; + private final CheckboxSet cbsDiseases; + private final TextField defaultValueField; + private final ComboBox defaultValueCombo; + private final CustomizableFieldOptionsComponent optionsComponent; + private final Label optionsHeading; + private final CustomizableFieldTranslationsComponent translationsComponent; + + private CustomizableFieldMetadataDto dto; + + public CustomizableFieldEditForm(boolean isEdit) { + + setTemplateContents(HTML_LAYOUT); + setWidth(840, Unit.PIXELS); + + // Section headings + Label basicsHeading = new Label(I18nProperties.getString(Strings.headingCustomizableFieldBasics)); + basicsHeading.addStyleName(H3); + addComponent(basicsHeading, BASICS_HEADING_LOC); + + Label placementHeading = new Label(I18nProperties.getString(Strings.headingCustomizableFieldPlacement)); + placementHeading.addStyleName(H3); + addComponent(placementHeading, PLACEMENT_HEADING_LOC); + + Label behaviorHeading = new Label(I18nProperties.getString(Strings.headingCustomizableFieldBehavior)); + behaviorHeading.addStyleName(H3); + addComponent(behaviorHeading, BEHAVIOR_HEADING_LOC); + + // --- BASICS --- + + TextField nameField = new TextField(caption(CustomizableFieldMetadataDto.NAME)); + nameField.setWidth(100, Unit.PERCENTAGE); + nameField.setRequiredIndicatorVisible(true); + if (isEdit) { + nameField.setPlaceholder(caption(CustomizableFieldMetadataDto.NAME)); + } + addComponent(nameField, CustomizableFieldMetadataDto.NAME); + binder.forField(nameField) + .asRequired(I18nProperties.getValidationError(Validations.required, caption(CustomizableFieldMetadataDto.NAME))) + .withValidator(new StringLengthValidator(I18nProperties.getValidationError(Validations.textTooLong, 512), null, 512)) + .bind(CustomizableFieldMetadataDto::getName, CustomizableFieldMetadataDto::setName); + + ComboBox fieldTypeCombo = new ComboBox<>(caption(CustomizableFieldMetadataDto.FIELD_TYPE)); + fieldTypeCombo.setWidth(100, Unit.PERCENTAGE); + fieldTypeCombo.setEmptySelectionAllowed(false); + fieldTypeCombo.setItems(CustomizableFieldType.values()); + fieldTypeCombo.setItemCaptionGenerator(I18nProperties::getEnumCaption); + fieldTypeCombo.setRequiredIndicatorVisible(true); + if (isEdit) { + fieldTypeCombo.setEnabled(false); + } + addComponent(fieldTypeCombo, CustomizableFieldMetadataDto.FIELD_TYPE); + binder.forField(fieldTypeCombo) + .asRequired(I18nProperties.getValidationError(Validations.required, caption(CustomizableFieldMetadataDto.FIELD_TYPE))) + .bind(CustomizableFieldMetadataDto::getFieldType, CustomizableFieldMetadataDto::setFieldType); + fieldTypeCombo.addValueChangeListener(e -> updateOptionsVisibility(e.getValue())); + + TextField descriptionField = new TextField(caption(CustomizableFieldMetadataDto.DESCRIPTION)); + descriptionField.setWidth(100, Unit.PERCENTAGE); + addComponent(descriptionField, CustomizableFieldMetadataDto.DESCRIPTION); + binder.forField(descriptionField) + .withNullRepresentation("") + .bind(CustomizableFieldMetadataDto::getDescription, CustomizableFieldMetadataDto::setDescription); + + String defaultValueCaption = caption(CustomizableFieldMetadataDto.DEFAULT_VALUE); + defaultValueField = new TextField(defaultValueCaption); + defaultValueField.setWidth(100, Unit.PERCENTAGE); + addComponent(defaultValueField, CustomizableFieldMetadataDto.DEFAULT_VALUE); + binder.forField(defaultValueField) + .withNullRepresentation("") + .bind(CustomizableFieldMetadataDto::getDefaultValue, CustomizableFieldMetadataDto::setDefaultValue); + + defaultValueCombo = new ComboBox<>(defaultValueCaption); + defaultValueCombo.setWidth(100, Unit.PERCENTAGE); + defaultValueCombo.setEmptySelectionAllowed(true); + + optionsHeading = new Label(I18nProperties.getString(Strings.labelCustomizableFieldOptions)); + optionsHeading.addStyleName(CssStyles.VAADIN_CAPTION); + optionsHeading.setVisible(false); + addComponent(optionsHeading, OPTIONS_HEADING_LOC); + + optionsComponent = new CustomizableFieldOptionsComponent(); + optionsComponent.setVisible(false); + addComponent(optionsComponent, OPTIONS_LOC); + optionsComponent.addOptionsChangeListener(() -> { + List current = optionsComponent.getValue(); + String selected = defaultValueCombo.getValue(); + defaultValueCombo.setItems(current != null ? current : Collections.emptyList()); + defaultValueCombo.setValue(current != null && current.contains(selected) ? selected : null); + }); + + // --- PLACEMENT --- + + ComboBox contextCombo = new ComboBox<>(caption(CustomizableFieldMetadataDto.CONTEXT_CLASS)); + contextCombo.setWidth(100, Unit.PERCENTAGE); + contextCombo.setEmptySelectionAllowed(false); + contextCombo.setItems(CustomizableFieldContext.values()); + contextCombo.setItemCaptionGenerator(I18nProperties::getEnumCaption); + contextCombo.setRequiredIndicatorVisible(true); + addComponent(contextCombo, CustomizableFieldMetadataDto.CONTEXT_CLASS); + binder.forField(contextCombo) + .asRequired(I18nProperties.getValidationError(Validations.required, caption(CustomizableFieldMetadataDto.CONTEXT_CLASS))) + .bind(CustomizableFieldMetadataDto::getContextClass, CustomizableFieldMetadataDto::setContextClass); + + groupCombo = new ComboBox<>(caption(CustomizableFieldMetadataDto.UI_GROUP)); + groupCombo.setWidth(100, Unit.PERCENTAGE); + groupCombo.setEmptySelectionAllowed(true); + groupCombo.setItemCaptionGenerator(I18nProperties::getEnumCaption); + addComponent(groupCombo, CustomizableFieldMetadataDto.UI_GROUP); + binder.forField(groupCombo).bind(CustomizableFieldMetadataDto::getUiGroup, CustomizableFieldMetadataDto::setUiGroup); + + // Repopulate group items when context changes; clear value first, then retain only if still valid + contextCombo.addValueChangeListener(e -> { + CustomizableFieldContext ctx = e.getValue(); + CustomizableFieldGroup current = groupCombo.getValue(); + groupCombo.setValue(null); + if (ctx != null) { + groupCombo.setItems(CustomizableFieldGroup.getGroupsForContext(ctx)); + if (current != null && current.getContext() == ctx) { + groupCombo.setValue(current); + } + } else { + groupCombo.setItems(); + } + }); + + String posCaption = caption(CustomizableFieldMetadataDto.UI_LINE_POSITION); + TextField positionField = new TextField(posCaption); + positionField.setWidth(100, Unit.PERCENTAGE); + addComponent(positionField, CustomizableFieldMetadataDto.UI_LINE_POSITION); + binder.forField(positionField) + .withNullRepresentation("") + .withConverter(new StringToIntegerConverter(I18nProperties.getValidationError(Validations.onlyIntegerNumbersAllowed, posCaption))) + .bind(CustomizableFieldMetadataDto::getUiLinePosition, CustomizableFieldMetadataDto::setUiLinePosition); + + String weightCaption = caption(CustomizableFieldMetadataDto.UI_LINE_WEIGHT); + TextField weightField = new TextField(weightCaption); + weightField.setWidth(100, Unit.PERCENTAGE); + addComponent(weightField, CustomizableFieldMetadataDto.UI_LINE_WEIGHT); + binder.forField(weightField) + .withNullRepresentation("") + .withConverter(stringToFloatConverter(I18nProperties.getValidationError(Validations.onlyNumbersAllowed, weightCaption))) + .bind(CustomizableFieldMetadataDto::getUiLineWeight, CustomizableFieldMetadataDto::setUiLineWeight); + + // --- BEHAVIOR --- + + CheckBox activeBox = new CheckBox(caption(CustomizableFieldMetadataDto.ACTIVE)); + addComponent(activeBox, CustomizableFieldMetadataDto.ACTIVE); + binder.forField(activeBox).bind(d -> d.isActive(), (d, v) -> d.setActive(Boolean.TRUE.equals(v))); + + CheckBox readOnlyBox = new CheckBox(caption(CustomizableFieldMetadataDto.READ_ONLY)); + addComponent(readOnlyBox, CustomizableFieldMetadataDto.READ_ONLY); + binder.forField(readOnlyBox).bind(d -> d.isReadOnly(), (d, v) -> d.setReadOnly(Boolean.TRUE.equals(v))); + + CheckBox mandatoryBox = new CheckBox(caption(CustomizableFieldMetadataDto.MANDATORY)); + addComponent(mandatoryBox, CustomizableFieldMetadataDto.MANDATORY); + binder.forField(mandatoryBox).bind(d -> d.isMandatory(), (d, v) -> d.setMandatory(Boolean.TRUE.equals(v))); + + // --- VISIBILITY --- + + Label visibilityHeading = new Label(I18nProperties.getString(Strings.headingCustomizableFieldVisibility)); + visibilityHeading.addStyleName(H3); + addComponent(visibilityHeading, VISIBILITY_HEADING_LOC); + + cbsDiseases = new CheckboxSet<>(); + cbsDiseases.setCaption( + I18nProperties + .getPrefixCaption(CustomizableFieldMetadataDto.I18N_PREFIX, CustomizableFieldMetadataDto.VISIBILITY_RESTRICTIONS + "Diseases")); + cbsDiseases.setItems(FacadeProvider.getDiseaseConfigurationFacade().getAllDiseases(true, true, true), null, null); + cbsDiseases.setColumnCount(3); + addComponent(cbsDiseases, DISEASES_LOC); + + // --- TRANSLATIONS --- + + Label translationsHeading = new Label(I18nProperties.getString(Strings.headingCustomizableFieldTranslations)); + translationsHeading.addStyleName(H3); + addComponent(translationsHeading, TRANSLATIONS_HEADING_LOC); + + translationsComponent = new CustomizableFieldTranslationsComponent(); + addComponent(translationsComponent, TRANSLATIONS_LOC); + } + + private void updateOptionsVisibility(CustomizableFieldType type) { + boolean visible = OPTIONS_TYPES.contains(type); + optionsHeading.setVisible(visible); + optionsComponent.setVisible(visible); + if (visible) { + addComponent(defaultValueCombo, CustomizableFieldMetadataDto.DEFAULT_VALUE); + } else { + optionsComponent.setValue(null); + defaultValueCombo.clear(); + addComponent(defaultValueField, CustomizableFieldMetadataDto.DEFAULT_VALUE); + } + } + + @SuppressWarnings("java:S3776") + public void setValue(CustomizableFieldMetadataDto newDto) { + + this.dto = newDto; + + // Pre-populate group items before readBean so the binder can find the bound value + CustomizableFieldContext ctx = newDto != null ? newDto.getContextClass() : null; + if (ctx != null) { + groupCombo.setItems(CustomizableFieldGroup.getGroupsForContext(ctx)); + } else { + groupCombo.setItems(); + } + + binder.readBean(newDto); + + Set diseases = + newDto != null && newDto.getVisibilityRestrictions() != null && newDto.getVisibilityRestrictions().getDiseases() != null + ? new HashSet<>(newDto.getVisibilityRestrictions().getDiseases()) + : null; + cbsDiseases.setValue(diseases); + + CustomizableFieldType type = newDto != null ? newDto.getFieldType() : null; + updateOptionsVisibility(type); + if (newDto != null && OPTIONS_TYPES.contains(type)) { + List options = newDto.getCustomProperties() != null ? newDto.getCustomProperties().getOptions() : null; + optionsComponent.setValue(options); + defaultValueCombo.setItems(options != null ? options : Collections.emptyList()); + defaultValueCombo.setValue(options != null && options.contains(newDto.getDefaultValue()) ? newDto.getDefaultValue() : null); + } + + translationsComponent.setValue(newDto != null ? newDto.getTranslations() : null); + } + + /** + * Writes the binder values to the DTO and returns it. + * Returns {@code null} if binder validation fails (errors are shown inline on the fields). + */ + @SuppressWarnings("java:S3776") + public CustomizableFieldMetadataDto getValue() { + + if (dto == null) { + return null; + } + try { + binder.writeBean(dto); + } catch (ValidationException e) { + return null; + } + // Options, diseases and translations are outside the binder — write manually + if (OPTIONS_TYPES.contains(dto.getFieldType())) { + dto.setDefaultValue(defaultValueCombo.getValue()); + List options = optionsComponent.getValue(); + CustomizableFieldCustomProperties props = dto.getCustomProperties(); + if (options != null && !options.isEmpty()) { + if (props == null) { + props = new CustomizableFieldCustomProperties(); + dto.setCustomProperties(props); + } + props.setOptions(options); + } else if (props != null) { + props.setOptions(null); + } + } + dto.setTranslations(translationsComponent.getValue()); + + Set selectedDiseases = cbsDiseases.getValue(); + if (selectedDiseases != null && !selectedDiseases.isEmpty()) { + CustomizableFieldVisibilityRestrictions restrictions = dto.getVisibilityRestrictions(); + if (restrictions == null) { + restrictions = new CustomizableFieldVisibilityRestrictions(); + } + restrictions.setDiseases(new ArrayList<>(selectedDiseases)); + dto.setVisibilityRestrictions(restrictions); + } else { + if (dto.getVisibilityRestrictions() != null) { + dto.getVisibilityRestrictions().setDiseases(null); + } + } + return dto; + } + + /** Returns the binder validation status without writing to the DTO. */ + public BinderValidationStatus validate() { + return binder.validate(); + } + + private String caption(String propertyId) { + return I18nProperties.getPrefixCaption(CustomizableFieldMetadataDto.I18N_PREFIX, propertyId); + } + + private static Converter stringToFloatConverter(String errorMessage) { + return new Converter() { + + @Override + public Result convertToModel(String value, com.vaadin.data.ValueContext context) { + if (value == null || value.trim().isEmpty()) { + return Result.ok(null); + } + try { + return Result.ok(Float.parseFloat(value.trim())); + } catch (NumberFormatException e) { + return Result.error(errorMessage); + } + } + + @Override + public String convertToPresentation(Float value, com.vaadin.data.ValueContext context) { + return value == null ? "" : value.toString(); + } + }; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldOptionsComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldOptionsComponent.java new file mode 100644 index 00000000000..1546964e66a --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldOptionsComponent.java @@ -0,0 +1,195 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.configuration.customizablefield; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.icons.VaadinIcons; +import com.vaadin.shared.ui.MarginInfo; +import com.vaadin.ui.Button; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Strings; +import de.symeda.sormas.ui.utils.ButtonHelper; +import de.symeda.sormas.ui.utils.CssStyles; + +/** + * Vaadin 8 component for editing the selectable options of list-type customizable fields + * ({@code COMBOBOX}, {@code CHECKBOX_LIST}, {@code RADIO_BUTTON_LIST}). + *

+ * Renders one row per option: [option value text field] [🗑] + * Reads/writes {@code List}. + */ +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress missing equals +}) +public class CustomizableFieldOptionsComponent extends VerticalLayout { + + private static final long serialVersionUID = 1L; + + private final VerticalLayout rowsLayout; + private final List rows = new ArrayList<>(); + private final List changeListeners = new ArrayList<>(); + private final Label lblNoOptions; + + public CustomizableFieldOptionsComponent() { + + setWidthFull(); + setMargin(new MarginInfo(false, false, true, false)); + setSpacing(false); + CssStyles.style(this, CssStyles.VSPACE_TOP_4); + + lblNoOptions = new Label(I18nProperties.getString(Strings.infoNoCustomizableFieldOptions)); + addComponent(lblNoOptions); + + rowsLayout = new VerticalLayout(); + rowsLayout.setWidthFull(); + rowsLayout.setMargin(false); + rowsLayout.setSpacing(false); + addComponent(rowsLayout); + + Button btnAdd = ButtonHelper.createIconButtonWithCaption(null, null, VaadinIcons.PLUS, e -> addRow(null, true), CssStyles.VSPACE_TOP_5); + btnAdd.setHeight(25, Unit.PIXELS); + btnAdd.setWidthFull(); + addComponent(btnAdd); + } + + public void setValue(List options) { + + rows.clear(); + rowsLayout.removeAllComponents(); + + if (options != null) { + options.forEach(option -> addRow(option, false)); + rows.forEach(rowsLayout::addComponent); + } + + updateNoOptionsLabelVisibility(); + } + + /** + * Returns the current list of options, or {@code null} if there are none. + * Blank entries are skipped. + */ + @SuppressWarnings("java:S1168") + public List getValue() { + + if (rows.isEmpty()) { + return null; + } + List result = new ArrayList<>(); + for (OptionRow row : rows) { + String value = row.getValue(); + if (value != null && !value.trim().isEmpty()) { + result.add(value.trim()); + } + } + return result.isEmpty() ? null : result; + } + + /** + * Registers a listener that is called whenever the options list changes + * (row added, deleted, or option text edited). + */ + public void addOptionsChangeListener(Runnable listener) { + changeListeners.add(listener); + } + + private void fireChangeListeners() { + changeListeners.forEach(Runnable::run); + } + + private void addRow(String value, boolean render) { + + OptionRow row = new OptionRow(value); + row.setDeleteCallback(() -> { + rows.remove(row); + rowsLayout.removeComponent(row); + updateNoOptionsLabelVisibility(); + fireChangeListeners(); + }); + row.setChangeCallback(this::fireChangeListeners); + rows.add(row); + updateNoOptionsLabelVisibility(); + if (render) { + rowsLayout.addComponent(row); + fireChangeListeners(); + } + } + + private void updateNoOptionsLabelVisibility() { + + if (lblNoOptions != null) { + lblNoOptions.setVisible(rows.isEmpty()); + } + } + + @SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress missing equals + }) + private static final class OptionRow extends HorizontalLayout { + + private static final long serialVersionUID = 1L; + + private final TextField tfValue; + private transient Runnable deleteCallback; + private transient Runnable changeCallback; + + public OptionRow(String value) { + + tfValue = new TextField(); + tfValue.setWidthFull(); + tfValue.setPlaceholder(I18nProperties.getString(Strings.promptCustomizableFieldOption)); + if (value != null) { + tfValue.setValue(value); + } + tfValue.addValueChangeListener(e -> { + if (changeCallback != null) { + changeCallback.run(); + } + }); + + CssStyles.style(CssStyles.VSPACE_NONE, tfValue); + + Button btnDelete = ButtonHelper.createIconButtonWithCaption(null, null, VaadinIcons.TRASH, e -> deleteCallback.run()); + + addComponent(tfValue); + addComponent(btnDelete); + setExpandRatio(tfValue, 1); + setWidthFull(); + setMargin(false); + } + + public String getValue() { + return tfValue.getValue(); + } + + public void setDeleteCallback(Runnable callback) { + this.deleteCallback = callback; + } + + public void setChangeCallback(Runnable callback) { + this.changeCallback = callback; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldTranslationsComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldTranslationsComponent.java new file mode 100644 index 00000000000..17f7070a0a7 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldTranslationsComponent.java @@ -0,0 +1,228 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.configuration.customizablefield; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.vaadin.icons.VaadinIcons; +import com.vaadin.shared.ui.MarginInfo; +import com.vaadin.ui.Button; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +import de.symeda.sormas.api.Language; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Strings; +import de.symeda.sormas.ui.utils.ButtonHelper; +import de.symeda.sormas.ui.utils.CssStyles; + +/** + * Vaadin 8 component for editing per-language translations of a customizable field's name and description. + * Renders one row per language: [Language ▾] [Name] [Description] [🗑] + * Reads/writes {@code Map>}. + */ +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress sonar missing equals +}) +public class CustomizableFieldTranslationsComponent extends VerticalLayout { + + private static final long serialVersionUID = 1L; + + private final VerticalLayout rowsLayout; + private final List rows = new ArrayList<>(); + private final Label lblNoTranslations; + + public CustomizableFieldTranslationsComponent() { + + setWidthFull(); + setMargin(new MarginInfo(false, false, true, false)); + setSpacing(false); + CssStyles.style(this, CssStyles.VSPACE_TOP_4); + + lblNoTranslations = new Label(I18nProperties.getString(Strings.infoNoCustomizableFieldTranslations)); + addComponent(lblNoTranslations); + + rowsLayout = new VerticalLayout(); + rowsLayout.setWidthFull(); + rowsLayout.setMargin(false); + rowsLayout.setSpacing(false); + addComponent(rowsLayout); + + Button btnAdd = + ButtonHelper.createIconButtonWithCaption(null, null, VaadinIcons.PLUS, e -> addRow(null, null, null, true), CssStyles.VSPACE_TOP_5); + btnAdd.setHeight(25, Unit.PIXELS); + btnAdd.setWidthFull(); + addComponent(btnAdd); + } + + public void setValue(Map> translations) { + + rows.clear(); + rowsLayout.removeAllComponents(); + + if (translations != null) { + translations.forEach((localeStr, translationMap) -> { + Language lang = Language.fromLocaleString(localeStr); + String name = translationMap != null ? translationMap.get(CustomizableFieldMetadataDto.NAME) : null; + String description = translationMap != null ? translationMap.get(CustomizableFieldMetadataDto.DESCRIPTION) : null; + addRow(lang, name, description, false); + }); + rows.forEach(rowsLayout::addComponent); + } + + updateNoTranslationsLabelVisibility(); + } + + /** + * Returns the current translations, or {@code null} if there are none. + * Rows with no language selected or no values filled in are skipped. + */ + @SuppressWarnings("java:S1168") // suppress "return empty map instead of null" warning + public Map> getValue() { + + if (rows.isEmpty()) { + // we could return a empty map but it might be interpreted as no translaton and postgres might create a '{}' jsonb entry + return null; + } + Map> result = new HashMap<>(); + for (TranslationRow row : rows) { + Language lang = row.getLanguage(); + if (lang == null) { + continue; + } + Map entry = new HashMap<>(); + String name = row.getName(); + String description = row.getDescription(); + if (name != null && !name.trim().isEmpty()) { + entry.put(CustomizableFieldMetadataDto.NAME, name.trim()); + } + if (description != null && !description.trim().isEmpty()) { + entry.put(CustomizableFieldMetadataDto.DESCRIPTION, description.trim()); + } + if (!entry.isEmpty()) { + result.put(lang.getLocale().toString(), entry); + } + } + return result.isEmpty() ? null : result; + } + + /** Returns {@code true} if any two rows share the same language. */ + public boolean hasDuplicateLanguages() { + + Set seen = new HashSet<>(); + return rows.stream().map(TranslationRow::getLanguage).filter(l -> l != null).anyMatch(l -> !seen.add(l)); + } + + private void addRow(Language language, String name, String description, boolean render) { + + TranslationRow row = new TranslationRow(language, name, description); + row.setDeleteCallback(() -> { + rows.remove(row); + rowsLayout.removeComponent(row); + updateNoTranslationsLabelVisibility(); + }); + rows.add(row); + updateNoTranslationsLabelVisibility(); + if (render) { + rowsLayout.addComponent(row); + } + } + + private void updateNoTranslationsLabelVisibility() { + + if (lblNoTranslations != null) { + lblNoTranslations.setVisible(rows.isEmpty()); + } + } + + private static final class TranslationRow extends HorizontalLayout { + + private static final long serialVersionUID = 1L; + + private final ComboBox cbLanguage; + private final TextField tfName; + private final TextField tfDescription; + private transient Runnable deleteCallback; + + public TranslationRow(Language language, String name, String description) { + + cbLanguage = new ComboBox<>(); + cbLanguage.setItems(Language.values()); + cbLanguage.setItemCaptionGenerator(Language::toString); + cbLanguage.setWidth(220, Unit.PIXELS); + cbLanguage.setPlaceholder(I18nProperties.getString(Strings.promptCustomizableEnumTranslationLanguage)); + if (language != null) { + cbLanguage.setValue(language); + } + + tfName = new TextField(); + tfName.setWidthFull(); + tfName.setPlaceholder(I18nProperties.getPrefixCaption(CustomizableFieldMetadataDto.I18N_PREFIX, CustomizableFieldMetadataDto.NAME)); + if (name != null) { + tfName.setValue(name); + } + + tfDescription = new TextField(); + tfDescription.setWidthFull(); + tfDescription + .setPlaceholder(I18nProperties.getPrefixCaption(CustomizableFieldMetadataDto.I18N_PREFIX, CustomizableFieldMetadataDto.DESCRIPTION)); + if (description != null) { + tfDescription.setValue(description); + } + + CssStyles.style(CssStyles.VSPACE_NONE, cbLanguage, tfName, tfDescription); + + Button btnDelete = ButtonHelper.createIconButtonWithCaption(null, null, VaadinIcons.TRASH, e -> deleteCallback.run()); + + addComponent(cbLanguage); + addComponent(tfName); + addComponent(tfDescription); + addComponent(btnDelete); + setExpandRatio(tfName, 1); + setExpandRatio(tfDescription, 1); + setWidthFull(); + setMargin(false); + CssStyles.style(this, CssStyles.VSPACE_4); + } + + public Language getLanguage() { + return cbLanguage.getValue(); + } + + public String getName() { + return tfName.getValue(); + } + + @Override + public String getDescription() { + return tfDescription.getValue(); + } + + public void setDeleteCallback(Runnable deleteCallback) { + this.deleteCallback = deleteCallback; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsController.java new file mode 100644 index 00000000000..e676a8c8801 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsController.java @@ -0,0 +1,148 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.configuration.customizablefield; + +import com.vaadin.ui.Notification; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.i18n.Captions; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Strings; +import de.symeda.sormas.ui.SormasUI; +import de.symeda.sormas.ui.utils.CommitDiscardWrapperComponent; +import de.symeda.sormas.ui.utils.DeletableUtils; +import de.symeda.sormas.ui.utils.VaadinUiUtil; + +/** + * Controller for customizable field metadata CRUD operations. + */ +public class CustomizableFieldsController { + + /** + * Open the create dialog for a new customizable field. + */ + public void createField() { + + CustomizableFieldMetadataDto dto = new CustomizableFieldMetadataDto(); + dto.setActive(true); + + CustomizableFieldEditForm form = new CustomizableFieldEditForm(false); + form.setValue(dto); + + final CommitDiscardWrapperComponent cdw = new CommitDiscardWrapperComponent<>(form); + + // Validate via Vaadin 8 Binder before allowing commit to proceed + cdw.setPreCommitListener(successCallback -> { + if (form.validate().isOk()) { + successCallback.run(); + } + }); + + cdw.addCommitListener(() -> { + FacadeProvider.getCustomizableFieldMetadataFacade().save(form.getValue()); + Notification.show(I18nProperties.getString(Strings.messageCustomizableFieldCreated), Notification.Type.ASSISTIVE_NOTIFICATION); + SormasUI.get().getNavigator().navigateTo(CustomizableFieldsView.VIEW_NAME); + }); + + VaadinUiUtil.showModalPopupWindow(cdw, I18nProperties.getString(Strings.headingCreateCustomizableField)); + } + + /** + * Open the edit dialog for an existing customizable field. + */ + public void editField(String uuid) { + + CustomizableFieldMetadataDto dto = FacadeProvider.getCustomizableFieldMetadataFacade().getByUuid(uuid); + + CustomizableFieldEditForm form = new CustomizableFieldEditForm(true); + form.setValue(dto); + + final CommitDiscardWrapperComponent cdw = new CommitDiscardWrapperComponent<>(form); + + // Validate via Vaadin 8 Binder before allowing commit to proceed + cdw.setPreCommitListener(successCallback -> { + if (form.validate().isOk()) { + successCallback.run(); + } + }); + + cdw.addCommitListener(() -> { + FacadeProvider.getCustomizableFieldMetadataFacade().save(form.getValue()); + Notification.show(I18nProperties.getString(Strings.messageCustomizableFieldSaved), Notification.Type.ASSISTIVE_NOTIFICATION); + SormasUI.get().getNavigator().navigateTo(CustomizableFieldsView.VIEW_NAME); + }); + + VaadinUiUtil.showModalPopupWindow(cdw, I18nProperties.getString(Strings.headingEditCustomizableField) + " - " + dto.getName()); + } + + /** + * Show a dialog to clone the given field with a new name. + */ + public void cloneField(CustomizableFieldMetadataDto sourceDto) { + + TextField nameField = + new TextField(I18nProperties.getPrefixCaption(CustomizableFieldMetadataDto.I18N_PREFIX, CustomizableFieldMetadataDto.NAME)); + nameField.setWidth(280, com.vaadin.server.Sizeable.Unit.PIXELS); + nameField.setRequiredIndicatorVisible(true); + + VerticalLayout content = new VerticalLayout(); + content.setMargin(true); + content.setSpacing(true); + content.addComponent(nameField); + + VaadinUiUtil.showConfirmationPopup( + I18nProperties.getString(Strings.headingCloneCustomizableField), + content, + I18nProperties.getCaption(Captions.actionClone), + I18nProperties.getCaption(Captions.actionCancel), + result -> { + if (Boolean.TRUE.equals(result)) { + String newName = nameField.getValue(); + if (newName == null || newName.trim().isEmpty()) { + Notification.show(I18nProperties.getCaption(Captions.actionClone) + " - name required", Notification.Type.WARNING_MESSAGE); + return; + } + FacadeProvider.getCustomizableFieldMetadataFacade().cloneField(sourceDto.getUuid(), newName.trim()); + Notification.show(I18nProperties.getString(Strings.messageCustomizableFieldCloned), Notification.Type.ASSISTIVE_NOTIFICATION); + SormasUI.get().getNavigator().navigateTo(CustomizableFieldsView.VIEW_NAME); + } + }); + } + + /** + * Toggle the active state of a customizable field. + */ + public void toggleActive(CustomizableFieldMetadataDto dto) { + + FacadeProvider.getCustomizableFieldMetadataFacade().setFieldActive(dto.getUuid(), !dto.isActive()); + SormasUI.get().getNavigator().navigateTo(CustomizableFieldsView.VIEW_NAME); + } + + /** + * Show a delete confirmation and delete if confirmed. + */ + public void deleteField(String uuid, CustomizableFieldsGrid grid) { + + DeletableUtils.showDeleteWithReasonPopup(I18nProperties.getString(Strings.promptConfirmDeleteCustomizableField), deletionDetails -> { + FacadeProvider.getCustomizableFieldMetadataFacade().delete(uuid, deletionDetails); + Notification.show(I18nProperties.getString(Strings.messageCustomizableFieldDeleted), Notification.Type.ASSISTIVE_NOTIFICATION); + grid.reload(); + }); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsGrid.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsGrid.java new file mode 100644 index 00000000000..279ac3bd7b8 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsGrid.java @@ -0,0 +1,94 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.configuration.customizablefield; + +import com.vaadin.icons.VaadinIcons; +import com.vaadin.ui.renderers.HtmlRenderer; + +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataCriteria; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.i18n.Captions; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.ui.ControllerProvider; +import de.symeda.sormas.ui.utils.FilteredGrid; +import de.symeda.sormas.ui.utils.ShowDetailsListener; + +/** + * Grid for displaying and managing customizable field metadata. + */ +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress missing equals +}) +public class CustomizableFieldsGrid extends FilteredGrid { + + private static final long serialVersionUID = 1L; + + private static final String CLONE_COLUMN_ID = "clone"; + private static final String TOGGLE_ACTIVE_COLUMN_ID = "toggleActive"; + private static final String DELETE_COLUMN_ID = "delete"; + + public CustomizableFieldsGrid(CustomizableFieldMetadataCriteria criteria) { + + super(CustomizableFieldMetadataDto.class); + setSizeFull(); + + setLazyDataProvider( + FacadeProvider.getCustomizableFieldMetadataFacade()::getIndexList, + FacadeProvider.getCustomizableFieldMetadataFacade()::count); + setCriteria(criteria); + + setColumns( + CustomizableFieldMetadataDto.NAME, + CustomizableFieldMetadataDto.CONTEXT_CLASS, + CustomizableFieldMetadataDto.UI_GROUP, + CustomizableFieldMetadataDto.FIELD_TYPE, + CustomizableFieldMetadataDto.ACTIVE, + CustomizableFieldMetadataDto.READ_ONLY); + + addEditColumn(e -> ControllerProvider.getCustomizableFieldsController().editField(e.getUuid())); + + addColumn(e -> VaadinIcons.COPY.getHtml(), new HtmlRenderer()).setId(CLONE_COLUMN_ID) + .setCaption(I18nProperties.getCaption(Captions.actionClone)) + .setSortable(false) + .setWidth(40); + + addColumn(e -> e.isActive() ? VaadinIcons.CLOSE.getHtml() : VaadinIcons.CHECK.getHtml(), new HtmlRenderer()).setId(TOGGLE_ACTIVE_COLUMN_ID) + .setCaption(I18nProperties.getCaption(Captions.actionEnable) + "/" + I18nProperties.getCaption(Captions.actionDisable)) + .setSortable(false) + .setWidth(55); + + addColumn(e -> VaadinIcons.TRASH.getHtml(), new HtmlRenderer()).setId(DELETE_COLUMN_ID) + .setCaption(I18nProperties.getCaption(Captions.actionDelete)) + .setSortable(false) + .setWidth(40); + + addItemClickListener(new ShowDetailsListener<>(CLONE_COLUMN_ID, e -> ControllerProvider.getCustomizableFieldsController().cloneField(e))); + addItemClickListener( + new ShowDetailsListener<>(TOGGLE_ACTIVE_COLUMN_ID, e -> ControllerProvider.getCustomizableFieldsController().toggleActive(e))); + addItemClickListener( + new ShowDetailsListener<>(DELETE_COLUMN_ID, e -> ControllerProvider.getCustomizableFieldsController().deleteField(e.getUuid(), this))); + + for (Column column : getColumns()) { + column.setCaption(I18nProperties.getPrefixCaption(CustomizableFieldMetadataDto.I18N_PREFIX, column.getId(), column.getCaption())); + } + } + + public void reload() { + getDataProvider().refreshAll(); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsView.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsView.java new file mode 100644 index 00000000000..17fdcb76991 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/customizablefield/CustomizableFieldsView.java @@ -0,0 +1,185 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * 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.ui.configuration.customizablefield; + +import java.util.Arrays; +import java.util.Objects; + +import com.vaadin.navigator.ViewChangeListener; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.VerticalLayout; + +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataCriteria; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldType; +import de.symeda.sormas.api.i18n.Captions; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Strings; +import de.symeda.sormas.api.utils.criteria.BaseCriteria; +import de.symeda.sormas.ui.ControllerProvider; +import de.symeda.sormas.ui.ViewModelProviders; +import de.symeda.sormas.ui.configuration.AbstractConfigurationView; +import de.symeda.sormas.ui.configuration.infrastructure.components.SearchField; +import de.symeda.sormas.ui.utils.ButtonHelper; +import de.symeda.sormas.ui.utils.CssStyles; + +/** + * Admin view for managing customizable field metadata. + */ +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress sonar missing equals +}) +public class CustomizableFieldsView extends AbstractConfigurationView { + + public static final String VIEW_NAME = ROOT_VIEW_NAME + "/customizableFields"; + + private final CustomizableFieldMetadataCriteria criteria; + private final CustomizableFieldsGrid grid; + + private SearchField searchField; + private ComboBox contextFilter; + private ComboBox fieldTypeFilter; + private ComboBox activeFilter; + + public CustomizableFieldsView() { + + super(VIEW_NAME); + + criteria = + ViewModelProviders.of(CustomizableFieldsView.class).get(CustomizableFieldMetadataCriteria.class, new CustomizableFieldMetadataCriteria()); + grid = new CustomizableFieldsGrid(criteria); + + final VerticalLayout gridLayout = new VerticalLayout(); + gridLayout.addComponent(createFilterBar()); + gridLayout.addComponent(grid); + gridLayout.setMargin(true); + gridLayout.setSpacing(false); + gridLayout.setExpandRatio(grid, 1); + gridLayout.setSizeFull(); + gridLayout.setStyleName("crud-main-layout"); + + addComponent(gridLayout); + } + + private HorizontalLayout createFilterBar() { + + final HorizontalLayout filterLayout = new HorizontalLayout(); + filterLayout.setMargin(false); + filterLayout.setSpacing(true); + + searchField = new SearchField(); + searchField.setInputPrompt(I18nProperties.getString(Strings.promptCustomizableFieldSearchField)); + searchField.addTextChangeListener(e -> { + criteria.freeTextFilter(e.getText()); + grid.reload(); + }); + filterLayout.addComponent(searchField); + + contextFilter = new ComboBox<>( + I18nProperties.getPrefixCaption(CustomizableFieldMetadataDto.I18N_PREFIX, CustomizableFieldMetadataDto.CONTEXT_CLASS), + Arrays.asList(CustomizableFieldContext.values())); + contextFilter.setItemCaptionGenerator(Objects::toString); + contextFilter.addValueChangeListener(e -> { + criteria.setContextClass(e.getValue()); + grid.reload(); + }); + filterLayout.addComponent(contextFilter); + + fieldTypeFilter = new ComboBox<>( + I18nProperties.getPrefixCaption(CustomizableFieldMetadataDto.I18N_PREFIX, CustomizableFieldMetadataDto.FIELD_TYPE), + Arrays.asList(CustomizableFieldType.values())); + fieldTypeFilter.setItemCaptionGenerator(Objects::toString); + fieldTypeFilter.addValueChangeListener(e -> { + criteria.setFieldType(e.getValue()); + grid.reload(); + }); + filterLayout.addComponent(fieldTypeFilter); + + activeFilter = new ComboBox<>(I18nProperties.getPrefixCaption(CustomizableFieldMetadataDto.I18N_PREFIX, CustomizableFieldMetadataDto.ACTIVE)); + activeFilter.setItems( + I18nProperties.getCaption(Captions.customizableFieldsAllActive), + I18nProperties.getCaption(Captions.customizableFieldsActiveOnly), + I18nProperties.getCaption(Captions.customizableFieldsInactiveOnly)); + activeFilter.addValueChangeListener(e -> { + String val = e.getValue(); + if (val == null || val.equals(I18nProperties.getCaption(Captions.customizableFieldsAllActive))) { + criteria.setActive(null); + } else if (val.equals(I18nProperties.getCaption(Captions.customizableFieldsActiveOnly))) { + criteria.setActive(Boolean.TRUE); + } else { + criteria.setActive(Boolean.FALSE); + } + grid.reload(); + }); + filterLayout.addComponent(activeFilter); + + filterLayout.addComponent(ButtonHelper.createButton(Captions.actionResetFilters, event -> { + ViewModelProviders.of(CustomizableFieldsView.class).remove(CustomizableFieldMetadataCriteria.class); + navigateTo((BaseCriteria[]) null); + }, CssStyles.FORCE_CAPTION)); + + filterLayout.addComponent( + ButtonHelper.createButton( + Captions.actionCreate, + event -> ControllerProvider.getCustomizableFieldsController().createField(), + CssStyles.FORCE_CAPTION)); + + return filterLayout; + } + + @Override + public void enter(ViewChangeListener.ViewChangeEvent event) { + + super.enter(event); + String params = event.getParameters().trim(); + if (params.startsWith("?")) { + params = params.substring(1); + criteria.fromUrlParams(params); + } + updateFilterComponents(); + grid.reload(); + } + + public void updateFilterComponents() { + + applyingCriteria = true; + searchField.setValue(criteria.getFreeTextFilter()); + contextFilter.setValue(criteria.getContextClass()); + fieldTypeFilter.setValue(criteria.getFieldType()); + Boolean active = criteria.getActive(); + if (active == null) { + activeFilter.setValue(I18nProperties.getCaption(Captions.customizableFieldsAllActive)); + } else if (Boolean.TRUE.equals(active)) { + activeFilter.setValue(I18nProperties.getCaption(Captions.customizableFieldsActiveOnly)); + } else { + activeFilter.setValue(I18nProperties.getCaption(Captions.customizableFieldsInactiveOnly)); + } + applyingCriteria = false; + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactController.java index 394fd77934c..a2ca8232157 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactController.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactController.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -58,6 +59,9 @@ import de.symeda.sormas.api.contact.ContactStatus; import de.symeda.sormas.api.contact.FollowUpStatus; import de.symeda.sormas.api.contact.SimilarContactDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.deletionconfiguration.DeletionInfoDto; import de.symeda.sormas.api.docgeneneration.DocumentWorkflow; import de.symeda.sormas.api.docgeneneration.RootEntityType; @@ -159,7 +163,7 @@ private void saveContactsFromLineListing(LineListingLayout lineListingForm, Link } AdoptAddressLayout adoptAddressLayout = lineListingForm.getSharedInfoField().getCaseSelector().getAdoptAddressLayout(); - boolean adoptHomeAddress = adoptAddressLayout != null ? adoptAddressLayout.isAdoptAddress() : false; + boolean adoptHomeAddress = adoptAddressLayout != null && adoptAddressLayout.isAdoptAddress(); while (!contacts.isEmpty()) { LineDto contactLineDto = contacts.pop(); @@ -194,7 +198,7 @@ private void saveContactsFromLineListing(LineListingLayout lineListingForm, Link } public void openLineListingWindow(EventDto eventDto, Set eventParticipantIndexDtos) { - if (eventParticipantIndexDtos.size() == 0) { + if (eventParticipantIndexDtos.isEmpty()) { new Notification( I18nProperties.getString(Strings.headingNoEventParticipantsSelected), I18nProperties.getString(Strings.messageNoEventParticipantsSelected), @@ -452,7 +456,7 @@ public CommitDiscardWrapperComponent getContactCreateComponen createForm.setPerson(casePerson); } final CommitDiscardWrapperComponent createComponent = - new CommitDiscardWrapperComponent(createForm, UiUtil.permitted(UserRight.CONTACT_CREATE), createForm.getFieldGroup()); + new CommitDiscardWrapperComponent<>(createForm, UiUtil.permitted(UserRight.CONTACT_CREATE), createForm.getFieldGroup()); contactSaveTriggered = false; createComponent.addCommitListener(() -> { @@ -677,9 +681,7 @@ public void selectOrCreateContact(final ContactDto contact, final PersonReferenc } }); - contactSelect.setSelectionChangeCallback((commitAllowed) -> { - component.getCommitButton().setEnabled(commitAllowed); - }); + contactSelect.setSelectionChangeCallback(commitAllowed -> component.getCommitButton().setEnabled(commitAllowed)); VaadinUiUtil.showModalPopupWindow(component, I18nProperties.getString(Strings.headingPickOrCreateContact)); contactSelect.selectBestMatch(); @@ -707,7 +709,7 @@ public CommitDiscardWrapperComponent getContactDataEditComponen ContactDataForm editForm = new ContactDataForm(contact.getDisease(), viewMode, isPsuedonymized, contact.isInJurisdiction()); editForm.setValue(contact); final CommitDiscardWrapperComponent editComponent = - new CommitDiscardWrapperComponent(editForm, true, editForm.getFieldGroup()); + new CommitDiscardWrapperComponent<>(editForm, true, editForm.getFieldGroup()); editComponent.getButtonsPanel() .addComponentAsFirst(new DeletionLabel(automaticDeletionInfoDto, manuallyDeletionInfoDto, contact.isDeleted(), ContactDto.I18N_PREFIX)); @@ -773,7 +775,7 @@ public void showBulkContactDataEditComponent( Collection selectedContacts, String caseUuid, AbstractContactGrid contactGrid) { - if (selectedContacts.size() == 0) { + if (selectedContacts.isEmpty()) { new Notification( I18nProperties.getString(Strings.headingNoContactsSelected), I18nProperties.getString(Strings.messageNoContactsSelected), @@ -809,7 +811,7 @@ public void showBulkContactDataEditComponent( ContactFacade contactFacade = FacadeProvider.getContactFacade(); boolean classificationChange = form.getClassificationCheckBox().getValue(); - boolean contactOfficerChange = district != null ? form.getContactOfficerCheckBox().getValue() : false; + boolean contactOfficerChange = district != null && form.getContactOfficerCheckBox().getValue(); List selectedContactsCpy = new ArrayList<>(selectedContacts); @@ -853,7 +855,7 @@ private Consumer> bulkOperationCallback( } public void sendEmailsToAllSelectedItems(Collection selectedRows, AbstractContactGrid contactGrid) { - if (selectedRows.size() == 0) { + if (selectedRows.isEmpty()) { new Notification( I18nProperties.getString(Strings.headingNoContactsSelected), I18nProperties.getString(Strings.messageNoContactsSelected), @@ -1012,13 +1014,9 @@ public void openSelectCaseForContactWindow(Disease disease, Consumer component = new CommitDiscardWrapperComponent<>(selectionField); component.getCommitButton().setCaption(I18nProperties.getCaption(Captions.actionConfirm)); component.getCommitButton().setEnabled(false); - component.addCommitListener(() -> { - selectedCaseCallback.accept(selectionField.getValue()); - }); + component.addCommitListener(() -> selectedCaseCallback.accept(selectionField.getValue())); - selectionField.setSelectionChangeCallback((commitAllowed) -> { - component.getCommitButton().setEnabled(commitAllowed); - }); + selectionField.setSelectionChangeCallback(commitAllowed -> component.getCommitButton().setEnabled(commitAllowed)); VaadinUiUtil.showModalPopupWindow(component, I18nProperties.getString(Strings.headingSelectSourceCase)); } @@ -1027,17 +1025,39 @@ public CommitDiscardWrapperComponent getEpiDataComponent(final Stri ContactDto contact = FacadeProvider.getContactFacade().getByUuid(contactUuid); EpiDataDto epiData = contact.getEpiData(); - EpiDataForm epiDataForm = - new EpiDataForm(contact.getDisease(), ContactDto.class, epiData.isPseudonymized(), epiData.isInJurisdiction(), null, isEditAllowed, null); + + List epiDataMetadata = + FacadeProvider.getCustomizableFieldMetadataFacade().getActiveFieldsForContext(CustomizableFieldContext.EPIDATA); + Map epiDataFieldValues = + FacadeProvider.getCustomizableFieldValueFacade().getValuesForEntity(epiData.getUuid(), CustomizableFieldContext.EPIDATA); + + EpiDataForm epiDataForm = new EpiDataForm( + contact.getDisease(), + ContactDto.class, + epiData.isPseudonymized(), + epiData.isInJurisdiction(), + null, + isEditAllowed, + null, + epiDataMetadata, + epiDataFieldValues); epiDataForm.setValue(epiData); - final CommitDiscardWrapperComponent editView = - new CommitDiscardWrapperComponent(epiDataForm, epiDataForm.getFieldGroup()); + final CommitDiscardWrapperComponent editView = new CommitDiscardWrapperComponent<>(epiDataForm, epiDataForm.getFieldGroup()); + + epiDataForm.addCustomizableFieldValueChangeListener(e -> editView.setDirty(true)); + editView.addDiscardListener(epiDataForm::resetCustomizableFieldValues); editView.addCommitListener(() -> { ContactDto contactDto = FacadeProvider.getContactFacade().getByUuid(contactUuid); contactDto.setEpiData(epiDataForm.getValue()); FacadeProvider.getContactFacade().save(contactDto); + FacadeProvider.getCustomizableFieldValueFacade() + .saveEntityCustomFields(contactDto.getEpiData().getUuid(), CustomizableFieldContext.EPIDATA, epiDataForm.collectCurrentFieldValues()); + epiDataForm.collectExposureCustomizableFieldValues() + .forEach( + (exposureUuid, fields) -> FacadeProvider.getCustomizableFieldValueFacade() + .saveEntityCustomFields(exposureUuid, CustomizableFieldContext.EXPOSURE, fields)); Notification.show(I18nProperties.getString(Strings.messageContactSaved), Type.WARNING_MESSAGE); SormasUI.refreshView(); }); @@ -1048,7 +1068,7 @@ public CommitDiscardWrapperComponent getEpiDataComponent(final Stri public void deleteContact(ContactIndexDto contact, Runnable callback) { DeletableUtils.showDeleteWithReasonPopup( String.format(I18nProperties.getString(Strings.confirmationDeleteEntity), I18nProperties.getString(Strings.entityContact)), - (deleteDetails) -> { + deleteDetails -> { FacadeProvider.getContactFacade().delete(contact.getUuid(), deleteDetails); callback.run(); }); @@ -1118,11 +1138,11 @@ public void linkSelectedContactsToEvent(Collection se } ControllerProvider.getEventController() - .selectOrCreateEventForContactList(selectedRows.stream().map(ContactIndexDto::toReference).collect(Collectors.toList()), remaining -> { - bulkOperationCallback(null, contactGrid, null).accept( + .selectOrCreateEventForContactList( + selectedRows.stream().map(ContactIndexDto::toReference).collect(Collectors.toList()), + remaining -> bulkOperationCallback(null, contactGrid, null).accept( selectedRows.stream() .filter(s -> remaining.stream().anyMatch(r -> r.getUuid().equals(s.getUuid()))) - .collect(Collectors.toList())); - }); + .collect(Collectors.toList()))); } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java index 6fb20a3a375..48f3ad7b828 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/epidata/EpiDataForm.java @@ -29,7 +29,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; @@ -52,6 +54,10 @@ import de.symeda.sormas.api.caze.CaseDataDto; import de.symeda.sormas.api.contact.ContactDto; import de.symeda.sormas.api.contact.ContactReferenceDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityContext; import de.symeda.sormas.api.disease.DiseaseConfigurationDto; import de.symeda.sormas.api.epidata.ClusterType; import de.symeda.sormas.api.epidata.EpiDataDto; @@ -73,8 +79,13 @@ import de.symeda.sormas.ui.utils.FieldAccessHelper; import de.symeda.sormas.ui.utils.FieldHelper; import de.symeda.sormas.ui.utils.NullableOptionGroup; +import de.symeda.sormas.ui.utils.components.CustomizableFieldsGroup; import de.symeda.sormas.ui.utils.components.MultilineLabel; +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress missing equals not relevant for Vaadin components +}) public class EpiDataForm extends AbstractEditForm { private static final long serialVersionUID = 1L; @@ -86,6 +97,10 @@ public class EpiDataForm extends AbstractEditForm { private static final String LOC_SOURCE_CASE_CONTACTS_HEADING = "locSourceCaseContactsHeading"; private static final String LOC_EPI_DATA_FIELDS_HINT = "locEpiDataFieldsHint"; private static final String LOC_EXP_PERIOD_HEADING = "locExpPeriodHeading"; + + private static final String LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_INVESTIGATION = CustomizableFieldGroup.EPIDATA_EXPOSURE_INVESTIGATION.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_ACTIVITY_AS_CASE = CustomizableFieldGroup.EPIDATA_ACTIVITY_AS_CASE.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_CONTACT_WITH_SOURCE_CASE = CustomizableFieldGroup.EPIDATA_CONTACT_WITH_SOURCE_CASE.getKey(); private static final String EXPOSURE_DATES_LAYOUT = fluidRowLocs(3, "EXPOSURE_START_DATE_LABEL", 3, "EXPOSURE_START_DATE_VALUE", 3, "EXPOSURE_END_DATE_LABEL", 3, "EXPOSURE_END_DATE_VALUE"); private static final String LOC_OTHER_INFORMATION_HEADING = "locOtherInformationHeading"; @@ -98,6 +113,7 @@ public class EpiDataForm extends AbstractEditForm { loc(LOC_EXP_PERIOD_HEADING) + loc(EpiDataDto.EXPOSURE_DETAILS_KNOWN) + loc(EpiDataDto.EXPOSURES) + + loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_INVESTIGATION) + loc(LOC_CONCLUSION_HEADING) + fluidRowLocs(6,EpiDataDto.CASE_IMPORTED_STATUS,6,"") + fluidRowLocs(6, EpiDataDto.IMPORTED_CASE, 6, EpiDataDto.COUNTRY)+ @@ -106,6 +122,7 @@ public class EpiDataForm extends AbstractEditForm { loc(LOC_ACTIVITY_AS_CASE_INVESTIGATION_HEADING) + loc(EpiDataDto.ACTIVITY_AS_CASE_DETAILS_KNOWN)+ loc(EpiDataDto.ACTIVITIES_AS_CASE) + + loc(LOC_CUSTOMIZABLE_FIELDS_ACTIVITY_AS_CASE) + loc(LOC_CLUSTER_TYPE_HEADING)+ fluidRowLocs(3, EpiDataDto.CLUSTER_RELATED,5,EpiDataDto.CLUSTER_TYPE,4,EpiDataDto.CLUSTER_TYPE_TEXT) + locCss(VSPACE_TOP_3, LOC_EPI_DATA_FIELDS_HINT) + @@ -115,7 +132,8 @@ public class EpiDataForm extends AbstractEditForm { private static final String SOURCE_CONTACTS_HTML_LAYOUT = locCss(VSPACE_TOP_3, LOC_SOURCE_CASE_CONTACTS_HEADING) + - loc(EpiDataDto.CONTACT_WITH_SOURCE_CASE_KNOWN); + loc(EpiDataDto.CONTACT_WITH_SOURCE_CASE_KNOWN) + + loc(LOC_CUSTOMIZABLE_FIELDS_CONTACT_WITH_SOURCE_CASE); private static final String OTHER_INFORMATION_HTML_LAYOUT = loc(LOC_OTHER_INFORMATION_HEADING) + fluidRowLocs(EpiDataDto.OTHER_DETAILS); @@ -123,10 +141,14 @@ public class EpiDataForm extends AbstractEditForm { private final Disease disease; private final Class parentClass; - private final Consumer sourceContactsToggleCallback; + private final transient Consumer sourceContactsToggleCallback; private final boolean isPseudonymized; private final Date symptomOnsetDate; + private CustomizableFieldsGroup exposureInvestigationPanel; + private CustomizableFieldsGroup activityAsCasePanel; + private CustomizableFieldsGroup contactWithSourceCasePanel; + public EpiDataForm( Disease disease, Class parentClass, @@ -134,7 +156,9 @@ public EpiDataForm( boolean inJurisdiction, Consumer sourceContactsToggleCallback, boolean isEditAllowed, - Date date) { + Date date, + List customizableFieldsMetadata, + Map customizableFieldsValues) { super( EpiDataDto.class, EpiDataDto.I18N_PREFIX, @@ -147,6 +171,8 @@ public EpiDataForm( this.sourceContactsToggleCallback = sourceContactsToggleCallback; this.isPseudonymized = isPseudonymized; this.symptomOnsetDate = date; + setCustomizableFieldsMetadata(customizableFieldsMetadata); + setCustomizableFieldsValues(customizableFieldsValues); addFields(); } @@ -158,6 +184,13 @@ protected void addFields() { addHeadingsAndInfoTexts(); + exposureInvestigationPanel = new CustomizableFieldsGroup(CustomizableFieldGroup.EPIDATA_EXPOSURE_INVESTIGATION); + exposureInvestigationPanel.setVisibilityContext(new CustomizableFieldVisibilityContext().withDisease(disease)); + exposureInvestigationPanel.setFieldsMetadata(getCustomizableFieldsMetadata()); + exposureInvestigationPanel.setFieldsValues(getCustomizableFieldsValues()); + exposureInvestigationPanel.updateFieldsDisplay(); + getContent().addComponent(exposureInvestigationPanel, LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_INVESTIGATION); + NullableOptionGroup ogExposureDetailsKnown = addField(EpiDataDto.EXPOSURE_DETAILS_KNOWN, NullableOptionGroup.class); ExposuresField exposuresField = addField( EpiDataDto.EXPOSURES, @@ -176,6 +209,13 @@ protected void addFields() { addActivityAsCaseFields(); } + activityAsCasePanel = new CustomizableFieldsGroup(CustomizableFieldGroup.EPIDATA_ACTIVITY_AS_CASE); + activityAsCasePanel.setVisibilityContext(new CustomizableFieldVisibilityContext().withDisease(disease)); + activityAsCasePanel.setFieldsMetadata(getCustomizableFieldsMetadata()); + activityAsCasePanel.setFieldsValues(getCustomizableFieldsValues()); + activityAsCasePanel.updateFieldsDisplay(); + getContent().addComponent(activityAsCasePanel, LOC_CUSTOMIZABLE_FIELDS_ACTIVITY_AS_CASE); + addField(EpiDataDto.HIGH_TRANSMISSION_RISK_AREA, NullableOptionGroup.class); addField(EpiDataDto.LARGE_OUTBREAKS_AREA, NullableOptionGroup.class); addField(EpiDataDto.AREA_INFECTED_ANIMALS, NullableOptionGroup.class); @@ -183,7 +223,7 @@ protected void addFields() { if (sourceContactsToggleCallback != null) { ogContactWithSourceCaseKnown.addValueChangeListener(e -> { - YesNoUnknown sourceContactsKnown = (YesNoUnknown) FieldHelper.getNullableSourceFieldValue((Field) e.getProperty()); + YesNoUnknown sourceContactsKnown = (YesNoUnknown) FieldHelper.getNullableSourceFieldValue((Field) e.getProperty()); sourceContactsToggleCallback.accept(YesNoUnknown.YES == sourceContactsKnown); }); } @@ -217,6 +257,14 @@ protected void addFields() { .setVisibleWhen(getFieldGroup(), EpiDataDto.MODE_OF_TRANSMISSION_TYPE, EpiDataDto.MODE_OF_TRANSMISSION, ModeOfTransmission.OTHER, true); FieldHelper.setVisibleWhen(getFieldGroup(), EpiDataDto.INFECTION_SOURCE_TEXT, EpiDataDto.INFECTION_SOURCE, InfectionSource.OTHER, true); FieldHelper.setVisibleWhen(getFieldGroup(), EpiDataDto.COUNTRY, EpiDataDto.IMPORTED_CASE, YesNoUnknown.YES, true); + + contactWithSourceCasePanel = new CustomizableFieldsGroup(CustomizableFieldGroup.EPIDATA_CONTACT_WITH_SOURCE_CASE); + contactWithSourceCasePanel.setVisibilityContext(new CustomizableFieldVisibilityContext().withDisease(disease)); + contactWithSourceCasePanel.setFieldsMetadata(getCustomizableFieldsMetadata()); + contactWithSourceCasePanel.setFieldsValues(getCustomizableFieldsValues()); + contactWithSourceCasePanel.updateFieldsDisplay(); + getContent().addComponent(contactWithSourceCasePanel, LOC_CUSTOMIZABLE_FIELDS_CONTACT_WITH_SOURCE_CASE); + initializeVisibilitiesAndAllowedVisibilities(); initializeAccessAndAllowedAccesses(); @@ -300,9 +348,8 @@ private void addActivityAsCaseFields() { Collections.singletonList(YesNoUnknown.YES), true); - activityAsCaseField.addValueChangeListener(e -> { - ogActivityAsCaseDetailsKnown.setEnabled(CollectionUtils.isEmpty(activityAsCaseField.getValue())); - }); + activityAsCaseField + .addValueChangeListener(e -> ogActivityAsCaseDetailsKnown.setEnabled(CollectionUtils.isEmpty(activityAsCaseField.getValue()))); } private void addHeadingsAndInfoTexts() { @@ -344,6 +391,66 @@ private void addHeadingsAndInfoTexts() { getContent().addComponent(otherInformationLabel, LOC_OTHER_INFORMATION_HEADING); } + /** + * Collects the current values from all customizable field panels. + * + * @return map of metadata DTO to value DTO, suitable for + * {@link de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade#saveEntityCustomFields} + */ + public Map collectCurrentFieldValues() { + Map result = new HashMap<>(); + for (CustomizableFieldsGroup panel : new CustomizableFieldsGroup[] { + exposureInvestigationPanel, + activityAsCasePanel, + contactWithSourceCasePanel }) { + if (panel != null) { + panel.getFieldsValues().forEach((metadata, valueDto) -> { + if (valueDto != null) { + result.put(metadata, valueDto); + } + }); + } + } + return result; + } + + /** + * Registers a listener that fires whenever any customizable field in any of this form's + * groups changes its value. Used by the controller to drive + * {@link de.symeda.sormas.ui.utils.CommitDiscardWrapperComponent#setDirty(boolean)}. + * + * @param listener + * the listener to register on all panels + */ + public void addCustomizableFieldValueChangeListener(com.vaadin.data.HasValue.ValueChangeListener listener) { + for (CustomizableFieldsGroup panel : new CustomizableFieldsGroup[] { + exposureInvestigationPanel, + activityAsCasePanel, + contactWithSourceCasePanel }) { + if (panel != null) { + panel.addValueChangeListener(listener); + } + } + } + + /** + * Resets all customizable field panels to the original values that were loaded when the form + * was opened. Call this from a + * {@link de.symeda.sormas.ui.utils.CommitDiscardWrapperComponent.DiscardListener} to keep + * customizable fields in sync with the regular field discard. + */ + public void resetCustomizableFieldValues() { + for (CustomizableFieldsGroup panel : new CustomizableFieldsGroup[] { + exposureInvestigationPanel, + activityAsCasePanel, + contactWithSourceCasePanel }) { + if (panel != null) { + panel.setFieldsValues(getCustomizableFieldsValues()); + panel.updateFieldsDisplay(); + } + } + } + public void disableContactWithSourceCaseKnownField() { setEnabled(false, EpiDataDto.CONTACT_WITH_SOURCE_CASE_KNOWN); } @@ -352,6 +459,10 @@ public void setGetSourceContactsCallback(Supplier> cal ((ExposuresField) getField(EpiDataDto.EXPOSURES)).setGetSourceContactsCallback(callback); } + public Map> collectExposureCustomizableFieldValues() { + return ((ExposuresField) getField(EpiDataDto.EXPOSURES)).collectCustomizableFieldValues(); + } + @Override protected String createHtmlLayout() { String layout = parentClass == CaseDataDto.class ? MAIN_HTML_LAYOUT + SOURCE_CONTACTS_HTML_LAYOUT : MAIN_HTML_LAYOUT; diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java index ef309738be9..c4a9b2eec3d 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposureForm.java @@ -27,8 +27,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import com.vaadin.icons.VaadinIcons; @@ -50,6 +52,10 @@ import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.caze.CaseDataDto; import de.symeda.sormas.api.contact.ContactReferenceDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldVisibilityContext; import de.symeda.sormas.api.disease.DiseaseConfigurationDto; import de.symeda.sormas.api.epidata.AnimalCondition; import de.symeda.sormas.api.event.MeansOfTransport; @@ -87,12 +93,16 @@ import de.symeda.sormas.ui.utils.DateTimeField; import de.symeda.sormas.ui.utils.FieldHelper; import de.symeda.sormas.ui.utils.NullableOptionGroup; +import de.symeda.sormas.ui.utils.components.CustomizableFieldsGroup; public class ExposureForm extends AbstractEditForm { private static final long serialVersionUID = 8262753698264714832L; public static final String MAIN_ACCORDION_LOC = "mainAccordionLoc"; + private static final String LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_DETAILS = CustomizableFieldGroup.EXPOSURE_DETAILS.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_EXPOSURES_GENERAL = CustomizableFieldGroup.EXPOSURES_GENERAL.getKey(); + private static final String LOC_CUSTOMIZABLE_FIELDS_LOCATION_GENERAL = CustomizableFieldGroup.LOCATION_GENERAL.getKey(); private static final String LOC_EXPOSURES_HEADING = "locExposuresHeading"; private static final String LOC_EXPOSURE_DETAILS_HEADING = "locExposureDetailsHeading"; private static final String LOC_LOCATION_HEADING = "locLocationHeading"; @@ -100,13 +110,14 @@ public class ExposureForm extends AbstractEditForm { private static final String LOC_BURIAL_DETAILS_HEADING = "locBurialDetailsHeading"; private static final String LOC_CONCLUSION_HEADING = "locConclusionHeading"; - //@formatter:off public static final String MAIN_ACCORDION_LAYOUT = fluidRowLocs(MAIN_ACCORDION_LOC); private static final String UUID_REPORTING_USER = fluidRowLocs(ExposureDto.UUID, ExposureDto.REPORTING_USER); + //@formatter:off private static final String EXPOSURE_DETAILS_LAYOUT = fluidRowLocs(ExposureDto.START_DATE, ExposureDto.END_DATE) + + loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_DETAILS) + loc(LOC_EXPOSURES_HEADING) + fluidRowLocs(ExposureDto.EXPOSURE_CATEGORY, ExposureDto.EXPOSURE_SETTING, ExposureDto.EXPOSURE_SETTING_DETAILS) + fluidRow( @@ -135,6 +146,7 @@ public class ExposureForm extends AbstractEditForm { ExposureDto.PROTECTIVE_MEASURE_DETAILS )) ) + + loc(LOC_CUSTOMIZABLE_FIELDS_EXPOSURES_GENERAL) + loc(ExposureDto.DESCRIPTION); private static final String ACTIVITY_DETAILS_LAYOUT = @@ -203,7 +215,8 @@ public class ExposureForm extends AbstractEditForm { ) + loc(ExposureDto.MEANS_OF_TRANSPORT_DETAILS) + fluidRowLocs(ExposureDto.CONNECTION_NUMBER, ExposureDto.SEAT_NUMBER) + - loc(ExposureDto.LOCATION); + loc(ExposureDto.LOCATION)+ + loc(LOC_CUSTOMIZABLE_FIELDS_LOCATION_GENERAL); //@formatter:on private final Class epiDataParentClass; @@ -237,13 +250,19 @@ public class ExposureForm extends AbstractEditForm { private TextField animalCategoryDetailsField; private NullableOptionGroup fomiteTransmissionLocationField; + private CustomizableFieldsGroup exposureDetailsPanel; + private CustomizableFieldsGroup exposuresGeneralPanel; + private CustomizableFieldsGroup locationGeneralPanel; + public ExposureForm( boolean create, Class epiDataParentClass, List sourceContacts, FieldVisibilityCheckers fieldVisibilityCheckers, - UiFieldAccessCheckers fieldAccessCheckers, - Disease disease) { + UiFieldAccessCheckers fieldAccessCheckers, + Disease disease, + List customizableFieldsMetadata, + Map customizableFieldsValues) { super(ExposureDto.class, ExposureDto.I18N_PREFIX, false, fieldVisibilityCheckers, fieldAccessCheckers); setWidth(960, Unit.PIXELS); @@ -252,6 +271,9 @@ public ExposureForm( this.epiDataParentClass = epiDataParentClass; this.disease = disease; + setCustomizableFieldsMetadata(customizableFieldsMetadata); + setCustomizableFieldsValues(customizableFieldsValues); + if (create) { hideValidationUntilNextCommit(); } @@ -275,13 +297,35 @@ protected void addFields() { locationDetailsLayout.setTemplateContents(LOCATION_DETAILS_LAYOUT); addHeadingsAndInfoTexts(); + + exposureDetailsPanel = new CustomizableFieldsGroup(CustomizableFieldGroup.EXPOSURE_DETAILS); + exposureDetailsPanel.setVisibilityContext(new CustomizableFieldVisibilityContext().withDisease(disease)); + exposureDetailsPanel.setFieldsMetadata(getCustomizableFieldsMetadata()); + exposureDetailsPanel.setFieldsValues(getCustomizableFieldsValues()); + exposureDetailsPanel.updateFieldsDisplay(); + exposureDetailsLayout.addComponent(exposureDetailsPanel, LOC_CUSTOMIZABLE_FIELDS_EXPOSURE_DETAILS); + addBasicFields(); + exposuresGeneralPanel = new CustomizableFieldsGroup(CustomizableFieldGroup.EXPOSURES_GENERAL); + exposuresGeneralPanel.setVisibilityContext(new CustomizableFieldVisibilityContext().withDisease(disease)); + exposuresGeneralPanel.setFieldsMetadata(getCustomizableFieldsMetadata()); + exposuresGeneralPanel.setFieldsValues(getCustomizableFieldsValues()); + 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); + + locationGeneralPanel = new CustomizableFieldsGroup(CustomizableFieldGroup.LOCATION_GENERAL); + locationGeneralPanel.setVisibilityContext(new CustomizableFieldVisibilityContext().withDisease(disease)); + locationGeneralPanel.setFieldsMetadata(getCustomizableFieldsMetadata()); + locationGeneralPanel.setFieldsValues(getCustomizableFieldsValues()); + locationGeneralPanel.updateFieldsDisplay(); + locationDetailsLayout.addComponent(locationGeneralPanel, LOC_CUSTOMIZABLE_FIELDS_LOCATION_GENERAL); getField(ExposureDto.MEANS_OF_TRANSPORT).addValueChangeListener(e -> { if (e.getProperty().getValue() == MeansOfTransport.PLANE) { getField(ExposureDto.CONNECTION_NUMBER).setCaption(I18nProperties.getCaption(Captions.exposureFlightNumber)); @@ -915,6 +959,23 @@ private void addFieldsWithCssToLayout(CustomLayout layout, Class collectCurrentFieldValues() { + Map result = new HashMap<>(); + for (CustomizableFieldsGroup panel : new CustomizableFieldsGroup[] { + exposureDetailsPanel, + exposuresGeneralPanel, + locationGeneralPanel }) { + if (panel != null) { + panel.getFieldsValues().forEach((metadata, valueDto) -> { + if (valueDto != null) { + result.put(metadata, valueDto); + } + }); + } + } + return result; + } + @Override protected String createHtmlLayout() { //@formatter:off diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposuresField.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposuresField.java index ab7460b3278..7562f13347e 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposuresField.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/exposure/ExposuresField.java @@ -15,7 +15,10 @@ package de.symeda.sormas.ui.exposure; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -36,6 +39,9 @@ import de.symeda.sormas.api.caze.CaseDataDto; import de.symeda.sormas.api.contact.ContactDto; import de.symeda.sormas.api.contact.ContactReferenceDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; +import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; +import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.exposure.ExposureCategory; import de.symeda.sormas.api.exposure.ExposureDto; import de.symeda.sormas.api.i18n.Captions; @@ -57,7 +63,10 @@ import de.symeda.sormas.ui.utils.FieldAccessCellStyleGenerator; import de.symeda.sormas.ui.utils.VaadinUiUtil; -@SuppressWarnings("serial") +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // suppress missing equals not relevant for Vaadin components +}) public class ExposuresField extends AbstractTableField { private static final String COLUMN_EXPOSURE_CATEGORY = ExposureDto.EXPOSURE_CATEGORY; @@ -72,11 +81,12 @@ public class ExposuresField extends AbstractTableField { private boolean isPseudonymized; private boolean isEditAllowed; private Disease disease; + private final Map> pendingCustomizableFieldValues = new HashMap<>(); public ExposuresField( Disease disease, FieldVisibilityCheckers fieldVisibilityCheckers, - UiFieldAccessCheckers fieldAccessCheckers, + UiFieldAccessCheckers fieldAccessCheckers, boolean isEditAllowed) { super(fieldAccessCheckers, isEditAllowed); @@ -215,13 +225,21 @@ protected void editEntry(ExposureDto entry, boolean create, Consumer exposureMetadata = + FacadeProvider.getCustomizableFieldMetadataFacade().getActiveFieldsForContext(CustomizableFieldContext.EXPOSURE); + Map exposureFieldValues = pendingCustomizableFieldValues.containsKey(entry.getUuid()) + ? pendingCustomizableFieldValues.get(entry.getUuid()) + : FacadeProvider.getCustomizableFieldValueFacade().getValuesForEntity(entry.getUuid(), CustomizableFieldContext.EXPOSURE); + ExposureForm exposureForm = new ExposureForm( create, epiDataParentClass, getSourceContactsCallback != null ? getSourceContactsCallback.get() : null, fieldVisibilityCheckers, fieldAccessCheckers, - disease); + disease, + exposureMetadata, + exposureFieldValues); exposureForm.setValue(entry); final CommitDiscardWrapperComponent component = @@ -245,6 +263,7 @@ protected void editEntry(ExposureDto entry, boolean create, Consumer { if (!exposureForm.getFieldGroup().isModified()) { + pendingCustomizableFieldValues.put(entry.getUuid(), exposureForm.collectCurrentFieldValues()); commitCallback.accept(exposureForm.getValue()); updateAddButtonVisibility(getValue().size()); @@ -255,6 +274,7 @@ protected void editEntry(ExposureDto entry, boolean create, Consumer { popupWindow.close(); ExposuresField.this.removeEntry(entry); + pendingCustomizableFieldValues.remove(entry.getUuid()); updateAddButtonVisibility(getValue().size()); }, I18nProperties.getCaption(ExposureDto.I18N_PREFIX)); @@ -285,6 +305,10 @@ public void setPropertyDataSource(Property newDataSource) { } } + public Map> collectCustomizableFieldValues() { + return Collections.unmodifiableMap(pendingCustomizableFieldValues); + } + public void setGetSourceContactsCallback(Supplier> callback) { getSourceContactsCallback = callback; } diff --git a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/button.scss b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/button.scss index efeb984fbed..915866295e8 100644 --- a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/button.scss +++ b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/button.scss @@ -1,102 +1,105 @@ - @mixin sormas-button { .v-button { text-transform: uppercase; border-color: $v-selection-color; box-shadow: none; - - &.v-button-link { - text-transform: none; - } + + &.v-button-link { + text-transform: none; + } &.v-button-caption-overflow-label { text-overflow: ellipsis; overflow: hidden; } - - &.v-button-font-size-large { - font-size: 1.5em; - } - + + &.v-button-font-size-large { + font-size: 1.5em; + } + &.v-button-subtle { - background-color: inherit; + background-color: inherit; } - + &.v-button-border-neutral { - border-color: #000000; + border-color: #000000; } &.v-button-compact { - padding: 0px; - height: auto; + padding: 0px; + height: auto; } - + &.v-button-danger, &.v-button-critical { @include valo-button-style($background-color: $s-critical-color); border-color: darken($s-critical-color, 25%) } - + &.v-button-warning { @include valo-button-style($background-color: $s-warning-color); border-color: darken($s-warning-color, 25%) } - + &.v-button-filter { background-color: $v-focus-color; text-decoration: none; text-transform: none; - font-weight: bold; + font-weight: bold; color: $v-font-color; - + &.v-button-filter-light { background-color: $v-focus-light-color; - + &:hover { background-color: $v-focus-color; } - + &.v-disabled { - background-color: $v-focus-light-color; + background-color: $v-focus-light-color; } } - + &.v-button-filter-dark { - background-color: $s-primary-color; - color: #FFFFFF; + background-color: $s-primary-color; + color: #FFFFFF; } - + &.v-button-filter-enabled { - background-color: #2eb82e; - color: #FFFFFF; - - &:hover { - background-color: #248f24; - } + background-color: #2eb82e; + color: #FFFFFF; + + &:hover { + background-color: #248f24; + } } - + &.v-button-filter-disabled { - background-color: #C8C8C8; - - &:hover { - background-color: #B8B8B8; - } + background-color: #C8C8C8; + + &:hover { + background-color: #B8B8B8; + } } - + &.v-button-filter-small { - height: 26px; - padding: 0px 7px; + height: 26px; + padding: 0px 7px; } } - &.v-button-vertical-align-top, .vertical-align-top { + + &.v-button-vertical-align-top, + .vertical-align-top { vertical-align: top; } } // Segmented button group used for YesNoUnknown fields — matches the NullableOptionGroup horizontal style. .v-horizontallayout.yes-no-unknown-group { - line-height: 0; + display: flex; + align-items: center; + margin-bottom: 1rem; .v-slot { margin-right: -2px; @@ -122,6 +125,7 @@ &:first-child { border-radius: 4px 0 0 4px; } + &:last-child { border-radius: 0 4px 4px 0; } diff --git a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/customizablefields.scss b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/customizablefields.scss new file mode 100644 index 00000000000..c638ecb1664 --- /dev/null +++ b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/customizablefields.scss @@ -0,0 +1,15 @@ +@mixin sormas-customizablefields { + + .customizable-fields-group { + background-color: #fffcf6; + border-radius: 0 4px 4px 0; + padding-bottom: 8px; + margin-bottom: 0.5rem; + font-size: 0.95em; + color: #5a6a78; + + .v-caption { + font-style: italic; + } + } +} \ No newline at end of file diff --git a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/sormas.scss b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/sormas.scss index 2da49bdb443..103dcb40410 100644 --- a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/sormas.scss +++ b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/sormas.scss @@ -50,7 +50,7 @@ $v-gradient: false; $v-border: 2px solid $v-focus-color; $v-border-subtle: 2px solid $v-selection-color; $v-border-primary: 2px solid $v-primary-color; -$v-caption-font-weight: 600 !default; +$v-caption-font-weight: 600 !default; $v-layout-margin-top: round($v-unit-size / 1.5) !default; $v-layout-margin-right: $v-layout-margin-top !default; @@ -60,7 +60,7 @@ $v-layout-spacing-vertical: round($v-unit-size / 1.8) !default; $v-layout-spacing-horizontal: round($v-unit-size / 1.8) !default; -$editor-shadow: 0 0 10px 10px rgba(0,0,0,.1) !default; +$editor-shadow: 0 0 10px 10px rgba(0, 0, 0, .1) !default; $editor-background-color: #6691C4 !default; $editor-embed-background-color: darken($editor-background-color, 5%) !default; $editor-raised-background-color: lighten($editor-background-color, 10%) !default; @@ -75,6 +75,7 @@ $editor-caption-font-color: valo-font-color($editor-background-color, 0.5) !defa @import "components/combobox.scss"; @import "components/countelement.scss"; @import "components/customcomponent.scss"; +@import "components/customizablefields.scss"; @import "components/datefield.scss"; @import "components/exttokenfield.scss"; @import "components/grid.scss"; @@ -119,13 +120,16 @@ $editor-caption-font-color: valo-font-color($editor-background-color, 0.5) !defa } @function valo-font-color ($bg-color, $contrast: $v-font-color-contrast) { - @if type-of($bg-color) == color { + @if type-of($bg-color)==color { @if is-dark-color($bg-color) { @return $v-background-color; - } @else { + } + + @else { @return #374B59; } } + @return #374B59; } @@ -133,12 +137,13 @@ $editor-caption-font-color: valo-font-color($editor-background-color, 0.5) !defa @include valo; @include sormas-global; - + @include sormas-button; - @include sormas-checkbox; + @include sormas-checkbox; @include sormas-combobox; @include sormas-countelement; @include sormas-customcomponent; + @include sormas-customizablefields; @include sormas-datefield; @include sormas-exttokenfield; @include sormas-grid; @@ -172,11 +177,11 @@ $editor-caption-font-color: valo-font-color($editor-background-color, 0.5) !defa @include sormas-login-view; @include sormas-statistics-view; @include sormas-view; - + @include sormas-bootstrap-grid; @include sormas-deprecated; - + .sormas-content { overflow: auto; } -} +} \ No newline at end of file diff --git a/sormas-ui/src/main/webapp/WEB-INF/glassfish-web.xml b/sormas-ui/src/main/webapp/WEB-INF/glassfish-web.xml index 58b3520520b..9970219c1f3 100644 --- a/sormas-ui/src/main/webapp/WEB-INF/glassfish-web.xml +++ b/sormas-ui/src/main/webapp/WEB-INF/glassfish-web.xml @@ -1,9 +1,7 @@ - + - + CASE_CREATE @@ -1221,4 +1219,9 @@ EPIPULSE_EXPORT_DELETE - + + CUSTOMIZABLE_FIELD_MANAGEMENT + CUSTOMIZABLE_FIELD_MANAGEMENT + + + \ No newline at end of file diff --git a/sormas-ui/src/main/webapp/WEB-INF/web.xml b/sormas-ui/src/main/webapp/WEB-INF/web.xml index bcdff354bf7..a9f10d30120 100644 --- a/sormas-ui/src/main/webapp/WEB-INF/web.xml +++ b/sormas-ui/src/main/webapp/WEB-INF/web.xml @@ -1,9 +1,9 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + version="3.1"> SORMAS Web Interface @@ -25,9 +25,9 @@ CASE_VIEW - - CASE_VIEW_ARCHIVED - + + CASE_VIEW_ARCHIVED + CASE_EDIT @@ -89,9 +89,9 @@ IMMUNIZATION_VIEW - - IMMUNIZATION_VIEW_ARCHIVED - + + IMMUNIZATION_VIEW_ARCHIVED + IMMUNIZATION_CREATE @@ -225,9 +225,9 @@ CONTACT_VIEW - - CONTACT_VIEW_ARCHIVED - + + CONTACT_VIEW_ARCHIVED + CONTACT_ARCHIVE @@ -305,9 +305,9 @@ TASK_ARCHIVE - - TASK_VIEW_ARCHIVED - + + TASK_VIEW_ARCHIVED + ACTION_CREATE @@ -329,9 +329,9 @@ EVENT_VIEW - - EVENT_VIEW_ARCHIVED - + + EVENT_VIEW_ARCHIVED + EVENT_EDIT @@ -377,9 +377,9 @@ EVENTPARTICIPANT_VIEW - - EVENTPARTICIPANT_VIEW_ARCHIVED - + + EVENTPARTICIPANT_VIEW_ARCHIVED + EVENTGROUP_CREATE @@ -401,9 +401,9 @@ EVENTGROUP_DELETE - - EVENTGROUP_VIEW_ARCHIVED - + + EVENTGROUP_VIEW_ARCHIVED + WEEKLYREPORT_CREATE @@ -425,9 +425,9 @@ USER_VIEW - - USER_ROLE_VIEW - + + USER_ROLE_VIEW + USER_ROLE_EDIT @@ -477,9 +477,9 @@ INFRASTRUCTURE_VIEW - - INFRASTRUCTURE_VIEW_ARCHIVED - + + INFRASTRUCTURE_VIEW_ARCHIVED + INFRASTRUCTURE_EXPORT @@ -625,9 +625,9 @@ CAMPAIGN_VIEW - - CAMPAIGN_VIEW_ARCHIVED - + + CAMPAIGN_VIEW_ARCHIVED + CAMPAIGN_EDIT @@ -645,9 +645,9 @@ CAMPAIGN_FORM_DATA_VIEW - - CAMPAIGN_FORM_DATA_VIEW_ARCHIVED - + + CAMPAIGN_FORM_DATA_VIEW_ARCHIVED + CAMPAIGN_FORM_DATA_EDIT @@ -725,9 +725,9 @@ TRAVEL_ENTRY_VIEW - - TRAVEL_ENTRY_VIEW_ARCHIVED - + + TRAVEL_ENTRY_VIEW_ARCHIVED + TRAVEL_ENTRY_CREATE @@ -761,9 +761,9 @@ ENVIRONMENT_ARCHIVE - - ENVIRONMENT_VIEW_ARCHIVED - + + ENVIRONMENT_VIEW_ARCHIVED + ENVIRONMENT_DELETE @@ -841,13 +841,13 @@ SELF_REPORT_DELETE - - SELF_REPORT_EXPORT - + + SELF_REPORT_EXPORT + - - SELF_REPORT_IMPORT - + + SELF_REPORT_IMPORT + SELF_REPORT_ARCHIVE @@ -989,4 +989,8 @@ EPIPULSE_EXPORT_DELETE - + + CUSTOMIZABLE_FIELD_MANAGEMENT + + + \ No newline at end of file From 29b506bc69fc4d1a4c4194ca0d8693752b28de37 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Mon, 20 Apr 2026 10:38:39 +0200 Subject: [PATCH 53/55] Fixed EJB local inject causing test failures --- .../customizablefield/CustomizableFieldValueFacadeEjb.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java index 23cbd69bf33..81d4fa152c9 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldValueFacadeEjb.java @@ -35,13 +35,13 @@ import de.symeda.sormas.api.common.progress.ProcessedEntity; import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto; -import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataFacade; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueCriteria; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade; import de.symeda.sormas.api.customizablefield.CustomizableFieldValueReferenceDto; import de.symeda.sormas.api.utils.ValidationRuntimeException; import de.symeda.sormas.backend.common.AbstractCoreFacadeEjb; +import de.symeda.sormas.backend.customizablefield.CustomizableFieldMetadataFacadeEjb.CustomizableFieldMetadataFacadeEjbLocal; import de.symeda.sormas.backend.util.DtoHelper; import de.symeda.sormas.backend.util.Pseudonymizer; @@ -57,7 +57,7 @@ public class CustomizableFieldValueFacadeEjb @EJB private CustomizableFieldMetadataService customizableFieldMetadataService; @EJB - private CustomizableFieldMetadataFacade customizableFieldMetadataFacade; + private CustomizableFieldMetadataFacadeEjbLocal customizableFieldMetadataFacade; public CustomizableFieldValueFacadeEjb() { } From 9f94409375114087df29e111ea4aac8579a61b62 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Mon, 20 Apr 2026 10:57:27 +0200 Subject: [PATCH 54/55] Removed customizable field value table partitioning, not supported by unit tests postgres v10 --- .../src/main/resources/sql/sormas_schema.sql | 33 ++-------- .../CustomizableFieldSchemaTest.java | 62 ------------------- 2 files changed, 4 insertions(+), 91 deletions(-) delete mode 100644 sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldSchemaTest.java diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 5d999a2eded..48eccfe6ed0 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15638,11 +15638,9 @@ CREATE TRIGGER delete_history_trigger ALTER TABLE customizablefieldmetadata_history OWNER TO sormas_user; -- Create CustomizableFieldValue table --- Partitioned by contextClass (LIST) matching CustomizableFieldContext enum values. --- A new partition must be added here whenever a new value is added to that enum. CREATE TABLE IF NOT EXISTS customizablefieldvalue ( id bigint NOT NULL, - uuid character varying(36) NOT NULL, + uuid character varying(36) NOT NULL UNIQUE, changeDate timestamp(3) NOT NULL DEFAULT NOW(), creationDate timestamp(3) NOT NULL DEFAULT NOW(), deleted boolean DEFAULT false, @@ -15664,22 +15662,10 @@ CREATE TABLE IF NOT EXISTS customizablefieldvalue ( change_user_id bigint, sys_period tstzrange NOT NULL, - - PRIMARY KEY (id, contextClass), - UNIQUE(uuid, contextClass), + + PRIMARY KEY (id), UNIQUE(customizablefieldmetadata_id, entityUuid, contextClass) -) PARTITION BY LIST (contextClass); - --- One partition per CustomizableFieldContext enum value -CREATE TABLE customizablefieldvalue_case - PARTITION OF customizablefieldvalue - FOR VALUES IN ('CASE'); -CREATE TABLE customizablefieldvalue_epidata - PARTITION OF customizablefieldvalue - FOR VALUES IN ('EPIDATA'); -CREATE TABLE customizablefieldvalue_exposure - PARTITION OF customizablefieldvalue - FOR VALUES IN ('EXPOSURE'); +); CREATE INDEX idx_customizablefieldvalue_uuid ON customizablefieldvalue (uuid); @@ -15692,23 +15678,12 @@ CREATE INDEX idx_customizablefieldvalue_fieldMetadata CREATE INDEX idx_customizablefieldvalue_deleted ON customizablefieldvalue (deleted); -ALTER TABLE customizablefieldvalue - ADD CONSTRAINT fk_customizablefieldvalue_metadata - FOREIGN KEY (customizablefieldmetadata_id) - REFERENCES customizablefieldmetadata(id) - ON DELETE CASCADE; - ALTER TABLE customizablefieldvalue ADD CONSTRAINT fk_change_user_id FOREIGN KEY (change_user_id) REFERENCES users (id); - -ALTER TABLE customizablefieldvalue_case OWNER TO sormas_user; -ALTER TABLE customizablefieldvalue_epidata OWNER TO sormas_user; -ALTER TABLE customizablefieldvalue_exposure OWNER TO sormas_user; ALTER TABLE customizablefieldvalue OWNER TO sormas_user; -- CustomizableFieldValue history tables CREATE TABLE customizablefieldvalue_history (LIKE customizablefieldvalue); - DROP TRIGGER IF EXISTS versioning_trigger ON customizablefieldvalue; CREATE TRIGGER versioning_trigger BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldvalue diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldSchemaTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldSchemaTest.java deleted file mode 100644 index 4dafcc4ae63..00000000000 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldSchemaTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SORMAS® - Surveillance Outbreak Response Management & Analysis System - * Copyright © 2016-2026 SORMAS Foundation gGmbH - * 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.backend.customizablefield; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Objects; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -import de.symeda.sormas.api.customizablefield.CustomizableFieldContext; - -/** - * Verifies that for every {@link CustomizableFieldContext} enum value there is a - * corresponding LIST partition of {@code customizablefieldvalue} declared in - * {@code sormas_schema.sql}. - *

- * Convention: the partition table is named - * {@code customizablefieldvalue_}. - *

- * When a new context is added to the enum, this test will fail until the matching - * {@code CREATE TABLE customizablefieldvalue_ PARTITION OF customizablefieldvalue - * FOR VALUES IN ('')} migration is added to the schema. - */ -class CustomizableFieldSchemaTest { - - private static final String PARTITION_TABLE_PREFIX = "customizablefieldvalue_"; - - @ParameterizedTest - @EnumSource(CustomizableFieldContext.class) - void testPartitionTableExistsForContext(CustomizableFieldContext context) throws IOException, URISyntaxException { - String schema = new String( - Files.readAllBytes(Paths.get(Objects.requireNonNull(getClass().getClassLoader().getResource("sql/sormas_schema.sql")).toURI()))); - - String expectedTableName = PARTITION_TABLE_PREFIX + context.name().toLowerCase(); - String expectedStatement = "CREATE TABLE " + expectedTableName; - - assertTrue( - schema.contains(expectedStatement), - "Missing partition table declaration in sormas_schema.sql for CustomizableFieldContext." + context.name() + ": expected to find '" - + expectedStatement + "'. Add a PARTITION OF customizablefieldvalue FOR VALUES IN ('" + context.name() - + "') migration alongside the new enum value."); - } -} From c841d1b7f3bfc46c2d74a7f25461a79b443e8154 Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Mon, 20 Apr 2026 11:28:38 +0200 Subject: [PATCH 55/55] Fixed history tables --- sormas-backend/src/main/resources/sql/sormas_schema.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 48eccfe6ed0..4095230d62c 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15627,7 +15627,7 @@ CREATE TABLE customizablefieldmetadata_history (LIKE customizablefieldmetadata); DROP TRIGGER IF EXISTS versioning_trigger ON customizablefieldmetadata; CREATE TRIGGER versioning_trigger -BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldmetadata +BEFORE INSERT OR UPDATE ON customizablefieldmetadata FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldmetadata_history', true); DROP TRIGGER IF EXISTS delete_history_trigger ON customizablefieldmetadata; @@ -15686,7 +15686,7 @@ CREATE TABLE customizablefieldvalue_history (LIKE customizablefieldvalue); DROP TRIGGER IF EXISTS versioning_trigger ON customizablefieldvalue; CREATE TRIGGER versioning_trigger -BEFORE INSERT OR UPDATE OR DELETE ON customizablefieldvalue +BEFORE INSERT OR UPDATE ON customizablefieldvalue FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'customizablefieldvalue_history', true); DROP TRIGGER IF EXISTS delete_history_trigger ON customizablefieldvalue;