1
0
forked from test/crm

fix: apply permlevel restrictions on datafields

This commit is contained in:
Shariq Ansari 2025-01-02 23:13:29 +05:30
parent 7f2ccef7c8
commit 321751ab5a
18 changed files with 184 additions and 99 deletions

View File

@ -565,70 +565,6 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
return fields_meta
@frappe.whitelist()
def get_sidebar_fields(doctype, name):
if not frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": "Side Panel"}):
return []
layout = frappe.get_doc("CRM Fields Layout", {"dt": doctype, "type": "Side Panel"}).layout
if not layout:
return []
layout = json.loads(layout)
not_allowed_fieldtypes = [
"Tab Break",
"Section Break",
"Column Break",
]
fields = frappe.get_meta(doctype).fields
fields = [field for field in fields if field.fieldtype not in not_allowed_fieldtypes]
doc = frappe.get_cached_doc(doctype, name)
has_high_permlevel_fields = any(df.permlevel > 0 for df in fields)
if has_high_permlevel_fields:
has_read_access_to_permlevels = doc.get_permlevel_access("read")
has_write_access_to_permlevels = doc.get_permlevel_access("write")
for section in layout:
section["name"] = section.get("name") or section.get("label")
for column in section.get("columns") if section.get("columns") else []:
for field in column.get("fields") if column.get("fields") else []:
field_obj = next((f for f in fields if f.fieldname == field), None)
if field_obj:
if field_obj.permlevel > 0:
field_has_write_access = field_obj.permlevel in has_write_access_to_permlevels
field_has_read_access = field_obj.permlevel in has_read_access_to_permlevels
if not field_has_write_access and field_has_read_access:
field_obj.read_only = 1
if not field_has_read_access and not field_has_write_access:
field_obj.hidden = 1
column["fields"][column.get("fields").index(field)] = get_field_obj(field_obj)
fields_meta = {}
for field in fields:
fields_meta[field.fieldname] = field
return layout
def get_field_obj(field):
field = field.as_dict()
field["placeholder"] = field.get("placeholder") or "Add " + field.label + "..."
if field.fieldtype == "Link":
field["placeholder"] = field.get("placeholder") or "Select " + field.label + "..."
elif field.fieldtype == "Select" and field.options:
field["placeholder"] = field.get("placeholder") or "Select " + field.label + "..."
field["options"] = [{"label": option, "value": option} for option in field.options.split("\n")]
if field.read_only:
field["tooltip"] = "This field is read only and cannot be edited."
return field
def get_assigned_users(doctype, name, default_assigned_to=None):
assigned_users = frappe.get_all(
"ToDo",

View File

@ -46,7 +46,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-01-01 20:42:29.862890",
"modified": "2025-01-02 22:12:51.663011",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Fields Layout",
@ -64,6 +64,15 @@
"role": "System Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1
}
],
"sort_field": "creation",

View File

