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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sormas-api/src/main/java/de/symeda/sormas/api/Disease.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public enum Disease
INFLUENZA_B(false, true, true, false, false, 0, true, false, false),
H_METAPNEUMOVIRUS(true, false, true, false, false, 0, true, false, false),
RESPIRATORY_SYNCYTIAL_VIRUS(true, false, true, false, false, 0, true, false, false),
PARAINFLUENZA_1_4(true, false, true, false, false, 0, true, false, false),
PARAINFLUENZA_1_4(false, false, true, false, false, 0, true, false, false),
ADENOVIRUS(true, false, true, false, false, 0, true, false, false),
RHINOVIRUS(true, false, true, false, false, 0, true, false, false),
ENTEROVIRUS(true, false, true, false, false, 0, true, false, false),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2275,6 +2275,8 @@ public interface Captions {
String PathogenTest_prescriberPostalCode = "PathogenTest.prescriberPostalCode";
String PathogenTest_reportDate = "PathogenTest.reportDate";
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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ public enum PathogenTestType {

@Diseases(value = {
Disease.RESPIRATORY_SYNCYTIAL_VIRUS,
Disease.MEASLES })
Disease.MEASLES,
Disease.INVASIVE_PNEUMOCOCCAL_INFECTION,
Disease.INVASIVE_MENINGOCOCCAL_INFECTION })
SEQUENCING,

