diff --git a/crm/api/doc.py b/crm/api/doc.py
index 64d14f6f..11ddcf5b 100644
--- a/crm/api/doc.py
+++ b/crm/api/doc.py
@@ -565,95 +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 field in section.get("fields") if section.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
- section["fields"][section.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):
- obj = {
- "label": field.label,
- "type": get_type(field),
- "name": field.fieldname,
- "hidden": field.hidden,
- "reqd": field.reqd,
- "read_only": field.read_only,
- "all_properties": field,
- }
-
- obj["placeholder"] = field.get("placeholder") or "Add " + field.label + "..."
-
- if field.fieldtype == "Link":
- obj["placeholder"] = field.get("placeholder") or "Select " + field.label + "..."
- obj["doctype"] = field.options
- elif field.fieldtype == "Select" and field.options:
- obj["placeholder"] = field.get("placeholder") or "Select " + field.label + "..."
- obj["options"] = [{"label": option, "value": option} for option in field.options.split("\n")]
-
- if field.read_only:
- obj["tooltip"] = "This field is read only and cannot be edited."
-
- return obj
-
-
-def get_type(field):
- if field.fieldtype == "Data" and field.options == "Phone":
- return "phone"
- elif field.fieldtype == "Data" and field.options == "Email":
- return "email"
- elif field.fieldtype == "Check":
- return "checkbox"
- elif field.fieldtype == "Int":
- return "number"
- elif field.fieldtype in ["Small Text", "Text", "Long Text"]:
- return "textarea"
- elif field.read_only:
- return "read_only"
- return field.fieldtype.lower()
-
-
def get_assigned_users(doctype, name, default_assigned_to=None):
assigned_users = frappe.get_all(
"ToDo",
@@ -685,22 +596,7 @@ def get_fields(doctype: str, allow_all_fieldtypes: bool = False):
for field in fields:
if field.fieldtype not in not_allowed_fieldtypes and field.fieldname:
- _fields.append(
- {
- "label": field.label,
- "type": field.fieldtype,
- "value": field.fieldname,
- "options": field.options,
- "mandatory": field.reqd,
- "read_only": field.read_only,
- "hidden": field.hidden,
- "depends_on": field.depends_on,
- "mandatory_depends_on": field.mandatory_depends_on,
- "read_only_depends_on": field.read_only_depends_on,
- "link_filters": field.get("link_filters"),
- "placeholder": field.get("placeholder"),
- }
- )
+ _fields.append(field)
return _fields
diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py
index beb63cbd..b650301e 100644
--- a/crm/fcrm/doctype/crm_deal/crm_deal.py
+++ b/crm/fcrm/doctype/crm_deal/crm_deal.py
@@ -80,7 +80,7 @@ class CRMDeal(Document):
# the agent is already set as an assignee
return
- assign({"assign_to": [agent], "doctype": "CRM Deal", "name": self.name})
+ assign({"assign_to": [agent], "doctype": "CRM Deal", "name": self.name}, ignore_permissions=True)
def share_with_agent(self, agent):
if not agent:
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 7094e58d..43019c5c 100644
--- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
+++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
@@ -37,7 +37,7 @@
"fieldname": "layout",
"fieldtype": "Code",
"label": "Layout",
- "options": "JS"
+ "options": "JSON"
},
{
"fieldname": "column_break_post",
@@ -46,7 +46,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-12-29 12:58:54.280569",
+ "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 23552e5c..271a5da9 100644
--- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
+++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
@@ -6,6 +6,7 @@ import json
import frappe
from frappe import _
from frappe.model.document import Document
+from frappe.utils import random_string
class CRMFieldsLayout(Document):
@@ -13,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
@@ -29,38 +30,116 @@ def get_fields_layout(doctype: str, type: str):
has_tabs = tabs[0].get("sections") if tabs and tabs[0] else False
if not has_tabs:
- tabs = [{"no_tabs": True, "sections": tabs}]
+ tabs = [{"name": "first_tab", "sections": tabs}]
allowed_fields = []
for tab in tabs:
for section in tab.get("sections"):
- if not section.get("fields"):
+ if "columns" not in section:
continue
- allowed_fields.extend(section.get("fields"))
+ for column in section.get("columns"):
+ if not column.get("fields"):
+ continue
+ allowed_fields.extend(column.get("fields"))
fields = frappe.get_meta(doctype).fields
fields = [field for field in fields if field.fieldname in allowed_fields]
for tab in tabs:
for section in tab.get("sections"):
- for field in section.get("fields") if section.get("fields") else []:
- field = next((f for f in fields if f.fieldname == field), None)
- if field:
- field = {
- "label": _(field.label),
- "name": field.fieldname,
- "type": field.fieldtype,
- "options": getOptions(field),
- "mandatory": field.reqd,
- "read_only": field.read_only,
- "placeholder": field.get("placeholder"),
- "filters": field.get("link_filters"),
- }
- section["fields"][section.get("fields").index(field["name"])] = field
+ for column in section.get("columns") if section.get("columns") else []:
+ for field in column.get("fields") if column.get("fields") else []:
+ 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}):
@@ -82,18 +161,45 @@ def save_fields_layout(doctype: str, type: str, layout: str):
def get_default_layout(doctype: str):
fields = frappe.get_meta(doctype).fields
- fields = [
- field.fieldname
- for field in fields
- if field.fieldtype not in ["Tab Break", "Section Break", "Column Break"]
- ]
- return [{"no_tabs": True, "sections": [{"hideLabel": True, "fields": fields}]}]
+ tabs = []
+ if fields and fields[0].fieldtype != "Tab Break":
+ sections = []
+ if fields and fields[0].fieldtype != "Section Break":
+ sections.append(
+ {
+ "name": "section_" + str(random_string(4)),
+ "columns": [{"name": "column_" + str(random_string(4)), "fields": []}],
+ }
+ )
+ tabs.append({"name": "tab_" + str(random_string(4)), "sections": sections})
-def getOptions(field):
- if field.fieldtype == "Select" and field.options:
- field.options = field.options.split("\n")
- field.options = [{"label": _(option), "value": option} for option in field.options]
- field.options.insert(0, {"label": "", "value": ""})
- return field.options
+ for field in fields:
+ if field.fieldtype == "Tab Break":
+ tabs.append(
+ {
+ "name": "tab_" + str(random_string(4)),
+ "sections": [
+ {
+ "name": "section_" + str(random_string(4)),
+ "columns": [{"name": "column_" + str(random_string(4)), "fields": []}],
+ }
+ ],
+ }
+ )
+ elif field.fieldtype == "Section Break":
+ tabs[-1]["sections"].append(
+ {
+ "name": "section_" + str(random_string(4)),
+ "columns": [{"name": "column_" + str(random_string(4)), "fields": []}],
+ }
+ )
+ elif field.fieldtype == "Column Break":
+ tabs[-1]["sections"][-1]["columns"].append(
+ {"name": "column_" + str(random_string(4)), "fields": []}
+ )
+ else:
+ tabs[-1]["sections"][-1]["columns"][-1]["fields"].append(field.fieldname)
+
+ return tabs
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/crm_lead.py b/crm/fcrm/doctype/crm_lead/crm_lead.py
index e81cc566..bdf0885f 100644
--- a/crm/fcrm/doctype/crm_lead/crm_lead.py
+++ b/crm/fcrm/doctype/crm_lead/crm_lead.py
@@ -388,11 +388,10 @@ def convert_to_deal(lead, doc=None):
lead = frappe.get_cached_doc("CRM Lead", lead)
if frappe.db.exists("CRM Lead Status", "Qualified"):
- lead.status = "Qualified"
- lead.converted = 1
+ lead.db_set("status", "Qualified")
+ lead.db_set("converted", 1)
if lead.sla and frappe.db.exists("CRM Communication Status", "Replied"):
- lead.communication_status = "Replied"
- lead.save(ignore_permissions=True)
+ lead.db_set("communication_status", "Replied")
contact = lead.create_contact(False)
organization = lead.create_organization()
deal = lead.create_deal(contact, organization)
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/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
index e51a1b4f..408075c5 100644
--- a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
+++ b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
@@ -6,13 +6,13 @@
"engine": "InnoDB",
"field_order": [
"enabled",
- "is_erpnext_in_different_site",
- "column_break_vfru",
"erpnext_company",
- "section_break_oubd",
+ "column_break_vfru",
+ "is_erpnext_in_different_site",
"erpnext_site_url",
- "column_break_fllx",
+ "section_break_oubd",
"api_key",
+ "column_break_fllx",
"api_secret",
"section_break_jnbn",
"create_customer_on_status_change",
@@ -37,7 +37,8 @@
{
"depends_on": "enabled",
"fieldname": "section_break_oubd",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "ERPNext Site API's"
},
{
"fieldname": "column_break_fllx",
@@ -101,7 +102,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2024-12-30 19:22:07.929304",
+ "modified": "2024-12-31 23:24:29.636820",
"modified_by": "Administrator",
"module": "FCRM",
"name": "ERPNext CRM Settings",
diff --git a/crm/install.py b/crm/install.py
index 6dc50be9..b0201d6d 100644
--- a/crm/install.py
+++ b/crm/install.py
@@ -120,53 +120,53 @@ def add_default_fields_layout(force=False):
quick_entry_layouts = {
"CRM Lead-Quick Entry": {
"doctype": "CRM Lead",
- "layout": '[{"label":"Person","fields":["salutation","first_name","last_name","email","mobile_no", "gender"],"hideLabel":true},{"label":"Organization","fields":["organization","website","no_of_employees","territory","annual_revenue","industry"],"hideLabel":true,"hideBorder":false},{"label":"Other","columns":2,"fields":["status","lead_owner"],"hideLabel":true,"hideBorder":false}]',
+ "layout": '[{"name": "person_section", "columns": [{"name": "column_5jrk", "fields": ["salutation", "email"]}, {"name": "column_5CPV", "fields": ["first_name", "mobile_no"]}, {"name": "column_gXOy", "fields": ["last_name", "gender"]}]}, {"name": "organization_section", "columns": [{"name": "column_GHfX", "fields": ["organization", "territory"]}, {"name": "column_hXjS", "fields": ["website", "annual_revenue"]}, {"name": "column_RDNA", "fields": ["no_of_employees", "industry"]}]}, {"name": "lead_section", "columns": [{"name": "column_EO1H", "fields": ["status"]}, {"name": "column_RWBe", "fields": ["lead_owner"]}]}]',
},
"CRM Deal-Quick Entry": {
"doctype": "CRM Deal",
- "layout": '[{"label": "Select Organization", "fields": ["organization"], "hideLabel": true, "editable": true}, {"label": "Organization Details", "fields": ["organization_name", "website", "no_of_employees", "territory", "annual_revenue", "industry"], "hideLabel": true, "editable": true}, {"label": "Select Contact", "fields": ["contact"], "hideLabel": true, "editable": true}, {"label": "Contact Details", "fields": ["salutation", "first_name", "last_name", "email", "mobile_no", "gender"], "hideLabel": true, "editable": true}, {"label": "Other", "columns": 2, "fields": ["status", "deal_owner"], "hideLabel": true}]',
+ "layout": '[{"name": "organization_section", "hidden": true, "editable": false, "columns": [{"name": "column_GpMP", "fields": ["organization"]}, {"name": "column_FPTn", "fields": []}]}, {"name": "organization_details_section", "editable": false, "columns": [{"name": "column_S3tQ", "fields": ["organization_name", "territory"]}, {"name": "column_KqV1", "fields": ["website", "annual_revenue"]}, {"name": "column_1r67", "fields": ["no_of_employees", "industry"]}]}, {"name": "contact_section", "hidden": true, "editable": false, "columns": [{"name": "column_CeXr", "fields": ["contact"]}, {"name": "column_yHbk", "fields": []}]}, {"name": "contact_details_section", "editable": false, "columns": [{"name": "column_ZTWr", "fields": ["salutation", "email"]}, {"name": "column_tabr", "fields": ["first_name", "mobile_no"]}, {"name": "column_Qjdx", "fields": ["last_name", "gender"]}]}, {"name": "deal_section", "columns": [{"name": "column_mdps", "fields": ["status"]}, {"name": "column_H40H", "fields": ["deal_owner"]}]}]',
},
"Contact-Quick Entry": {
"doctype": "Contact",
- "layout": '[{"label":"Salutation","columns":1,"fields":["salutation"],"hideLabel":true},{"label":"Full Name","columns":2,"hideBorder":true,"fields":["first_name","last_name"],"hideLabel":true},{"label":"Email","columns":1,"hideBorder":true,"fields":["email_id"],"hideLabel":true},{"label":"Mobile No. & Gender","columns":2,"hideBorder":true,"fields":["mobile_no","gender"],"hideLabel":true},{"label":"Organization","columns":1,"hideBorder":true,"fields":["company_name"],"hideLabel":true},{"label":"Designation","columns":1,"hideBorder":true,"fields":["designation"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]',
+ "layout": '[{"name": "salutation_section", "columns": [{"name": "column_eXks", "fields": ["salutation"]}]}, {"name": "full_name_section", "hideBorder": true, "columns": [{"name": "column_cSxf", "fields": ["first_name"]}, {"name": "column_yBc7", "fields": ["last_name"]}]}, {"name": "email_section", "hideBorder": true, "columns": [{"name": "column_tH3L", "fields": ["email_id"]}]}, {"name": "mobile_gender_section", "hideBorder": true, "columns": [{"name": "column_lrfI", "fields": ["mobile_no"]}, {"name": "column_Tx3n", "fields": ["gender"]}]}, {"name": "organization_section", "hideBorder": true, "columns": [{"name": "column_S0J8", "fields": ["company_name"]}]}, {"name": "designation_section", "hideBorder": true, "columns": [{"name": "column_bsO8", "fields": ["designation"]}]}, {"name": "address_section", "hideBorder": true, "columns": [{"name": "column_W3VY", "fields": ["address"]}]}]',
},
"CRM Organization-Quick Entry": {
"doctype": "CRM Organization",
- "layout": '[{"label":"Organization Name","columns":1,"fields":["organization_name"],"hideLabel":true},{"label":"Website & Revenue","columns":2,"hideBorder":true,"fields":["website","annual_revenue"],"hideLabel":true},{"label":"Territory","columns":1,"hideBorder":true,"fields":["territory"],"hideLabel":true},{"label":"No of Employees & Industry","columns":2,"hideBorder":true,"fields":["no_of_employees","industry"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]',
+ "layout": '[{"name": "organization_section", "columns": [{"name": "column_zOuv", "fields": ["organization_name"]}]}, {"name": "website_revenue_section", "hideBorder": true, "columns": [{"name": "column_I5Dy", "fields": ["website"]}, {"name": "column_Rgss", "fields": ["annual_revenue"]}]}, {"name": "territory_section", "hideBorder": true, "columns": [{"name": "column_w6ap", "fields": ["territory"]}]}, {"name": "employee_industry_section", "hideBorder": true, "columns": [{"name": "column_u5tZ", "fields": ["no_of_employees"]}, {"name": "column_FFrT", "fields": ["industry"]}]}, {"name": "address_section", "hideBorder": true, "columns": [{"name": "column_O2dk", "fields": ["address"]}]}]',
},
"Address-Quick Entry": {
"doctype": "Address",
- "layout": '[{"label":"Address","columns":1,"fields":["address_title","address_type","address_line1","address_line2","city","state","country","pincode"],"hideLabel":true}]',
+ "layout": '[{"name": "details_section", "columns": [{"name": "column_uSSG", "fields": ["address_title", "address_type", "address_line1", "address_line2", "city", "state", "country", "pincode"]}]}]',
},
}
sidebar_fields_layouts = {
"CRM Lead-Side Panel": {
"doctype": "CRM Lead",
- "layout": '[{"label": "Details", "name": "details", "opened": true, "fields": ["organization", "website", "territory", "industry", "job_title", "source", "lead_owner"]}, {"label": "Person", "name": "person_tab", "opened": true, "fields": ["salutation", "first_name", "last_name", "email", "mobile_no"]}]',
+ "layout": '[{"label": "Details", "name": "details_section", "opened": true, "columns": [{"name": "column_kl92", "fields": ["organization", "website", "territory", "industry", "job_title", "source", "lead_owner"]}]}, {"label": "Person", "name": "person_section", "opened": true, "columns": [{"name": "column_XmW2", "fields": ["salutation", "first_name", "last_name", "email", "mobile_no"]}]}]',
},
"CRM Deal-Side Panel": {
"doctype": "CRM Deal",
- "layout": '[{"label":"Contacts","name":"contacts_section","opened":true,"editable":false,"contacts":[]},{"label":"Organization Details","name":"organization_tab","opened":true,"fields":["organization","website","territory","annual_revenue","close_date","probability","next_step","deal_owner"]}]',
+ "layout": '[{"label": "Contacts", "name": "contacts_section", "opened": true, "editable": false, "contacts": []}, {"label": "Organization Details", "name": "organization_section", "opened": true, "columns": [{"name": "column_na2Q", "fields": ["organization", "website", "territory", "annual_revenue", "close_date", "probability", "next_step", "deal_owner"]}]}]',
},
"Contact-Side Panel": {
"doctype": "Contact",
- "layout": '[{"label":"Details","name":"details","opened":true,"fields":["salutation","first_name","last_name","email_id","mobile_no","gender","company_name","designation","address"]}]',
+ "layout": '[{"label": "Details", "name": "details_section", "opened": true, "columns": [{"name": "column_eIWl", "fields": ["salutation", "first_name", "last_name", "email_id", "mobile_no", "gender", "company_name", "designation", "address"]}]}]',
},
"CRM Organization-Side Panel": {
"doctype": "CRM Organization",
- "layout": '[{"label":"Details","name":"details","opened":true,"fields":["organization_name","website","territory","industry","no_of_employees","address"]}]',
+ "layout": '[{"label": "Details", "name": "details_section", "opened": true, "columns": [{"name": "column_IJOV", "fields": ["organization_name", "website", "territory", "industry", "no_of_employees", "address"]}]}]',
},
}
data_fields_layouts = {
"CRM Lead-Data Fields": {
"doctype": "CRM Lead",
- "layout": '[{"no_tabs":true,"sections":[{"label": "Details", "name": "details", "opened": true, "fields": ["organization", "website", "territory", "industry", "job_title", "source", "lead_owner"]}, {"label": "Person", "name": "person_tab", "opened": true, "fields": ["salutation", "first_name", "last_name", "email", "mobile_no"]}]}]',
+ "layout": '[{"label": "Details", "name": "details_section", "opened": true, "columns": [{"name": "column_ZgLG", "fields": ["organization", "industry", "lead_owner"]}, {"name": "column_TbYq", "fields": ["website", "job_title"]}, {"name": "column_OKSX", "fields": ["territory", "source"]}]}, {"label": "Person", "name": "person_section", "opened": true, "columns": [{"name": "column_6c5g", "fields": ["salutation", "email"]}, {"name": "column_1n7Q", "fields": ["first_name", "mobile_no"]}, {"name": "column_cT6C", "fields": ["last_name"]}]}]',
},
"CRM Deal-Data Fields": {
"doctype": "CRM Deal",
- "layout": '[{"no_tabs":true,"sections":[{"label":"Organization Details","name":"organization_tab","opened":true,"fields":["organization","website","territory","annual_revenue","close_date","probability","next_step","deal_owner"]}]}]',
+ "layout": '[{"label": "Details", "name": "details_section", "opened": true, "columns": [{"name": "column_z9XL", "fields": ["organization", "annual_revenue", "next_step"]}, {"name": "column_gM4w", "fields": ["website", "close_date", "deal_owner"]}, {"name": "column_gWmE", "fields": ["territory", "probability"]}]}]',
},
}
diff --git a/crm/patches.txt b/crm/patches.txt
index a0e91a3d..f964d13f 100644
--- a/crm/patches.txt
+++ b/crm/patches.txt
@@ -8,4 +8,5 @@ crm.patches.v1_0.move_crm_note_data_to_fcrm_note
crm.patches.v1_0.create_email_template_custom_fields
crm.patches.v1_0.create_default_fields_layout #10/12/2024
crm.patches.v1_0.create_default_sidebar_fields_layout
-crm.patches.v1_0.update_deal_quick_entry_layout
\ No newline at end of file
+crm.patches.v1_0.update_deal_quick_entry_layout
+crm.patches.v1_0.update_layouts_to_new_format
\ No newline at end of file
diff --git a/crm/patches/v1_0/update_layouts_to_new_format.py b/crm/patches/v1_0/update_layouts_to_new_format.py
new file mode 100644
index 00000000..70cf9b61
--- /dev/null
+++ b/crm/patches/v1_0/update_layouts_to_new_format.py
@@ -0,0 +1,101 @@
+import json
+from math import ceil
+
+import frappe
+from frappe.utils import random_string
+
+
+def execute():
+ layouts = frappe.get_all("CRM Fields Layout", fields=["name", "layout", "type"])
+
+ for layout in layouts:
+ old_layout = layout.layout
+ new_layout = get_new_layout(old_layout, layout.type)
+
+ frappe.db.set_value("CRM Fields Layout", layout.name, "layout", new_layout)
+
+
+def get_new_layout(old_layout, type):
+ if isinstance(old_layout, str):
+ old_layout = json.loads(old_layout)
+ new_layout = []
+ already_converted = False
+
+ starts_with_sections = False
+
+ if not old_layout[0].get("sections"):
+ starts_with_sections = True
+
+ if starts_with_sections:
+ old_layout = [{"sections": old_layout}]
+
+ for tab in old_layout:
+ new_tab = tab.copy()
+ if "no_tabs" in new_tab:
+ new_tab.pop("no_tabs")
+ new_tab["sections"] = []
+ new_tab["name"] = "tab_" + str(random_string(4))
+ for section in tab.get("sections"):
+ section["name"] = section.get("name") or "section_" + str(random_string(4))
+
+ if section.get("label") == "Select Organization":
+ section["name"] = "organization_section"
+ section["hidden"] = 1
+ elif section.get("label") == "Organization Details":
+ section["name"] = "organization_details_section"
+ elif section.get("label") == "Select Contact":
+ section["name"] = "contact_section"
+ section["hidden"] = 1
+ elif section.get("label") == "Contact Details":
+ section["name"] = "contact_details_section"
+
+ if "contacts" in section:
+ new_tab["sections"].append(section)
+ continue
+ if isinstance(section.get("columns"), list):
+ already_converted = True
+ break
+ column_count = section.get("columns") or 3
+ if type == "Side Panel":
+ column_count = 1
+ fields = section.get("fields") or []
+
+ new_section = section.copy()
+
+ if "fields" in new_section:
+ new_section.pop("fields")
+ new_section["columns"] = []
+
+ if len(fields) == 0:
+ new_section["columns"].append({"name": "column_" + str(random_string(4)), "fields": []})
+ new_tab["sections"].append(new_section)
+ continue
+
+ if len(fields) == 1 and column_count > 1:
+ new_section["columns"].append(
+ {"name": "column_" + str(random_string(4)), "fields": [fields[0]]}
+ )
+ new_section["columns"].append({"name": "column_" + str(random_string(4)), "fields": []})
+ new_tab["sections"].append(new_section)
+ continue
+
+ fields_per_column = ceil(len(fields) / column_count)
+ for i in range(column_count):
+ new_column = {
+ "name": "column_" + str(random_string(4)),
+ "fields": fields[i * fields_per_column : (i + 1) * fields_per_column],
+ }
+ new_section["columns"].append(new_column)
+ new_tab["sections"].append(new_section)
+ new_layout.append(new_tab)
+
+ if starts_with_sections:
+ new_layout = new_layout[0].get("sections")
+
+ if already_converted:
+ new_layout = old_layout
+
+ if type == "Side Panel" and "sections" in new_layout[0]:
+ new_layout = new_layout[0].get("sections")
+
+ return json.dumps(new_layout)
diff --git a/frontend/src/components/Controls/Grid.vue b/frontend/src/components/Controls/Grid.vue
index e866bab1..0515015e 100644
--- a/frontend/src/components/Controls/Grid.vue
+++ b/frontend/src/components/Controls/Grid.vue
@@ -33,7 +33,7 @@
{{ __(field.label) }}
@@ -84,36 +84,71 @@
+
+
(row[field.fieldname] = v)"
+ :placeholder="field.placeholder"
+ :hideMe="true"
+ >
+
+
+
+
+
+
+
+
+
+ {{ getUser(option.value).full_name }}
+
+
+
+
@@ -170,6 +205,7 @@
:index="index"
:data="row"
:doctype="doctype"
+ :parentDoctype="parentDoctype"
/>
@@ -198,6 +234,7 @@
v-if="showGridRowFieldsModal"
v-model="showGridRowFieldsModal"
:doctype="doctype"
+ :parentDoctype="parentDoctype"
/>
{
function getFieldObj(field) {
return {
- label: field.label,
- name: field.fieldname,
- type: field.fieldtype,
- options: field.options,
- in_list_view: field.in_list_view,
+ ...field,
+ filters: field.link_filters && JSON.parse(field.link_filters),
+ placeholder: field.placeholder || field.label,
}
}
@@ -317,8 +356,8 @@ const toggleSelectRow = (row) => {
const addRow = () => {
const newRow = {}
fields.value?.forEach((field) => {
- if (field.type === 'Check') newRow[field.name] = false
- else newRow[field.name] = ''
+ if (field.fieldtype === 'Check') newRow[field.fieldname] = false
+ else newRow[field.fieldname] = ''
})
newRow.name = getRandom(10)
showRowList.value.push(false)
diff --git a/frontend/src/components/Controls/GridRowFieldsModal.vue b/frontend/src/components/Controls/GridRowFieldsModal.vue
index 4c18383f..fb0227b2 100644
--- a/frontend/src/components/Controls/GridRowFieldsModal.vue
+++ b/frontend/src/components/Controls/GridRowFieldsModal.vue
@@ -1,5 +1,5 @@
-