diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldValidator.java b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldValidator.java index 6d3fda2812d..595786ab758 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldValidator.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldValidator.java @@ -59,9 +59,25 @@ public boolean isValid(DatasetField value, ConstraintValidatorContext context) { } // if value is not primitive or not empty - if (!dsfType.isPrimitive() || !StringUtils.isBlank(value.getValue())) { + // For controlled vocabulary fields, check that actual CV values are selected, + // not just that datasetFieldValues contains something (which might be an invalid N/A placeholder) + // See https://github.com/IQSS/dataverse/issues/11900 + if (!dsfType.isPrimitive()) { return true; } + + if (dsfType.isControlledVocabulary()) { + // For CV fields, check if there are actual controlled vocabulary values selected + if (value.getControlledVocabularyValues() != null && !value.getControlledVocabularyValues().isEmpty()) { + return true; + } + // If no CV values, fall through to required field check below + } else { + // For non-CV primitive fields, check if value is not blank + if (!StringUtils.isBlank(value.getValue())) { + return true; + } + } if (value.isRequired()) { String errorMessage = null; diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index 20617160a1c..5168c03a675 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -4005,8 +4005,8 @@ public String save() { dataset.setOwner(ownerId != null ? dataverseService.find(ownerId) : null); } // Validate - Set constraintViolations = workingVersion.validate(); - if (!constraintViolations.isEmpty()) { + workingVersion.validate(); // add validation messages to dataset fields + if (!workingVersion.isValid()) { FacesContext.getCurrentInstance().validationFailed(); return ""; } diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java index 15faaad5d52..55ec6c555fb 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java @@ -110,10 +110,14 @@ protected void validateOrDie(DatasetVersion dsv, Boolean lenient) throws Command Set constraintViolations = dsv.validate(); if (!constraintViolations.isEmpty()) { if (lenient) { - // populate invalid fields with N/A + // populate invalid primitive fields with N/A + // Note: controlled vocabulary fields should NOT get N/A values in datasetfieldvalue, + // as this creates an inconsistent state where the CV field appears valid but is empty. + // See https://github.com/IQSS/dataverse/issues/11900 constraintViolations.stream() .filter(cv -> cv.getRootBean() instanceof DatasetField) .map(cv -> ((DatasetField) cv.getRootBean())) + .filter(f -> !f.getDatasetFieldType().isControlledVocabulary()) .forEach(f -> f.setSingleValue(DatasetField.NA_VALUE)); } else {