@Diseases(value = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2525,7 +2525,8 @@ public static SymptomsDto build() {
UNDEFINED,
OTHER })
@HideForCountriesExcept(countries = {
CountryHelper.COUNTRY_CODE_SWITZERLAND })
CountryHelper.COUNTRY_CODE_SWITZERLAND,
CountryHelper.COUNTRY_CODE_LUXEMBOURG })
@SymptomGrouping(SymptomGroup.GENERAL)
private SymptomState fatigue;
@Diseases({
Expand Down
4 changes: 3 additions & 1 deletion sormas-api/src/main/resources/captions.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1149,7 +1149,7 @@ EpiData.caseImportedStatus=Case imported status
EpiData.clusterRelated=Cluster Related
EpiData.clusterType=Cluster type
EpiData.clusterTypeText=Specify cluster type text
EpiData.modeOfTransmission=Mode of transmission
EpiData.modeOfTransmission=Suspected main mode of transmission
EpiData.modeOfTransmissionType= Specify mode of transmission
EpiData.infectionSource= Suspected vehicle or source of infection
EpiData.infectionSourceText= Specify source of infection
Expand Down Expand Up @@ -1872,6 +1872,7 @@ PathogenTest.pcrTestSpecification=PCR/RT-PCR test specification
PathogenTest.testTypeText=Specify test details
PathogenTest.testedDisease=Tested disease
PathogenTest.testedDiseaseVariant=Tested disease variant
PathogenTest.rsv.testedDiseaseVariant=Tested disease subtype
PathogenTest.testedDiseaseDetails=Tested disease name
PathogenTest.testedPathogen=Tested pathogen
PathogenTest.testedPathogenDetails=Tested pathogen details
Expand All @@ -1884,6 +1885,7 @@ PathogenTest.externalId=External ID
PathogenTest.reportDate=Report date
PathogenTest.viaLims=Via LIMS
PathogenTest.testedDiseaseVariantDetails=Disease variant details
PathogenTest.rsv.testedDiseaseVariantDetails=Disease subtype details
PathogenTest.externalOrderId=External order ID
PathogenTest.preliminary=Preliminary
PathogenTest.ctValueE=Ct target E
Expand Down
6 changes: 3 additions & 3 deletions sormas-api/src/main/resources/enum.properties
Original file line number Diff line number Diff line change
Expand Up @@ -775,9 +775,9 @@ ExposureType.BURIAL=Burial
ExposureType.ANIMAL_CONTACT=Animal Contact
ExposureType.OTHER=Other
ExposureType.UNKNOWN=Unknown
ExposureType.RECREATIONAL_WATER=Exposure to recreational water
ExposureType.FOOD=Exposure to food
ExposureType.SEXUAL_CONTACT=Sexual exposure
ExposureType.RECREATIONAL_WATER=Recreational water
ExposureType.FOOD=Food
ExposureType.SEXUAL_CONTACT=Sexual contact
ExposureType.SYMPTOMATIC_CONTACT=Contact with symptomatic individuals
ExposureType.FLOOD_EXPOSURE=Flooded area

Expand Down
7 changes: 7 additions & 0 deletions sormas-backend/src/main/resources/sql/sormas_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14820,4 +14820,11 @@ ALTER TABLE symptoms_history ALTER COLUMN timeoffworkdays TYPE float4 USING time
ALTER TABLE healthconditions_history DROP COLUMN IF EXISTS immunodepression;

INSERT INTO schema_version (version_number, comment) VALUES (595, 'RSV issue fixes and minor observations of Giardiasis and Cryptosporidiosis #13540 #13613');

-- 2025-10-29 - Included new Disease variant/subtype for RSV #13543
INSERT INTO customizableenumvalue(id, uuid, changedate, creationdate, datatype, value, caption, diseases)
VALUES (nextval('entity_seq'), generate_base32_uuid(), now(), now(), 'DISEASE_VARIANT', 'INDETERMINATE', 'Indeterminate',
'RESPIRATORY_SYNCYTIAL_VIRUS');
INSERT INTO schema_version (version_number, comment) VALUES (596, 'Included new Disease variant/subtype for RSV #13543');

-- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. ***
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ protected void addFields() {
TextField diseaseVariantDetailsField = addField(CaseDataDto.DISEASE_VARIANT_DETAILS, TextField.class);
diseaseVariantDetailsField.setVisible(false);
diseaseVariantField.setNullSelectionAllowed(true);
if (disease == Disease.RESPIRATORY_SYNCYTIAL_VIRUS) {
diseaseVariantField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariant));
diseaseVariantDetailsField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariantDetails));
}
addField(CaseDataDto.DISEASE_DETAILS, TextField.class);
addField(CaseDataDto.PLAGUE_TYPE, NullableOptionGroup.class);
addField(CaseDataDto.DENGUE_FEVER_TYPE, NullableOptionGroup.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,8 @@ protected void addFields() {
EventDto.EVENT_INVESTIGATION_STATUS,
Arrays.asList(EventInvestigationStatus.ONGOING, EventInvestigationStatus.DONE, EventInvestigationStatus.DISCARDED),
true);
DateComparisonValidator.addStartEndValidators(investigationStartDate, investigationEndDate, false);
DateComparisonValidator.addStartEndValidators(startDate, investigationStartDate, false);
DateComparisonValidator.addStartEndValidators(investigationStartDate, investigationEndDate, true);
DateComparisonValidator.addStartEndValidators(startDate, investigationStartDate, true);
TextField title = addField(EventDto.EVENT_TITLE, TextField.class);
title.addStyleName(CssStyles.SOFT_REQUIRED);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,15 @@ protected void addFields() {
initializeAccessAndAllowedAccesses();

if (List.of(Disease.GIARDIASIS, Disease.CRYPTOSPORIDIOSIS).contains(caze.getDisease())) {
FieldHelper
.setRequiredWhenNotNull(getFieldGroup(), HospitalizationDto.ADMITTED_TO_HEALTH_FACILITY, HospitalizationDto.HOSPITALIZATION_REASON);
// Make hospitalization reason required when admitted to health facility or currently hospitalized
admittedToHealthFacilityField.addValueChangeListener(e -> {
YesNoUnknown value = (YesNoUnknown) admittedToHealthFacilityField.getNullableValue();
hospitalizationReason.setRequired(value == YesNoUnknown.YES);
});
currentlyHospitalizedField.addValueChangeListener(e -> {
YesNoUnknown value = (YesNoUnknown) currentlyHospitalizedField.getNullableValue();
hospitalizationReason.setRequired(value == YesNoUnknown.YES);
});
durationOfHospitalization.setVisible(true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ protected void addValidators() {
new DateComparisonValidator(
shipmentDate,
sampleDateField,
false,
true,
false,
I18nProperties.getValidationError(Validations.afterDate, shipmentDate.getCaption(), sampleDateField.getCaption())));
shipmentDate.addValidator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs;
import static de.symeda.sormas.ui.utils.LayoutUtil.loc;

import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
Expand Down Expand Up @@ -302,7 +304,7 @@ public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Co
testTypeField.setValue(newFieldValue.getTestType());
pcrTestSpecification.setValue(newFieldValue.getPcrTestSpecification());
testTypeTextField.setValue(newFieldValue.getTestTypeText());
if(!testResultField.isReadOnly()) {
if (!testResultField.isReadOnly()) {
testResultField.setValue(newFieldValue.getTestResult());
}
typingIdField.setValue(newFieldValue.getTypingId());
Expand Down Expand Up @@ -331,17 +333,37 @@ protected void addFields() {
testTypeTextField = addField(PathogenTestDto.TEST_TYPE_TEXT, TextField.class);
FieldHelper.addSoftRequiredStyle(testTypeTextField);
DateTimeField testDateField = addField(PathogenTestDto.TEST_DATE_TIME, DateTimeField.class);
testDateField.removeAllValidators();
testDateField.addValidator(
new DateComparisonValidator(
testDateField,
this::getSampleDate,
false,
false,
true,
I18nProperties.getValidationError(
Validations.afterDateWithDate,
testDateField.getCaption(),
I18nProperties.getPrefixCaption(SampleDto.I18N_PREFIX, SampleDto.SAMPLE_DATE_TIME),
DateFormatHelper.formatDate(getSampleDate()))));
testDateField.addValueChangeListener(e -> {
boolean hasTime = !getSampleDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().equals(LocalTime.MIDNIGHT);
if (hasTime) {
testDateField.removeAllValidators();
testDateField.addValidator(
new DateComparisonValidator(
testDateField,
this::getSampleDate,
false,
false,
false,
I18nProperties.getValidationError(
Validations.afterDateWithDate,
testDateField.getCaption(),
I18nProperties.getPrefixCaption(SampleDto.I18N_PREFIX, SampleDto.SAMPLE_DATE_TIME),
DateFormatHelper.formatLocalDateTime(getSampleDate()))));
}
});
Comment on lines +336 to +366

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Type mismatch in date-time comparison logic.

Line 350 contains a critical logic error:

boolean hasTime = !getSampleDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().equals(LocalTime.MIDNIGHT);

This compares a LocalDate object with LocalTime.MIDNIGHT, which are incompatible types and will never be equal. The condition will always evaluate to true, breaking the intended dynamic validator adjustment.

Apply this fix:

-		boolean hasTime = !getSampleDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().equals(LocalTime.MIDNIGHT);
+		boolean hasTime = !getSampleDate().toInstant().atZone(ZoneId.systemDefault()).toLocalTime().equals(LocalTime.MIDNIGHT);
🤖 Prompt for AI Agents
In sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java
around lines 336 to 366, the hasTime check incorrectly compares a LocalDate to
LocalTime.MIDNIGHT causing the condition to always be true; replace the
toLocalDate() call with toLocalTime() so you check the time-of-day portion
(e.g., hasTime =
!getSampleDate().toInstant().atZone(ZoneId.systemDefault()).toLocalTime().equals(LocalTime.MIDNIGHT)),
leaving the rest of the validator logic intact.

ComboBox lab = addInfrastructureField(PathogenTestDto.LAB);
lab.addItems(FacadeProvider.getFacilityFacade().getAllActiveLaboratories(true));
TextField labDetails = addField(PathogenTestDto.LAB_DETAILS, TextField.class);
Expand All @@ -354,9 +376,13 @@ protected void addFields() {
addField(PathogenTestDto.TESTED_DISEASE_DETAILS, TextField.class);
ComboBox diseaseVariantField = addCustomizableEnumField(PathogenTestDto.TESTED_DISEASE_VARIANT);
diseaseVariantField.setNullSelectionAllowed(true);
diseaseVariantField.setVisible(false);
TextField diseaseVariantDetailsField = addField(PathogenTestDto.TESTED_DISEASE_VARIANT_DETAILS, TextField.class);
diseaseVariantDetailsField.setVisible(false);

if (disease == Disease.RESPIRATORY_SYNCYTIAL_VIRUS) {
diseaseVariantField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariant));
diseaseVariantDetailsField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariantDetails));
}
ComboBox genoTypingCB = addField(PathogenTestDto.GENOTYPE_RESULT, ComboBox.class);
genoTypingCB.setVisible(true);
TextField genoTypingResultTextTF = addField(PathogenTestDto.GENOTYPE_RESULT_TEXT, TextField.class);
Expand Down Expand Up @@ -492,7 +518,11 @@ protected void addFields() {
{
put(
PathogenTestDto.TESTED_DISEASE,
Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS, Disease.INVASIVE_MENINGOCOCCAL_INFECTION, Disease.INVASIVE_PNEUMOCOCCAL_INFECTION));
Arrays.asList(
Disease.TUBERCULOSIS,
Disease.LATENT_TUBERCULOSIS,
Disease.INVASIVE_MENINGOCOCCAL_INFECTION,
Disease.INVASIVE_PNEUMOCOCCAL_INFECTION));
put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY));
}
};
Expand All @@ -503,7 +533,11 @@ protected void addFields() {
{
put(
PathogenTestDto.TESTED_DISEASE,
Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS, Disease.INVASIVE_MENINGOCOCCAL_INFECTION, Disease.INVASIVE_PNEUMOCOCCAL_INFECTION));
Arrays.asList(
Disease.TUBERCULOSIS,
Disease.LATENT_TUBERCULOSIS,
Disease.INVASIVE_MENINGOCOCCAL_INFECTION,
Disease.INVASIVE_PNEUMOCOCCAL_INFECTION));
put(
PathogenTestDto.TEST_TYPE,
Arrays.asList(
Expand Down Expand Up @@ -1037,6 +1071,17 @@ protected void addFields() {
FieldHelper
.setVisibleWhen(getFieldGroup(), PathogenTestDto.GENOTYPE_RESULT_TEXT, PathogenTestDto.GENOTYPE_RESULT, GenoTypeResult.OTHER, true);

//RSV subtype specification
Map<Object, List<Object>> rsvSubTypeDependencies = new HashMap<>() {

{
put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.RESPIRATORY_SYNCYTIAL_VIRUS));
put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.SEQUENCING, PathogenTestType.WHOLE_GENOME_SEQUENCING));
put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE));
}
};
FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.TESTED_DISEASE_VARIANT, rsvSubTypeDependencies, true);

