feat: load custom fields in lead page
This commit is contained in:
parent
9deb9afcac
commit
ccd138dc21
@ -1,20 +1,13 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.desk.form.load import get_docinfo
|
from pypika import Criterion
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_lead(name):
|
def get_lead(name):
|
||||||
Lead = frappe.qb.DocType("CRM Lead")
|
Lead = frappe.qb.DocType("CRM Lead")
|
||||||
|
|
||||||
query = (
|
query = frappe.qb.from_(Lead).select("*").where(Lead.name == name).limit(1)
|
||||||
frappe.qb.from_(Lead)
|
|
||||||
.select("*")
|
|
||||||
.where(Lead.name == name)
|
|
||||||
.limit(1)
|
|
||||||
)
|
|
||||||
|
|
||||||
lead = query.run(as_dict=True)
|
lead = query.run(as_dict=True)
|
||||||
if not len(lead):
|
if not len(lead):
|
||||||
@ -22,3 +15,127 @@ def get_lead(name):
|
|||||||
lead = lead.pop()
|
lead = lead.pop()
|
||||||
|
|
||||||
return lead
|
return lead
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_lead_fields():
|
||||||
|
DocField = frappe.qb.DocType("DocField")
|
||||||
|
CustomField = frappe.qb.DocType("Custom Field")
|
||||||
|
not_allowed_fieldtypes = [
|
||||||
|
"Section Break",
|
||||||
|
"Column Break",
|
||||||
|
]
|
||||||
|
restricted_fieldnames = [
|
||||||
|
"converted",
|
||||||
|
"lead_owner",
|
||||||
|
"status",
|
||||||
|
"image",
|
||||||
|
"naming_series"
|
||||||
|
]
|
||||||
|
|
||||||
|
fields = (
|
||||||
|
frappe.qb.from_(DocField)
|
||||||
|
.select(
|
||||||
|
DocField.fieldname,
|
||||||
|
DocField.fieldtype,
|
||||||
|
DocField.label,
|
||||||
|
DocField.name,
|
||||||
|
DocField.options,
|
||||||
|
DocField.read_only,
|
||||||
|
DocField.idx,
|
||||||
|
)
|
||||||
|
.where(DocField.parent == "CRM Lead")
|
||||||
|
.where(DocField.hidden == False)
|
||||||
|
.where(Criterion.notin(DocField.fieldtype, not_allowed_fieldtypes))
|
||||||
|
.where(Criterion.notin(DocField.fieldname, restricted_fieldnames))
|
||||||
|
.orderby(DocField.idx)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
custom_fields = (
|
||||||
|
frappe.qb.from_(CustomField)
|
||||||
|
.select(
|
||||||
|
CustomField.fieldname,
|
||||||
|
CustomField.fieldtype,
|
||||||
|
CustomField.label,
|
||||||
|
CustomField.name,
|
||||||
|
CustomField.options,
|
||||||
|
CustomField.read_only,
|
||||||
|
CustomField.idx,
|
||||||
|
CustomField.insert_after,
|
||||||
|
)
|
||||||
|
.where(CustomField.dt == "CRM Lead")
|
||||||
|
.where(CustomField.hidden == False)
|
||||||
|
.where(Criterion.notin(CustomField.fieldtype, not_allowed_fieldtypes))
|
||||||
|
.orderby(CustomField.idx)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
all_fields = []
|
||||||
|
all_fields.extend(fields)
|
||||||
|
|
||||||
|
# Add custom fields based on insert_after
|
||||||
|
for custom_field in custom_fields:
|
||||||
|
if custom_field.insert_after:
|
||||||
|
for i, field in enumerate(all_fields):
|
||||||
|
if field.fieldname == custom_field.insert_after:
|
||||||
|
all_fields.insert(i + 1, custom_field)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
all_fields.prepend(custom_field)
|
||||||
|
|
||||||
|
sections = {}
|
||||||
|
section_fields = []
|
||||||
|
last_section = None
|
||||||
|
|
||||||
|
for field in all_fields:
|
||||||
|
if field.fieldtype == "Tab Break" and last_section:
|
||||||
|
sections[last_section]["fields"] = section_fields
|
||||||
|
last_section = None
|
||||||
|
if field.read_only:
|
||||||
|
section_fields = []
|
||||||
|
continue
|
||||||
|
if field.fieldtype == "Tab Break":
|
||||||
|
section_fields = []
|
||||||
|
last_section = field.fieldname
|
||||||
|
sections[field.fieldname] = {
|
||||||
|
"label": field.label,
|
||||||
|
"opened": True,
|
||||||
|
"fields": [],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
section_fields.append(get_field_obj(field))
|
||||||
|
|
||||||
|
lead_fields = []
|
||||||
|
for section in sections:
|
||||||
|
lead_fields.append(sections[section])
|
||||||
|
|
||||||
|
return lead_fields
|
||||||
|
|
||||||
|
def get_field_obj(field):
|
||||||
|
obj = {
|
||||||
|
"label": field.label,
|
||||||
|
"type": get_type(field),
|
||||||
|
"name": field.fieldname,
|
||||||
|
}
|
||||||
|
|
||||||
|
obj["placeholder"] = "Add " + field.label.lower() + "..."
|
||||||
|
|
||||||
|
if field.fieldtype == "Link":
|
||||||
|
obj["placeholder"] = "Select " + field.label.lower() + "..."
|
||||||
|
obj["doctype"] = field.options
|
||||||
|
elif field.fieldtype == "Select":
|
||||||
|
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.read_only:
|
||||||
|
return "read_only"
|
||||||
|
return field.fieldtype.lower()
|
||||||
@ -8,36 +8,32 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"details",
|
||||||
|
"organization",
|
||||||
|
"website",
|
||||||
|
"industry",
|
||||||
|
"job_title",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
|
"source",
|
||||||
|
"person_tab",
|
||||||
"salutation",
|
"salutation",
|
||||||
"first_name",
|
"first_name",
|
||||||
"middle_name",
|
|
||||||
"last_name",
|
"last_name",
|
||||||
"column_break_izjs",
|
"email",
|
||||||
"lead_name",
|
"mobile_no",
|
||||||
"gender",
|
|
||||||
"image",
|
|
||||||
"column_break_lcuv",
|
|
||||||
"lead_owner",
|
|
||||||
"status",
|
|
||||||
"job_title",
|
|
||||||
"source",
|
|
||||||
"converted",
|
|
||||||
"organization_tab",
|
"organization_tab",
|
||||||
"section_break_uixv",
|
"section_break_uixv",
|
||||||
"organization",
|
"lead_name",
|
||||||
"no_of_employees",
|
"middle_name",
|
||||||
|
"gender",
|
||||||
|
"phone",
|
||||||
"column_break_dbsv",
|
"column_break_dbsv",
|
||||||
"website",
|
"status",
|
||||||
|
"lead_owner",
|
||||||
|
"no_of_employees",
|
||||||
"annual_revenue",
|
"annual_revenue",
|
||||||
"industry",
|
"image",
|
||||||
"contact_tab",
|
"converted"
|
||||||
"section_break_ymew",
|
|
||||||
"email",
|
|
||||||
"column_break_sijm",
|
|
||||||
"mobile_no",
|
|
||||||
"column_break_sjtw",
|
|
||||||
"phone"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -67,20 +63,12 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Last Name"
|
"label": "Last Name"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_lcuv",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "gender",
|
"fieldname": "gender",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Gender",
|
"label": "Gender",
|
||||||
"options": "Gender"
|
"options": "Gender"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_izjs",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "Open",
|
"default": "Open",
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
@ -91,10 +79,6 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "section_break_ymew",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "email",
|
"fieldname": "email",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@ -110,20 +94,12 @@
|
|||||||
"options": "URL",
|
"options": "URL",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_sijm",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "mobile_no",
|
"fieldname": "mobile_no",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
"options": "Phone"
|
"options": "Phone"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_sjtw",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "phone",
|
"fieldname": "phone",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@ -192,12 +168,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "organization_tab",
|
"fieldname": "organization_tab",
|
||||||
"fieldtype": "Tab Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Organization"
|
"label": "Others",
|
||||||
},
|
"read_only": 1
|
||||||
{
|
|
||||||
"fieldname": "contact_tab",
|
|
||||||
"fieldtype": "Tab Break",
|
|
||||||
"label": "Contact"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "organization",
|
"fieldname": "organization",
|
||||||
@ -212,12 +184,22 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Converted"
|
"label": "Converted"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "person_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Person"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "details",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Details"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-13 13:35:35.783003",
|
"modified": "2023-11-21 13:02:11.680600",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM Lead",
|
"name": "CRM Lead",
|
||||||
|
|||||||
@ -135,7 +135,7 @@
|
|||||||
<div class="flex flex-1 flex-col justify-between overflow-hidden">
|
<div class="flex flex-1 flex-col justify-between overflow-hidden">
|
||||||
<div class="flex flex-col overflow-y-auto">
|
<div class="flex flex-col overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
v-for="section in detailSections"
|
v-for="section in detailSections.data"
|
||||||
:key="section.label"
|
:key="section.label"
|
||||||
class="flex flex-col"
|
class="flex flex-col"
|
||||||
>
|
>
|
||||||
@ -173,6 +173,7 @@
|
|||||||
<div class="flex-1 overflow-hidden">
|
<div class="flex-1 overflow-hidden">
|
||||||
<FormControl
|
<FormControl
|
||||||
v-if="field.type === 'select'"
|
v-if="field.type === 'select'"
|
||||||
|
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
||||||
type="select"
|
type="select"
|
||||||
:options="field.options"
|
:options="field.options"
|
||||||
:value="lead.data[field.name]"
|
:value="lead.data[field.name]"
|
||||||
@ -180,14 +181,7 @@
|
|||||||
updateLead(field.name, $event.target.value)
|
updateLead(field.name, $event.target.value)
|
||||||
"
|
"
|
||||||
:debounce="500"
|
:debounce="500"
|
||||||
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
/>
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<IndicatorIcon
|
|
||||||
:class="leadStatuses[lead.data[field.name]].color"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</FormControl>
|
|
||||||
<FormControl
|
<FormControl
|
||||||
v-else-if="field.type === 'email'"
|
v-else-if="field.type === 'email'"
|
||||||
type="email"
|
type="email"
|
||||||
@ -204,7 +198,7 @@
|
|||||||
:value="lead.data[field.name]"
|
:value="lead.data[field.name]"
|
||||||
:doctype="field.doctype"
|
:doctype="field.doctype"
|
||||||
:placeholder="field.placeholder"
|
:placeholder="field.placeholder"
|
||||||
@change="(e) => field.change(e)"
|
@change="(data) => updateField(field.name, data)"
|
||||||
:onCreate="field.create"
|
:onCreate="field.create"
|
||||||
/>
|
/>
|
||||||
<FormControl
|
<FormControl
|
||||||
@ -246,12 +240,13 @@
|
|||||||
class="flex h-7 cursor-pointer items-center px-2 py-1"
|
class="flex h-7 cursor-pointer items-center px-2 py-1"
|
||||||
v-else-if="field.type === 'read_only'"
|
v-else-if="field.type === 'read_only'"
|
||||||
>
|
>
|
||||||
{{ field.value }}
|
{{ field.value || lead.data[field.name] }}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<FormControl
|
<FormControl
|
||||||
v-else
|
v-else
|
||||||
type="text"
|
type="text"
|
||||||
:value="lead.data[field.name]"
|
:value="lead.data[field.name]"
|
||||||
|
:placeholder="field.placeholder"
|
||||||
@change.stop="
|
@change.stop="
|
||||||
updateLead(field.name, $event.target.value)
|
updateLead(field.name, $event.target.value)
|
||||||
"
|
"
|
||||||
@ -427,98 +422,38 @@ function validateFile(file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const detailSections = computed(() => {
|
const detailSections = createResource({
|
||||||
return [
|
url: 'crm.fcrm.doctype.crm_lead.api.get_lead_fields',
|
||||||
{
|
cache: 'leadFields',
|
||||||
label: 'Details',
|
auto: true,
|
||||||
opened: true,
|
transform: (data) => {
|
||||||
fields: [
|
return getParsedFields(data)
|
||||||
{
|
},
|
||||||
label: 'Organization',
|
|
||||||
type: 'link',
|
|
||||||
name: 'organization',
|
|
||||||
placeholder: 'Select organization',
|
|
||||||
doctype: 'CRM Organization',
|
|
||||||
change: (data) => data && updateField('organization', data),
|
|
||||||
create: (value, close) => {
|
|
||||||
_organization.value.organization_name = value
|
|
||||||
showOrganizationModal.value = true
|
|
||||||
close()
|
|
||||||
},
|
|
||||||
link: () =>
|
|
||||||
router.push({
|
|
||||||
name: 'Organization',
|
|
||||||
params: { organizationId: lead.data.organization },
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Website',
|
|
||||||
type: 'read_only',
|
|
||||||
name: 'website',
|
|
||||||
value: organization.value?.website,
|
|
||||||
tooltip:
|
|
||||||
'It is a read only field, value is fetched from organization',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Industry',
|
|
||||||
type: 'read_only',
|
|
||||||
name: 'industry',
|
|
||||||
value: organization.value?.industry,
|
|
||||||
tooltip:
|
|
||||||
'It is a read only field, value is fetched from organization',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Job title',
|
|
||||||
type: 'data',
|
|
||||||
name: 'job_title',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Source',
|
|
||||||
type: 'link',
|
|
||||||
name: 'source',
|
|
||||||
placeholder: 'Select source...',
|
|
||||||
doctype: 'CRM Lead Source',
|
|
||||||
change: (data) => updateField('source', data),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Person',
|
|
||||||
opened: true,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: 'Salutation',
|
|
||||||
type: 'link',
|
|
||||||
name: 'salutation',
|
|
||||||
placeholder: 'Mr./Mrs./Ms...',
|
|
||||||
doctype: 'Salutation',
|
|
||||||
change: (data) => updateField('salutation', data),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'First name',
|
|
||||||
type: 'data',
|
|
||||||
name: 'first_name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Last name',
|
|
||||||
type: 'data',
|
|
||||||
name: 'last_name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Email',
|
|
||||||
type: 'email',
|
|
||||||
name: 'email',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Mobile no.',
|
|
||||||
type: 'phone',
|
|
||||||
name: 'mobile_no',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function getParsedFields(sections) {
|
||||||
|
sections.forEach((section) => {
|
||||||
|
section.fields.forEach((field) => {
|
||||||
|
if (['website', 'industry'].includes(field.name)) {
|
||||||
|
field.value = organization.value?.[field.name]
|
||||||
|
} else if (field.name == 'organization') {
|
||||||
|
field.create = (value, close) => {
|
||||||
|
_organization.value.organization_name = value
|
||||||
|
showOrganizationModal.value = true
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
field.link = () =>
|
||||||
|
router.push({
|
||||||
|
name: 'Organization',
|
||||||
|
params: { organizationId: lead.data.organization },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return sections
|
||||||
|
}
|
||||||
|
|
||||||
const organization = computed(() => {
|
const organization = computed(() => {
|
||||||
return getOrganization(lead.data.organization)
|
return getOrganization(lead.data.organization)
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user