diff --git a/frontend/src/locale/de.json b/frontend/src/locale/de.json
index e56a3e7a8e..b4a529312e 100644
--- a/frontend/src/locale/de.json
+++ b/frontend/src/locale/de.json
@@ -454,6 +454,8 @@
"BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Sieh dir die {wildbookDocsLink} an oder kontaktiere den Support",
"BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Wildbook-Dokumentation",
"BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Schließen",
+ "BULKIMPORT_ERROR_REQUIRE_PREFIX": "Erforderliches Projekt-ID-Präfix fehlt",
+ "BULKIMPORT_ERROR_REQUIRE_NAME": "Erforderlicher Forschungsprojektname fehlt",
"PHOTOS_UPLOADED_TITLE": "hochgeladene Fotos: {count}",
"PHOTOS_UPLOADED": "Fotos hochgeladen",
"PHOTOS_FAILED": "fehlgeschlagene Fotos",
diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json
index e418cdab76..e99c73c379 100644
--- a/frontend/src/locale/en.json
+++ b/frontend/src/locale/en.json
@@ -453,6 +453,8 @@
"BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Check the {wildbookDocsLink} for troubleshooting or contact support",
"BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Wildbook Docs",
"BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Close",
+ "BULKIMPORT_ERROR_REQUIRE_PREFIX": "missing required project ID prefix",
+ "BULKIMPORT_ERROR_REQUIRE_NAME": "missing required research project name",
"PHOTOS_UPLOADED_TITLE": "photos uploaded: {count}",
"PHOTOS_UPLOADED": "photos uploaded",
"PHOTOS_FAILED": "photos failed",
diff --git a/frontend/src/locale/es.json b/frontend/src/locale/es.json
index 49a06d6626..1bf05f3e57 100644
--- a/frontend/src/locale/es.json
+++ b/frontend/src/locale/es.json
@@ -452,6 +452,8 @@
"BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Consulta {wildbookDocsLink} para resolución de problemas o contacto con soporte",
"BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Documentación de Wildbook",
"BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Cerrar",
+ "BULKIMPORT_ERROR_REQUIRE_PREFIX": "falta el prefijo de ID del proyecto obligatorio",
+ "BULKIMPORT_ERROR_REQUIRE_NAME": "falta el nombre del proyecto de investigación obligatorio",
"PHOTOS_UPLOADED_TITLE": "fotos subidas: {count}",
"PHOTOS_UPLOADED": "fotos subidas",
"PHOTOS_FAILED": "fotos fallidas",
diff --git a/frontend/src/locale/fr.json b/frontend/src/locale/fr.json
index 9ddfda35c8..613b513a3f 100644
--- a/frontend/src/locale/fr.json
+++ b/frontend/src/locale/fr.json
@@ -452,6 +452,8 @@
"BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Consultez {wildbookDocsLink} pour le dépannage ou contactez le support",
"BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Docs Wildbook",
"BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Fermer",
+ "BULKIMPORT_ERROR_REQUIRE_PREFIX": "préfixe d’identifiant de projet requis manquant",
+ "BULKIMPORT_ERROR_REQUIRE_NAME": "nom du projet de recherche requis manquant",
"PHOTOS_UPLOADED_TITLE": "photos téléchargées : {count}",
"PHOTOS_UPLOADED": "photos téléchargées",
"PHOTOS_FAILED": "échecs photos",
diff --git a/frontend/src/locale/it.json b/frontend/src/locale/it.json
index 53e4f93f2b..8c017c9635 100644
--- a/frontend/src/locale/it.json
+++ b/frontend/src/locale/it.json
@@ -453,6 +453,8 @@
"BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Consulta {wildbookDocsLink} per risoluzione problemi o contatta il supporto",
"BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Documentazione Wildbook",
"BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Chiudi",
+ "BULKIMPORT_ERROR_REQUIRE_PREFIX": "manca il prefisso ID progetto obbligatorio",
+ "BULKIMPORT_ERROR_REQUIRE_NAME": "manca il nome del progetto di ricerca obbligatorio",
"PHOTOS_UPLOADED_TITLE": "foto caricate: {count}",
"PHOTOS_UPLOADED": "foto caricate",
"PHOTOS_FAILED": "foto non riuscite",
diff --git a/frontend/src/pages/BulkImport/BulkImportErrorSummaryBar.jsx b/frontend/src/pages/BulkImport/BulkImportErrorSummaryBar.jsx
index e70b54932f..e33c24f4f1 100644
--- a/frontend/src/pages/BulkImport/BulkImportErrorSummaryBar.jsx
+++ b/frontend/src/pages/BulkImport/BulkImportErrorSummaryBar.jsx
@@ -11,7 +11,7 @@ const ErrorSummaryBar = observer(({ store }) => {
Object.values(errors).forEach((rowErrors) => {
Object.values(rowErrors).forEach((errMsg) => {
- if (/required/i.test(JSON.stringify(errMsg))) {
+ if (/require/i.test(JSON.stringify(errMsg))) {
missingField += 1;
} else if (
/invalid/i.test(JSON.stringify(errMsg)) ||
diff --git a/frontend/src/pages/BulkImport/BulkImportStore.js b/frontend/src/pages/BulkImport/BulkImportStore.js
index eda3804fb0..6dd89af030 100644
--- a/frontend/src/pages/BulkImport/BulkImportStore.js
+++ b/frontend/src/pages/BulkImport/BulkImportStore.js
@@ -1669,6 +1669,77 @@ export class BulkImportStore {
}
});
});
+
+ const prefixRe = /^Encounter\.project(\d+)\.projectIdPrefix$/;
+ const nameRe = /^Encounter\.project(\d+)\.researchProjectName$/;
+
+ const prefixIdx = new Set();
+ const nameIdx = new Set();
+
+ this._columnsDef.forEach((col) => {
+ let m = prefixRe.exec(col);
+ if (m) prefixIdx.add(m[1]);
+
+ m = nameRe.exec(col);
+ if (m) nameIdx.add(m[1]);
+ });
+
+ const allIdx = new Set([...prefixIdx, ...nameIdx]);
+
+ const ensureRowErrorMap = (rowIndex) => {
+ if (!errors[rowIndex]) errors[rowIndex] = {};
+ return errors[rowIndex];
+ };
+
+ allIdx.forEach((idx) => {
+ const hasPrefixCol = prefixIdx.has(idx);
+ const hasNameCol = nameIdx.has(idx);
+
+ if (hasPrefixCol === hasNameCol) return;
+
+ const prefixKey = `Encounter.project${idx}.projectIdPrefix`;
+ const nameKey = `Encounter.project${idx}.researchProjectName`;
+
+ const row0 = ensureRowErrorMap(0);
+
+ if (!hasPrefixCol && hasNameCol) {
+ row0[nameKey] = "BULKIMPORT_ERROR_REQUIRE_PREFIX";
+ }
+
+ if (hasPrefixCol && !hasNameCol) {
+ row0[prefixKey] = "BULKIMPORT_ERROR_REQUIRE_NAME";
+ }
+ });
+
+ this._spreadsheetData.forEach((row, rowIndex) => {
+ allIdx.forEach((idx) => {
+ if (!prefixIdx.has(idx) || !nameIdx.has(idx)) return;
+
+ const prefixKey = `Encounter.project${idx}.projectIdPrefix`;
+ const nameKey = `Encounter.project${idx}.researchProjectName`;
+
+ const norm = (v) => String(v ?? "").trim();
+
+ const prefixVal = norm(row[prefixKey]);
+ const nameVal = norm(row[nameKey]);
+
+ const hasPrefixVal = prefixVal !== "";
+ const hasNameVal = nameVal !== "";
+
+ if (hasPrefixVal !== hasNameVal) {
+ const rowErr = ensureRowErrorMap(rowIndex);
+
+ if (!hasPrefixVal && hasNameVal) {
+ rowErr[nameKey] = "BULKIMPORT_ERROR_REQUIRE_PREFIX";
+ }
+
+ if (hasPrefixVal && !hasNameVal) {
+ rowErr[prefixKey] = "BULKIMPORT_ERROR_REQUIRE_NAME";
+ }
+ }
+ });
+ });
+
this._cachedValidation = { errors, warnings };
return this._cachedValidation;
}
diff --git a/frontend/src/pages/BulkImport/EditableDataTable.jsx b/frontend/src/pages/BulkImport/EditableDataTable.jsx
index 9b1f29b78b..99cdc97c58 100644
--- a/frontend/src/pages/BulkImport/EditableDataTable.jsx
+++ b/frontend/src/pages/BulkImport/EditableDataTable.jsx
@@ -23,7 +23,14 @@ const EditableCell = observer(
return store.getOptionsForSelectCell(columnId);
}, [columnId, store]);
const selectValue = useMemo(() => {
- return value ? { value, label: value } : null;
+ if (
+ value === null ||
+ value === undefined ||
+ String(value).trim() === ""
+ ) {
+ return null;
+ }
+ return { value, label: String(value) };
}, [value]);
const [showDetail, setShowDetail] = useState(false);
@@ -62,7 +69,7 @@ const EditableCell = observer(
{
if (e.key === "Enter") {
diff --git a/src/main/java/org/ecocean/api/bulk/BulkImportUtil.java b/src/main/java/org/ecocean/api/bulk/BulkImportUtil.java
index 3e651c8b94..0a51e910cc 100644
--- a/src/main/java/org/ecocean/api/bulk/BulkImportUtil.java
+++ b/src/main/java/org/ecocean/api/bulk/BulkImportUtil.java
@@ -139,6 +139,29 @@ public static Map validateRow(JSONObject row, Shepherd myShepher
ApiException.ERROR_RETURN_CODE_INVALID));
}
}
+
+ // 1314 introduces catching incomplete project name/prefix combos
+ List projectPrefixes = findIndexedFieldNames(fieldNames, "Encounter.project.projectIdPrefix");
+ List projectNames = findIndexedFieldNames(fieldNames, "Encounter.project.researchProjectName");
+ // we use the bigger of these
+ int pmax = Integer.max(projectPrefixes.size(), projectNames.size());
+ for (int i = 0; i < pmax; i++) {
+ String pnameKey = "Encounter.project" + i + ".researchProjectName";
+ String pprefixKey = "Encounter.project" + i + ".projectIdPrefix";
+ Object pname = getValue(rtn, pnameKey);
+ Object pprefix = getValue(rtn, pprefixKey);
+ if ((pname == null) && (pprefix == null)) continue;
+ if ((pname != null) && (pprefix != null)) continue;
+ if (pname == null)
+ rtn.put(pnameKey,
+ new BulkValidatorException("must have researchProjectName if given projectIdPrefix",
+ ApiException.ERROR_RETURN_CODE_REQUIRED));
+ if (pprefix == null)
+ rtn.put(pprefixKey,
+ new BulkValidatorException("must have projectIdPrefix if given researchProjectName",
+ ApiException.ERROR_RETURN_CODE_REQUIRED));
+ }
+
return rtn;
}