Consumer<Disease> updateDiseaseVariantField = disease -> {
List<DiseaseVariant> diseaseVariants =
FacadeProvider.getCustomizableEnumFacade().getEnumValues(CustomizableEnumType.DISEASE_VARIANT, disease);
Expand Down Expand Up @@ -1103,7 +1148,7 @@ protected void addFields() {
ImmutableList.of(PathogenTestType.GENOTYPING));

BiConsumer<Disease, PathogenTestType> resultField = (disease, testType) -> {
if(testResultField.isReadOnly()) {
if (testResultField.isReadOnly()) {
return;
}
if (resultFieldDecisionMap.containsKey(disease) && resultFieldDecisionMap.get(disease).contains(testType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public PathogenTestListEntry(PathogenTestDto pathogenTest, boolean showTestResul

if (pathogenTest.getTestedDiseaseVariant() != null) {
Label labelBottomLeft = new Label(pathogenTest.getTestedDiseaseVariant().toString());
CssStyles.style(labelBottomLeft, CssStyles.LABEL_BOLD, CssStyles.LABEL_UPPERCASE, CssStyles.LABEL_CRITICAL);
bottomLabelLayout.addComponent(labelBottomLeft);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ public DateComparisonValidator(
this.changeInvalidCommitted = changeInvalidCommitted;
}

/**
* To compare the time along with dates, set dateOnly to false.
*
* @param dateField
* @param referenceDateSupplier
* @param earlierOrSame
* @param changeInvalidCommitted
* @param dateOnly
* @param errorMessage
*/
public DateComparisonValidator(
Field<Date> dateField,
Supplier<Date> referenceDateSupplier,
boolean earlierOrSame,
boolean changeInvalidCommitted,
boolean dateOnly,
String errorMessage) {

super(errorMessage);
this.dateField = dateField;
this.referenceDateSupplier = referenceDateSupplier;
this.earlierOrSame = earlierOrSame;
this.dateOnly = dateOnly;
this.changeInvalidCommitted = changeInvalidCommitted;
}

public DateComparisonValidator(
Field<Date> dateField,
Supplier<Date> referenceDateSupplier,
Expand Down Expand Up @@ -94,7 +120,6 @@ public DateComparisonValidator(
this(dateField, () -> referenceDate, earlierOrSame, changeInvalidCommitted, errorMessage);
}


@Override
protected boolean isValidValue(Date date) {

Expand Down
Loading