diff --git a/build.gradle.kts b/build.gradle.kts index 3633fa061..51772d668 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ java { allprojects { group = "fr.insee.eno" - version = "3.29.3-SNAPSHOT" + version = "3.30.0-SNAPSHOT.1" } subprojects { diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddControlFormat.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddControlFormat.java index 2e03a0d5f..5421c1dfb 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddControlFormat.java +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddControlFormat.java @@ -2,6 +2,7 @@ import fr.insee.eno.core.processing.ProcessingStep; import fr.insee.lunatic.model.flat.*; +import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.math.RoundingMode; @@ -14,6 +15,7 @@ * Processing adding format controls to components * Format controls are controls generated by the min/max/decimals attributes of the Datepicker/InputNumber components */ +@Slf4j public class LunaticAddControlFormat implements ProcessingStep { /** * @@ -114,21 +116,21 @@ private List getFormatControlsFromInputNumberAttributes(String id, String maxValue = formatDoubleValue(max, decimalsCount); String controlExpression = String.format("not(not(isnull(%s)) and (%s>%s or %s<%s))", responseName, minValue, responseName, maxValue, responseName); String controlErrorMessage = String.format("\" La valeur doit être comprise entre %s et %s.\"", minValue, maxValue); - controls.add(0, createFormatControl(controlIdPrefix+"-borne-inf-sup", controlExpression, controlErrorMessage)); + controls.addFirst(createFormatControl(controlIdPrefix+"-borne-inf-sup", controlExpression, controlErrorMessage)); } if(min == null && max != null) { String maxValue = formatDoubleValue(max, decimalsCount); String controlExpression = String.format("not(not(isnull(%s)) and %s<%s)", responseName, maxValue, responseName); String controlErrorMessage = String.format("\" La valeur doit être inférieure à %s.\"", maxValue); - controls.add(0, createFormatControl(controlIdPrefix+"-borne-sup", controlExpression, controlErrorMessage)); + controls.addFirst(createFormatControl(controlIdPrefix+"-borne-sup", controlExpression, controlErrorMessage)); } if(min != null && max == null) { String minValue = formatDoubleValue(min, decimalsCount); String controlExpression = String.format("not(not(isnull(%s)) and %s>%s)", responseName, minValue, responseName); String controlErrorMessage = String.format("\" La valeur doit être supérieure à %s.\"", minValue); - controls.add(0, createFormatControl(controlIdPrefix+"-borne-inf", controlExpression, controlErrorMessage)); + controls.addFirst(createFormatControl(controlIdPrefix+"-borne-inf", controlExpression, controlErrorMessage)); } controls.add(createDecimalsFormatControl(controlIdPrefix, responseName, decimalsCount)); @@ -148,9 +150,12 @@ private void createFormatControlsForDatepicker(Datepicker datepicker) { List controls = datepicker.getControls(); - Optional control = getFormatControlFromDatepickerAttributes(id, minValue, maxValue, format, responseName); - - control.ifPresent(controlType -> controls.add(0, controlType)); + Optional controlYearFormat = generateDatepickerYearControl(id, format, responseName); + Optional controlBounds = getFormatControlFromDatepickerAttributes(id, minValue, maxValue, format, responseName); + controlBounds.ifPresent(controls::addFirst); + controlYearFormat.ifPresent(controls::addFirst); + // Note: it's important that the year format is added in first position, since in some cases only the message + // of the first control is displayed. } /** @@ -188,6 +193,20 @@ private Optional getFormatControlFromDatepickerAttributes(String id } return Optional.empty(); } + private Optional generateDatepickerYearControl(String id, String format, String responseName) { + if (format == null || !format.contains("YYYY")) { + log.warn("Datepicker '{}' (id={}) format is {} which doesn't have the year (YYYY).", + responseName, id, format); + return Optional.empty(); + } + String controlId = id + "-format-year"; + String expression = String.format("not(not(isnull(%s)) and (" + + "cast(cast(cast(%s, date, \"%s\"), string, \"YYYY\"), integer) <= 999 or " + + "cast(cast(cast(%s, date, \"%s\"), string, \"YYYY\"), integer) > 9999))", + responseName, responseName, format, responseName, format); + String message = "\"L'année doit être saisie avec 4 chiffres.\""; + return Optional.of(createFormatControl(controlId, expression, message)); + } /** * diff --git a/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddControlFormatTest.java b/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddControlFormatTest.java index 52e8491bb..fdb6e3405 100644 --- a/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddControlFormatTest.java +++ b/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddControlFormatTest.java @@ -2,6 +2,7 @@ import fr.insee.lunatic.model.flat.*; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.math.BigInteger; @@ -25,13 +26,13 @@ void init() { datePicker = new Datepicker(); datePicker.setComponentType(ComponentTypeEnum.DATEPICKER); datePicker.setId("datepicker-id"); - datePicker.setResponse(buildResponse("DATEVAR")); + datePicker.setResponse(buildResponse("DATE_VAR")); number = new InputNumber(); number.setComponentType(ComponentTypeEnum.INPUT_NUMBER); number.setId("number-id"); number.setDecimals(BigInteger.ZERO); - number.setResponse(buildResponse("NUMBERVAR")); + number.setResponse(buildResponse("NUMBER_VAR")); } @Test @@ -45,9 +46,9 @@ void shouldInputNumberHaveDecimalsControl() { List controls = number.getControls(); assertEquals(1, controls.size()); - ControlType control = controls.get(0); + ControlType control = controls.getFirst(); - assertEquals("not(not(isnull(NUMBERVAR)) and round(NUMBERVAR,10)<>NUMBERVAR)", control.getControl().getValue()); + assertEquals("not(not(isnull(NUMBER_VAR)) and round(NUMBER_VAR,10)<>NUMBER_VAR)", control.getControl().getValue()); assertEquals(LabelTypeEnum.VTL, control.getControl().getType()); assertEquals(LabelTypeEnum.VTL_MD, control.getErrorMessage().getType()); assertEquals("number-id-format-decimal", control.getId()); @@ -68,8 +69,8 @@ void shouldInputNumberHaveScaleOnDecimalValuesWhenDecimalsIsSet() { List controls = number.getControls(); - ControlType control = controls.get(0); - assertEquals("not(not(isnull(NUMBERVAR)) and (5.2400000000>NUMBERVAR or 10.1200000000NUMBER_VAR or 10.1200000000 controls = number.getControls(); assertEquals(2, controls.size()); - ControlType control = controls.get(0); + ControlType control = controls.getFirst(); - assertEquals("not(not(isnull(NUMBERVAR)) and (5>NUMBERVAR or 10NUMBER_VAR or 10 controls = number.getControls(); assertEquals(2, controls.size()); - ControlType control = controls.get(0); + ControlType control = controls.getFirst(); - assertEquals("not(not(isnull(NUMBERVAR)) and 5>NUMBERVAR)", control.getControl().getValue()); + assertEquals("not(not(isnull(NUMBER_VAR)) and 5>NUMBER_VAR)", control.getControl().getValue()); assertEquals(LabelTypeEnum.VTL, control.getControl().getType()); assertEquals(LabelTypeEnum.VTL_MD, control.getErrorMessage().getType()); assertEquals("number-id-format-borne-inf", control.getId()); @@ -131,9 +132,9 @@ void shouldInputNumberHaveMaxFormatControl() { List controls = number.getControls(); assertEquals(2, controls.size()); - ControlType control = controls.get(0); + ControlType control = controls.getFirst(); - assertEquals("not(not(isnull(NUMBERVAR)) and 10 controls = datePicker.getControls(); + assertEquals(1, controls.size()); + ControlType yearControl = controls.getFirst(); + + assertEquals("datepicker-id-format-year", yearControl.getId()); + String expected = "not(not(isnull(DATE_VAR)) and (" + + "cast(cast(cast(DATE_VAR, date, \"YYYY-MM-DD\"), string, \"YYYY\"), integer) <= 999 or " + + "cast(cast(cast(DATE_VAR, date, \"YYYY-MM-DD\"), string, \"YYYY\"), integer) > 9999))"; + assertEquals(expected, yearControl.getControl().getValue()); + assertEquals(LabelTypeEnum.VTL, yearControl.getControl().getType()); + assertEquals(LabelTypeEnum.VTL_MD, yearControl.getErrorMessage().getType()); + assertEquals(ControlTypeEnum.FORMAT, yearControl.getTypeOfControl()); + assertEquals(ControlCriticalityEnum.ERROR, yearControl.getCriticality()); + } + + @Test + @DisplayName("Datepicker: min and max format control") + void datepickerMinMaxFormatControl() { lunaticQuestionnaire = new Questionnaire(); lunaticQuestionnaire.getComponents().add(datePicker); datePicker.setMin("2020-01-01"); @@ -159,19 +186,23 @@ void shouldDatepickerHaveMinMaxFormatControl() { processing.apply(lunaticQuestionnaire); List controls = datePicker.getControls(); - assertEquals(1, controls.size()); - ControlType control = controls.get(0); + assertEquals(2, controls.size()); + ControlType control = controls.get(1); - assertEquals("not(not(isnull(DATEVAR)) and (cast(DATEVAR, date, \"YYYY-MM-DD\")cast(\"2023-01-01\", date, \"YYYY-MM-DD\")))", control.getControl().getValue()); + assertEquals("datepicker-id-format-date-borne-inf-sup", control.getId()); + String expected = "not(not(isnull(DATE_VAR)) and " + + "(cast(DATE_VAR, date, \"YYYY-MM-DD\")cast(\"2023-01-01\", date, \"YYYY-MM-DD\")))"; + assertEquals(expected, control.getControl().getValue()); assertEquals(LabelTypeEnum.VTL, control.getControl().getType()); assertEquals(LabelTypeEnum.VTL_MD, control.getErrorMessage().getType()); - assertEquals("datepicker-id-format-date-borne-inf-sup", control.getId()); assertEquals(ControlTypeEnum.FORMAT, control.getTypeOfControl()); assertEquals(ControlCriticalityEnum.ERROR, control.getCriticality()); } @Test - void shouldDatepickerHaveMinFormatControl() { + @DisplayName("Datepicker: min format control") + void datepickerMinFormatControl() { lunaticQuestionnaire = new Questionnaire(); lunaticQuestionnaire.getComponents().add(datePicker); datePicker.setMin("2020-01-01"); @@ -179,19 +210,22 @@ void shouldDatepickerHaveMinFormatControl() { processing.apply(lunaticQuestionnaire); List controls = datePicker.getControls(); - assertEquals(1, controls.size()); - ControlType control = controls.get(0); + assertEquals(2, controls.size()); + ControlType control = controls.get(1); - assertEquals("not(not(isnull(DATEVAR)) and (cast(DATEVAR, date, \"YYYY-MM-DD\") controls = datePicker.getControls(); - assertEquals(1, controls.size()); - ControlType control = controls.get(0); + assertEquals(2, controls.size()); + ControlType boundsControl = controls.get(1); + + assertEquals("datepicker-id-format-date-borne-sup", boundsControl.getId()); + String expected = "not(not(isnull(DATE_VAR)) " + + "and (cast(DATE_VAR, date, \"YYYY-MM-DD\")>cast(\"2023-01-01\", date, \"YYYY-MM-DD\")))"; + assertEquals(expected, boundsControl.getControl().getValue()); + assertEquals(LabelTypeEnum.VTL, boundsControl.getControl().getType()); + assertEquals(LabelTypeEnum.VTL_MD, boundsControl.getErrorMessage().getType()); + assertEquals(ControlTypeEnum.FORMAT, boundsControl.getTypeOfControl()); + assertEquals(ControlCriticalityEnum.ERROR, boundsControl.getCriticality()); + } - assertEquals("not(not(isnull(DATEVAR)) and (cast(DATEVAR, date, \"YYYY-MM-DD\")>cast(\"2023-01-01\", date, \"YYYY-MM-DD\")))", control.getControl().getValue()); - assertEquals(LabelTypeEnum.VTL, control.getControl().getType()); - assertEquals(LabelTypeEnum.VTL_MD, control.getErrorMessage().getType()); - assertEquals("datepicker-id-format-date-borne-sup", control.getId()); - assertEquals(ControlTypeEnum.FORMAT, control.getTypeOfControl()); - assertEquals(ControlCriticalityEnum.ERROR, control.getCriticality()); + @Test + @DisplayName("Datepicker: year format control when min/max is set") + void datepickerYearAndMinMaxFormatControl() { + lunaticQuestionnaire = new Questionnaire(); + lunaticQuestionnaire.getComponents().add(datePicker); + datePicker.setMax("2023-01-01"); + datePicker.setDateFormat("YYYY-MM-DD"); + processing.apply(lunaticQuestionnaire); + + List controls = datePicker.getControls(); + assertEquals(2, controls.size()); + ControlType yearControl = controls.get(0); + ControlType boundsControl = controls.get(1); + // Only testing ids here to check that the order is right, content is tested in other tests + assertEquals("datepicker-id-format-year", yearControl.getId()); + assertEquals("datepicker-id-format-date-borne-sup", boundsControl.getId()); } @Test @@ -226,7 +280,7 @@ void shouldLoopComponentsBeProcessed() { processing.apply(lunaticQuestionnaire); - assertEquals(1, datePicker.getControls().size()); + assertEquals(2, datePicker.getControls().size()); assertEquals(2, number.getControls().size()); } @@ -240,12 +294,12 @@ void shouldTableComponentsHaveSubComponentsControls() { List bodyCells = new ArrayList<>(); bodyCells.add(buildBodyCell("line1")); - bodyCells.add(buildBodyCell(table.getId()+"-co", "CHECKVAR", ComponentTypeEnum.CHECKBOX_ONE)); + bodyCells.add(buildBodyCell(table.getId()+"-co", "CHECKBOX_VAR", ComponentTypeEnum.CHECKBOX_ONE)); bodyLines.add(buildBodyLine(bodyCells)); bodyCells = new ArrayList<>(); bodyCells.add(buildBodyCell("line2")); - bodyCells.add(buildBodyCell(table.getId()+"-number", "NUMBERVAR", ComponentTypeEnum.INPUT_NUMBER, BigInteger.TWO, 2.0, 5.0)); + bodyCells.add(buildBodyCell(table.getId()+"-number", "NUMBER_VAR", ComponentTypeEnum.INPUT_NUMBER, BigInteger.TWO, 2.0, 5.0)); bodyLines.add(buildBodyLine(bodyCells)); lunaticQuestionnaire = new Questionnaire(); @@ -266,8 +320,8 @@ void shouldRosterComponentsHaveSubComponentsControls() { List bodyCells = roster.getComponents(); bodyCells.add(buildBodyCell("line1")); - bodyCells.add(buildBodyCell(roster.getId()+"-number", "NUMBERVAR", ComponentTypeEnum.INPUT_NUMBER, BigInteger.TWO, 2.0, 5.0)); - bodyCells.add(buildBodyCell(roster.getId()+"-co", "CHECKVAR", ComponentTypeEnum.CHECKBOX_ONE)); + bodyCells.add(buildBodyCell(roster.getId()+"-number", "NUMBER_VAR", ComponentTypeEnum.INPUT_NUMBER, BigInteger.TWO, 2.0, 5.0)); + bodyCells.add(buildBodyCell(roster.getId()+"-co", "CHECKBOX_VAR", ComponentTypeEnum.CHECKBOX_ONE)); lunaticQuestionnaire = new Questionnaire(); lunaticQuestionnaire.getComponents().add(roster);