diff --git a/crm/api/doc.py b/crm/api/doc.py
index 5f1c9cf2..11ddcf5b 100644
--- a/crm/api/doc.py
+++ b/crm/api/doc.py
@@ -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",
diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
index 1f246aab..43019c5c 100644
--- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
+++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
@@ -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",
diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
index 395aef60..271a5da9 100644
--- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
+++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
@@ -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}):
diff --git a/crm/fcrm/doctype/crm_industry/crm_industry.json b/crm/fcrm/doctype/crm_industry/crm_industry.json
index d3d15a71..5128d8be 100644
--- a/crm/fcrm/doctype/crm_industry/crm_industry.json
+++ b/crm/fcrm/doctype/crm_industry/crm_industry.json
@@ -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,
diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.json b/crm/fcrm/doctype/crm_lead/crm_lead.json
index 786c03a5..e39af407 100644
--- a/crm/fcrm/doctype/crm_lead/crm_lead.json
+++ b/crm/fcrm/doctype/crm_lead/crm_lead.json
@@ -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",
diff --git a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json b/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json
index 23aea6f6..416ccd7e 100644
--- a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json
+++ b/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json
@@ -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,
diff --git a/crm/fcrm/doctype/crm_lead_status/crm_lead_status.json b/crm/fcrm/doctype/crm_lead_status/crm_lead_status.json
index 587f6b61..21ca8b61 100644
--- a/crm/fcrm/doctype/crm_lead_status/crm_lead_status.json
+++ b/crm/fcrm/doctype/crm_lead_status/crm_lead_status.json
@@ -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",
diff --git a/frontend/src/components/Controls/Grid.vue b/frontend/src/components/Controls/Grid.vue
index e866bab1..519f7a1b 100644
--- a/frontend/src/components/Controls/Grid.vue
+++ b/frontend/src/components/Controls/Grid.vue
@@ -170,6 +170,7 @@
:index="index"
:data="row"
:doctype="doctype"
+ :parentDoctype="parentDoctype"
/>
@@ -198,6 +199,7 @@
v-if="showGridRowFieldsModal"
v-model="showGridRowFieldsModal"
:doctype="doctype"
+ :parentDoctype="parentDoctype"
/>
-
+
@@ -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))
diff --git a/frontend/src/components/Controls/GridRowModal.vue b/frontend/src/components/Controls/GridRowModal.vue
index 93351b4e..36fe0846 100644
--- a/frontend/src/components/Controls/GridRowModal.vue
+++ b/frontend/src/components/Controls/GridRowModal.vue
@@ -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,
})
diff --git a/frontend/src/pages/Contact.vue b/frontend/src/pages/Contact.vue
index b2035d48..c6d08a76 100644
--- a/frontend/src/pages/Contact.vue
+++ b/frontend/src/pages/Contact.vue
@@ -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),
})
diff --git a/frontend/src/pages/Deal.vue b/frontend/src/pages/Deal.vue
index 6c5ac9b7..90c9cb93 100644
--- a/frontend/src/pages/Deal.vue
+++ b/frontend/src/pages/Deal.vue
@@ -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),
})
diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue
index b396fc09..c59547b2 100644
--- a/frontend/src/pages/Lead.vue
+++ b/frontend/src/pages/Lead.vue
@@ -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,
})
diff --git a/frontend/src/pages/MobileContact.vue b/frontend/src/pages/MobileContact.vue
index c97e6b5f..452d611b 100644
--- a/frontend/src/pages/MobileContact.vue
+++ b/frontend/src/pages/MobileContact.vue
@@ -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),
})
diff --git a/frontend/src/pages/MobileDeal.vue b/frontend/src/pages/MobileDeal.vue
index 58efd5b9..72de7654 100644
--- a/frontend/src/pages/MobileDeal.vue
+++ b/frontend/src/pages/MobileDeal.vue
@@ -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),
})
diff --git a/frontend/src/pages/MobileLead.vue b/frontend/src/pages/MobileLead.vue
index 56d39589..26452022 100644
--- a/frontend/src/pages/MobileLead.vue
+++ b/frontend/src/pages/MobileLead.vue
@@ -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,
})
diff --git a/frontend/src/pages/MobileOrganization.vue b/frontend/src/pages/MobileOrganization.vue
index 8b74f968..2694b28d 100644
--- a/frontend/src/pages/MobileOrganization.vue
+++ b/frontend/src/pages/MobileOrganization.vue
@@ -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),
})
diff --git a/frontend/src/pages/Organization.vue b/frontend/src/pages/Organization.vue
index 7e6954d2..c1832046 100644
--- a/frontend/src/pages/Organization.vue
+++ b/frontend/src/pages/Organization.vue
@@ -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),
})