1
0
forked from test/crm

feat: load custom fields in lead page

This commit is contained in:
Shariq Ansari 2023-11-21 15:47:08 +05:30
parent 9deb9afcac
commit ccd138dc21
3 changed files with 193 additions and 159 deletions

View File

@ -1,20 +1,13 @@
import json
import frappe
from frappe import _
from frappe.desk.form.load import get_docinfo
from pypika import Criterion
@frappe.whitelist()
def get_lead(name):
Lead = frappe.qb.DocType("CRM Lead")
query = (
frappe.qb.from_(Lead)
.select("*")
.where(Lead.name == name)
.limit(1)
)
query = frappe.qb.from_(Lead).select("*").where(Lead.name == name).limit(1)
lead = query.run(as_dict=True)
if not len(lead):
@ -22,3 +15,127 @@ def get_lead(name):
lead = lead.pop()
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()

View File

@ -8,36 +8,32 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"details",
"organization",
"website",
"industry",
"job_title",
"naming_series",
"source",
"person_tab",
"salutation",
"first_name",
"middle_name",
"last_name",
"column_break_izjs",
"lead_name",
"gender",
"image",
"column_break_lcuv",
"lead_owner",
"status",
"job_title",
"source",
"converted",
"email",
"mobile_no",
"organization_tab",
"section_break_uixv",
"organization",
"no_of_employees",
"lead_name",
"middle_name",
"gender",
"phone",
"column_break_dbsv",
"website",
"status",
"lead_owner",
"no_of_employees",
"annual_revenue",
"industry",
"contact_tab",
"section_break_ymew",
"email",
"column_break_sijm",
"mobile_no",
"column_break_sjtw",
"phone"
"image",
"converted"
],
"fields": [
{
@ -67,20 +63,12 @@
"fieldtype": "Data",
"label": "Last Name"
},
{
"fieldname": "column_break_lcuv",
"fieldtype": "Column Break"
},
{
"fieldname": "gender",
"fieldtype": "Link",
"label": "Gender",
"options": "Gender"
},
{
"fieldname": "column_break_izjs",
"fieldtype": "Column Break"
},
{
"default": "Open",
"fieldname": "status",
@ -91,10 +79,6 @@
"reqd": 1,
"search_index": 1
},
{
"fieldname": "section_break_ymew",
"fieldtype": "Section Break"
},
{
"fieldname": "email",
"fieldtype": "Data",
@ -110,20 +94,12 @@
"options": "URL",
"read_only": 1
},
{
"fieldname": "column_break_sijm",
"fieldtype": "Column Break"
},
{
"fieldname": "mobile_no",
"fieldtype": "Data",
"label": "Mobile No",
"options": "Phone"
},
{
"fieldname": "column_break_sjtw",
"fieldtype": "Column Break"
},
{
"fieldname": "phone",
"fieldtype": "Data",
@ -192,12 +168,8 @@
{
"fieldname": "organization_tab",
"fieldtype": "Tab Break",
"label": "Organization"
},
{
"fieldname": "contact_tab",
"fieldtype": "Tab Break",
"label": "Contact"
"label": "Others",
"read_only": 1
},
{
"fieldname": "organization",
@ -212,12 +184,22 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Converted"
},
{
"fieldname": "person_tab",
"fieldtype": "Tab Break",
"label": "Person"
},
{
"fieldname": "details",
"fieldtype": "Tab Break",
"label": "Details"
}
],
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-11-13 13:35:35.783003",
"modified": "2023-11-21 13:02:11.680600",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Lead",

View File

@ -135,7 +135,7 @@
<div class="flex flex-1 flex-col justify-between overflow-hidden">
<div class="flex flex-col overflow-y-auto">
<div
v-for="section in detailSections"
v-for="section in detailSections.data"
:key="section.label"
class="flex flex-col"
>
@ -173,6 +173,7 @@
<div class="flex-1 overflow-hidden">
<FormControl
v-if="field.type === 'select'"
class="form-control cursor-pointer [&_select]:cursor-pointer"
type="select"
:options="field.options"
:value="lead.data[field.name]"
@ -180,14 +181,7 @@
updateLead(field.name, $event.target.value)
"
:debounce="500"
class="form-control cursor-pointer [&_select]:cursor-pointer"
>
<template #prefix>
<IndicatorIcon
:class="leadStatuses[lead.data[field.name]].color"
/>
</template>
</FormControl>
/>
<FormControl
v-else-if="field.type === 'email'"
type="email"
@ -204,7 +198,7 @@
:value="lead.data[field.name]"
:doctype="field.doctype"
:placeholder="field.placeholder"
@change="(e) => field.change(e)"
@change="(data) => updateField(field.name, data)"
:onCreate="field.create"
/>
<FormControl
@ -246,12 +240,13 @@
class="flex h-7 cursor-pointer items-center px-2 py-1"
v-else-if="field.type === 'read_only'"
>
{{ field.value }}
{{ field.value || lead.data[field.name] }}
</Tooltip>
<FormControl
v-else
type="text"
:value="lead.data[field.name]"
:placeholder="field.placeholder"
@change.stop="
updateLead(field.name, $event.target.value)
"
@ -427,98 +422,38 @@ function validateFile(file) {
}
}
const detailSections = computed(() => {
return [
{
label: 'Details',
opened: true,
fields: [
{
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',
},
],
},
]
const detailSections = createResource({
url: 'crm.fcrm.doctype.crm_lead.api.get_lead_fields',
cache: 'leadFields',
auto: true,
transform: (data) => {
return getParsedFields(data)
},
})
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(() => {
return getOrganization(lead.data.organization)
})