@ -14,7 +14,7 @@ class CRMFieldsLayout(Document):
@frappe.whitelist()
def get_fields_layout(doctype: str, type: str):
def get_fields_layout(doctype: str, type: str, parent_doctype: str | None = None):
tabs = []
layout = None
@ -52,11 +52,94 @@ def get_fields_layout(doctype: str, type: str):
field = next((f for f in fields if f.fieldname == field), None)
if field:
field = field.as_dict()
handle_perm_level_restrictions(field, doctype, parent_doctype)
column["fields"][column.get("fields").index(field["fieldname"])] = field
return tabs or []
@frappe.whitelist()
def get_sidepanel_sections(doctype):
if not frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": "Side Panel"}):
return []
layout = frappe.get_doc("CRM Fields Layout", {"dt": doctype, "type": "Side Panel"}).layout
if not layout:
return []
layout = json.loads(layout)
not_allowed_fieldtypes = [
"Tab Break",
"Section Break",
"Column Break",
]
fields = frappe.get_meta(doctype).fields
fields = [field for field in fields if field.fieldtype not in not_allowed_fieldtypes]
for section in layout:
section["name"] = section.get("name") or section.get("label")
for column in section.get("columns") if section.get("columns") else []:
for field in column.get("fields") if column.get("fields") else []:
field_obj = next((f for f in fields if f.fieldname == field), None)
if field_obj:
field_obj = field_obj.as_dict()
handle_perm_level_restrictions(field_obj, doctype)
column["fields"][column.get("fields").index(field)] = get_field_obj(field_obj)
fields_meta = {}
for field in fields:
fields_meta[field.fieldname] = field
return layout
def handle_perm_level_restrictions(field, doctype, parent_doctype=None):
if field.permlevel == 0:
return
field_has_write_access = field.permlevel in get_permlevel_access("write", doctype, parent_doctype)
field_has_read_access = field.permlevel in get_permlevel_access("read", doctype, parent_doctype)
if not field_has_write_access and field_has_read_access:
field.read_only = 1
if not field_has_read_access and not field_has_write_access:
field.hidden = 1
def get_permlevel_access(permission_type="write", doctype=None, parent_doctype=None):
allowed_permlevels = []
roles = frappe.get_roles()
meta = frappe.get_meta(doctype)
if meta.istable and parent_doctype:
meta = frappe.get_meta(parent_doctype)
elif meta.istable and not parent_doctype:
return [1, 0]
for perm in meta.permissions:
if perm.role in roles and perm.get(permission_type) and perm.permlevel not in allowed_permlevels:
allowed_permlevels.append(perm.permlevel)
return allowed_permlevels
def get_field_obj(field):
field["placeholder"] = field.get("placeholder") or "Add " + field.label + "..."
if field.fieldtype == "Link":
field["placeholder"] = field.get("placeholder") or "Select " + field.label + "..."
elif field.fieldtype == "Select" and field.options:
field["placeholder"] = field.get("placeholder") or "Select " + field.label + "..."
field["options"] = [{"label": option, "value": option} for option in field.options.split("\n")]
if field.read_only:
field["tooltip"] = "This field is read only and cannot be edited."
return field
@frappe.whitelist()
def save_fields_layout(doctype: str, type: str, layout: str):
if frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": type}):

View File

@ -21,7 +21,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-01-19 21:57:02.025918",
"modified": "2025-01-02 22:14:28.686821",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Industry",
@ -51,6 +51,15 @@
"role": "Sales Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1
}
],
"quick_entry": 1,

View File

@ -290,7 +290,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-09-17 18:36:57.289897",
"modified": "2025-01-02 22:14:01.991054",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Lead",
@ -320,6 +320,15 @@
"role": "Sales Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1
}
],
"sender_field": "email",

View File

@ -29,7 +29,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-01-19 21:56:04.702254",
"modified": "2025-01-02 22:13:30.498404",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Lead Source",
@ -59,6 +59,15 @@
"role": "Sales Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1
}
],
"quick_entry": 1,

View File

@ -37,7 +37,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-01-19 21:56:16.872924",
"modified": "2025-01-02 22:13:43.038656",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Lead Status",
@ -67,6 +67,15 @@
"role": "Sales Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1
}
],
"sort_field": "modified",

View File

@ -170,6 +170,7 @@
:index="index"
:data="row"
:doctype="doctype"
:parentDoctype="parentDoctype"
/>
</div>
</template>
@ -198,6 +199,7 @@
v-if="showGridRowFieldsModal"
v-model="showGridRowFieldsModal"
:doctype="doctype"
:parentDoctype="parentDoctype"
/>
<GridFieldsEditorModal
v-if="showGridFieldsEditorModal"

View File

