From ff4ca9fe664531ee54b63fe660641bfe1002fef4 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 25 Sep 2025 21:41:02 +0530 Subject: [PATCH] fix: add validation for mandatory fields in useDocument (cherry picked from commit dbcda4c548270f4b030d819857b1f393fdaadecb) --- frontend/src/data/document.js | 42 ++++++++++++++++++++++++++++++++++- frontend/src/utils/index.js | 30 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/frontend/src/data/document.js b/frontend/src/data/document.js index 5a970f20..eeb410aa 100644 --- a/frontend/src/data/document.js +++ b/frontend/src/data/document.js @@ -1,7 +1,8 @@ import { getScript } from '@/data/script' import { globalStore } from '@/stores/global' +import { getMeta } from '@/stores/meta' import { showSettings, activeSettingsPage } from '@/composables/settings' -import { runSequentially, parseAssignees } from '@/utils' +import { runSequentially, parseAssignees, evaluateExpression } from '@/utils' import { createDocumentResource, createResource, toast } from 'frappe-ui' import { ref, reactive } from 'vue' @@ -11,6 +12,7 @@ const assigneesCache = {} export function useDocument(doctype, docname) { const { setupScript, scripts } = getScript(doctype) + const meta = getMeta(doctype) documentsCache[doctype] = documentsCache[doctype] || {} @@ -37,6 +39,7 @@ export function useDocument(doctype, docname) { } }, setValue: { + validate, onSuccess: () => { triggerOnSave() toast.success(__('Document updated successfully')) @@ -152,6 +155,42 @@ export function useDocument(doctype, docname) { return [] } + function validate(d) { + checkMandatory(d.doc || d.fieldname) + } + + function checkMandatory(doc) { + let fields = meta?.getFields() || [] + + if (!fields || fields.length === 0) return + + let missingFields = [] + + fields.forEach((df) => { + let parent = meta?.doctypeMeta?.[df.parent] || null + if (evaluateExpression(df.mandatory_depends_on, doc, parent)) { + const value = doc[df.fieldname] + if ( + value === undefined || + value === null || + (typeof value === 'string' && value.trim() === '') || + (Array.isArray(value) && value.length === 0) + ) { + missingFields.push(df.label || df.fieldname) + } + } + }) + + if (missingFields.length > 0) { + toast.error( + __('Mandatory fields required: {0}', [missingFields.join(', ')]), + ) + throw new Error( + __('Mandatory fields required: {0}', [missingFields.join(', ')]), + ) + } + } + async function triggerOnLoad() { const handler = async function () { await (this.onLoad?.() || this.on_load?.() || this.onload?.()) @@ -280,6 +319,7 @@ export function useDocument(doctype, docname) { assignees: assigneesCache[doctype][docname || ''], scripts, error, + validate, getControllers, triggerOnLoad, triggerOnBeforeCreate, diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index 14b5ade9..4f7370c2 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -421,6 +421,36 @@ export function evaluateDependsOnValue(expression, doc) { return out } +export function evaluateExpression(expression, doc, parent) { + if (!expression) return false + if (!doc) return false + + let out = null + if (typeof expression === 'boolean') { + out = expression + } else if (typeof expression === 'function') { + out = expression(doc) + } else if (expression.substr(0, 5) == 'eval:') { + try { + out = _eval(expression.substr(5), { doc, parent }) + if (parent && parent.istable && expression.includes('is_submittable')) { + out = true + } + } catch (e) { + out = true + } + } else { + let value = doc[expression] + if (Array.isArray(value)) { + out = !!value.length + } else { + out = !!value + } + } + + return out +} + export function convertSize(size) { const units = ['B', 'KB', 'MB', 'GB', 'TB'] let unitIndex = 0