From e69b607aabd7a8fde1005b53a2a0a6fb190dc2a3 Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Fri, 19 Sep 2025 11:21:40 +0530 Subject: [PATCH] feat: add validation to prevent changing values on save --- frappe/model/base_document.py | 45 ++++++++++++++++++---------- frappe/model/document.py | 6 ++-- frappe/public/js/frappe/form/form.js | 4 +-- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 3a5057595e..c8d62453bf 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -1399,7 +1399,7 @@ class BaseDocument: else: return True - def reset_values_if_no_permlevel_access(self, has_access_to, high_permlevel_fields): + def reset_values_if_no_permlevel_access(self, has_access_to, high_permlevel_fields, mask_fields): """If the user does not have permissions at permlevel > 0, then reset the values to original / default""" to_reset = [ df @@ -1411,22 +1411,35 @@ class BaseDocument: ) ] - if to_reset: - if self.is_new(): - # if new, set default value - ref_doc = frappe.new_doc(self.doctype) - else: - # get values from old doc - if self.parent_doc: - parent_doc = self.parent_doc.get_latest() - child_docs = [d for d in parent_doc.get(self.parentfield) if d.name == self.name] - if not child_docs: - return - ref_doc = child_docs[0] - else: - ref_doc = self.get_latest() + to_reset = to_reset + mask_fields - for df in to_reset: + if not to_reset: + return + + if self.is_new(): + # if new, set default value + ref_doc = frappe.new_doc(self.doctype) + else: + # get values from old doc + if self.parent_doc: + parent_doc = self.parent_doc.get_latest() + child_docs = [d for d in parent_doc.get(self.parentfield) if d.name == self.name] + if not child_docs: + return + ref_doc = child_docs[0] + else: + ref_doc = self.get_latest() + + masked_fieldnames = [df.fieldname for df in to_reset if df.get("mask_readonly")] + ref_values = {} + if not self.is_new() and masked_fieldnames: + ref_values = frappe.db.get_value(self.doctype, self.name, masked_fieldnames, as_dict=True) or {} + + for df in to_reset: + if df.get("mask_readonly") and not self.is_new(): + if df.fieldname in ref_values: + self.set(df.fieldname, ref_values[df.fieldname]) + else: self.set(df.fieldname, ref_doc.get(df.fieldname)) def get_value(self, fieldname): diff --git a/frappe/model/document.py b/frappe/model/document.py index 916a2771fc..e5ed785341 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -923,8 +923,10 @@ class Document(BaseDocument): has_access_to = self.get_permlevel_access() high_permlevel_fields = self.meta.get_high_permlevel_fields() - if high_permlevel_fields: - self.reset_values_if_no_permlevel_access(has_access_to, high_permlevel_fields) + mask_fields = self.meta.get_masked_fields() + + if high_permlevel_fields or mask_fields: + self.reset_values_if_no_permlevel_access(has_access_to, high_permlevel_fields, mask_fields) # If new record then don't reset the values for child table if self.is_new(): diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 260d4647f0..c3effffc2a 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -465,9 +465,7 @@ frappe.ui.form.Form = class FrappeForm { this.show_conflict_message(); this.show_submission_queue_banner(); - if (!this.is_new()) { - this.mark_mask_fields_readonly(); - } + this.mark_mask_fields_readonly(); if (frappe.boot.read_only) { this.disable_form();