@ -36,7 +36,13 @@
:tabs="tabs.data"
:doctype="_doctype"
/>
<FieldLayout v-else :tabs="tabs.data" :data="{}" :modal="true" :preview="true"/>
<FieldLayout
v-else
:tabs="tabs.data"
:data="{}"
:modal="true"
:preview="true"
/>
</div>
</div>
</template>
@ -55,6 +61,10 @@ const props = defineProps({
type: String,
default: 'CRM Lead',
},
parentDoctype: {
type: String,
default: '',
},
})
const emit = defineEmits(['reload'])
@ -66,12 +76,16 @@ const dirty = ref(false)
const preview = ref(false)
function getParams() {
return { doctype: _doctype.value, type: 'Grid Row' }
return {
doctype: _doctype.value,
type: 'Grid Row',
parent_doctype: props.parentDoctype,
}
}
const tabs = createResource({
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout',
cache: ['GridRowFieldsModal', _doctype.value],
cache: ['GridRowFieldsModal', _doctype.value, props.parentDoctype],
params: getParams(),
onSuccess(data) {
tabs.originalData = JSON.parse(JSON.stringify(data))

View File

@ -41,6 +41,7 @@ const props = defineProps({
index: Number,
data: Object,
doctype: String,
parentDoctype: String,
})
const { isManager } = usersStore()
@ -50,8 +51,12 @@ const showGridRowFieldsModal = defineModel('showGridRowFieldsModal')
const tabs = createResource({
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout',
cache: ['Grid Row', props.doctype],
params: { doctype: props.doctype, type: 'Grid Row' },
cache: ['Grid Row', props.doctype, props.parentDoctype],
params: {
doctype: props.doctype,
type: 'Grid Row',
parent_doctype: props.parentDoctype,
},
auto: true,
})

View File

@ -336,9 +336,9 @@ const rows = computed(() => {
})
const sections = createResource({
url: 'crm.api.doc.get_sidebar_fields',
cache: ['sidePanelSections', props.contactId],
params: { doctype: 'Contact', name: props.contactId },
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'Contact'],
params: { doctype: 'Contact' },
auto: true,
transform: (data) => getParsedSections(data),
})

View File

@ -530,9 +530,9 @@ const tabs = computed(() => {
const { tabIndex } = useActiveTabManager(tabs, 'lastDealTab')
const sections = createResource({
url: 'crm.api.doc.get_sidebar_fields',
cache: ['sidePanelSections', props.dealId],
params: { doctype: 'CRM Deal', name: props.dealId },
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'CRM Deal'],
params: { doctype: 'CRM Deal' },
auto: true,
transform: (data) => getParsedSections(data),
})

View File

@ -531,9 +531,9 @@ function validateFile(file) {
}
const sections = createResource({
url: 'crm.api.doc.get_sidebar_fields',
cache: ['sidePanelSections', props.leadId],
params: { doctype: 'CRM Lead', name: props.leadId },
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'CRM Lead'],
params: { doctype: 'CRM Lead' },
auto: true,
})

View File

@ -348,9 +348,9 @@ const rows = computed(() => {
})
const fieldsLayout = createResource({
url: 'crm.api.doc.get_sidebar_fields',
cache: ['fieldsLayout', props.contactId],
params: { doctype: 'Contact', name: props.contactId },
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'Contact'],
params: { doctype: 'Contact' },
auto: true,
transform: (data) => getParsedFields(data),
})

View File

@ -501,9 +501,9 @@ const tabs = computed(() => {
const { tabIndex } = useActiveTabManager(tabs, 'lastDealTab')
const fieldsLayout = createResource({
url: 'crm.api.doc.get_sidebar_fields',
cache: ['fieldsLayout', props.dealId],
params: { doctype: 'CRM Deal', name: props.dealId },
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'CRM Deal'],
params: { doctype: 'CRM Deal' },
auto: true,
transform: (data) => getParsedFields(data),
})

View File

@ -421,9 +421,9 @@ watch(tabs, (value) => {
})
const fieldsLayout = createResource({
url: 'crm.api.doc.get_sidebar_fields',
cache: ['fieldsLayout', props.leadId],
params: { doctype: 'CRM Lead', name: props.leadId },
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'CRM Lead'],
params: { doctype: 'CRM Lead' },
auto: true,
})

View File

@ -327,9 +327,9 @@ const _organization = ref({})
const _address = ref({})
const fieldsLayout = createResource({
url: 'crm.api.doc.get_sidebar_fields',
cache: ['fieldsLayout', props.organizationId],
params: { doctype: 'CRM Organization', name: props.organizationId },
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'CRM Organization'],
params: { doctype: 'CRM Organization' },
auto: true,
transform: (data) => getParsedFields(data),
})

View File

@ -336,9 +336,9 @@ const _organization = ref({})
const _address = ref({})
const sections = createResource({
url: 'crm.api.doc.get_sidebar_fields',
cache: ['sidePanelSections', props.organizationId],
params: { doctype: 'CRM Organization', name: props.organizationId },
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'CRM Organization'],
params: { doctype: 'CRM Organization' },
auto: true,
transform: (data) => getParsedSections(data),
})