diff --git a/crm/api/doc.py b/crm/api/doc.py
index 6ff2788e..dd0c7cc9 100644
--- a/crm/api/doc.py
+++ b/crm/api/doc.py
@@ -45,3 +45,79 @@ def get_filterable_fields(doctype: str):
res = []
res.extend(from_doc_fields)
return res
+
+@frappe.whitelist()
+def get_doctype_fields(doctype):
+ not_allowed_fieldtypes = [
+ "Section Break",
+ "Column Break",
+ ]
+
+ fields = frappe.get_meta(doctype).fields
+ fields = [field for field in fields if field.fieldtype not in not_allowed_fieldtypes]
+
+ sections = {}
+ section_fields = []
+ last_section = None
+
+ for field in 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":
+ if field.read_only:
+ section_fields = []
+ continue
+ section_fields = []
+ last_section = field.fieldname
+ sections[field.fieldname] = {
+ "label": field.label,
+ "opened": True,
+ "fields": [],
+ }
+ else:
+ section_fields.append(get_field_obj(field))
+
+ all_fields = []
+ for section in sections:
+ all_fields.append(sections[section])
+
+ return all_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.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()
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json
index 1363f809..8e75e2b2 100644
--- a/crm/fcrm/doctype/crm_deal/crm_deal.json
+++ b/crm/fcrm/doctype/crm_deal/crm_deal.json
@@ -6,23 +6,24 @@
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
- "naming_series",
+ "organization_tab",
"organization",
"website",
"annual_revenue",
- "column_break_afce",
- "deal_owner",
"close_date",
- "status",
"probability",
"next_step",
- "section_break_eepu",
- "lead",
- "column_break_bqvs",
"contacts_tab",
"email",
"mobile_no",
- "contacts"
+ "contacts",
+ "others_tab",
+ "naming_series",
+ "status",
+ "deal_owner",
+ "section_break_eepu",
+ "lead",
+ "column_break_bqvs"
],
"fields": [
{
@@ -39,19 +40,17 @@
{
"fetch_from": "organization.annual_revenue",
"fieldname": "annual_revenue",
- "fieldtype": "Int",
- "label": "Annual Revenue"
- },
- {
- "fieldname": "column_break_afce",
- "fieldtype": "Column Break"
+ "fieldtype": "Currency",
+ "label": "Amount",
+ "read_only": 1
},
{
"fetch_from": "organization.website",
"fieldname": "website",
"fieldtype": "Data",
"label": "Website",
- "options": "URL"
+ "options": "URL",
+ "read_only": 1
},
{
"fieldname": "close_date",
@@ -92,7 +91,8 @@
{
"fieldname": "contacts_tab",
"fieldtype": "Tab Break",
- "label": "Contacts"
+ "label": "Contacts",
+ "read_only": 1
},
{
"fieldname": "email",
@@ -121,11 +121,22 @@
"fieldtype": "Table",
"label": "Contacts",
"options": "CRM Contacts"
+ },
+ {
+ "fieldname": "others_tab",
+ "fieldtype": "Tab Break",
+ "label": "Others",
+ "read_only": 1
+ },
+ {
+ "fieldname": "organization_tab",
+ "fieldtype": "Tab Break",
+ "label": "Organization"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2023-11-09 19:58:15.620483",
+ "modified": "2023-11-22 17:52:31.595525",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Deal",
diff --git a/crm/fcrm/doctype/crm_lead/api.py b/crm/fcrm/doctype/crm_lead/api.py
index 4abd959a..f51be6fa 100644
--- a/crm/fcrm/doctype/crm_lead/api.py
+++ b/crm/fcrm/doctype/crm_lead/api.py
@@ -1,24 +1,16 @@
-import json
-
import frappe
from frappe import _
-from frappe.desk.form.load import get_docinfo
@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):
frappe.throw(_("Lead not found"), frappe.DoesNotExistError)
lead = lead.pop()
- return lead
+ return lead
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.json b/crm/fcrm/doctype/crm_lead/crm_lead.json
index 3aaa2ad6..c885e4a7 100644
--- a/crm/fcrm/doctype/crm_lead/crm_lead.json
+++ b/crm/fcrm/doctype/crm_lead/crm_lead.json
@@ -8,36 +8,32 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "naming_series",
- "salutation",
- "first_name",
- "middle_name",
- "last_name",
- "column_break_izjs",
- "lead_name",
- "gender",
- "image",
- "column_break_lcuv",
- "lead_owner",
- "status",
+ "details",
+ "organization",
+ "website",
+ "industry",
"job_title",
"source",
- "converted",
+ "person_tab",
+ "salutation",
+ "first_name",
+ "last_name",
+ "email",
+ "mobile_no",
"organization_tab",
"section_break_uixv",
- "organization",
- "no_of_employees",
+ "naming_series",
+ "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-22 13:03:02.261001",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Lead",
diff --git a/frontend/src/components/Section.vue b/frontend/src/components/Section.vue
new file mode 100644
index 00000000..20e4573e
--- /dev/null
+++ b/frontend/src/components/Section.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+ {{ label || 'Untitled' }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/SectionFields.vue b/frontend/src/components/SectionFields.vue
new file mode 100644
index 00000000..12d6dbd0
--- /dev/null
+++ b/frontend/src/components/SectionFields.vue
@@ -0,0 +1,119 @@
+
+
+
+
+ {{ field.label }}
+
+
+
+
+ emit('update', field.name, data)"
+ :onCreate="field.create"
+ />
+
+ {{ field.value }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/Toggler.vue b/frontend/src/components/Toggler.vue
deleted file mode 100644
index 01060875..00000000
--- a/frontend/src/components/Toggler.vue
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
diff --git a/frontend/src/pages/Deal.vue b/frontend/src/pages/Deal.vue
index 42dd6ffa..c4a027a4 100644
--- a/frontend/src/pages/Deal.vue
+++ b/frontend/src/pages/Deal.vue
@@ -96,24 +96,13 @@
-
-
-
-
- {{ section.label }}
-
+
-
-
+
+
+
+
-
- {{ field.label }}
-
-
- field.change(e)"
- :onCreate="field.create"
- />
-
-
- {{ field.value }}
-
-
-
-
-
-
-
-
-
+
+
-
-
- {{ getContactByName(contact.name).full_name }}
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
+
-
-
-
+
+
+
+
+
+
-
-
- No contacts added
-
+ class="flex flex-col gap-1.5 text-base text-gray-800"
+ >
+
+
+ {{ getContactByName(contact.name).email_id }}
+
+
+
+ {{ getContactByName(contact.name).mobile_no }}
+
+
+
+
-
-
+
+ No contacts added
+
+
+
@@ -353,12 +265,13 @@ import LinkIcon from '@/components/Icons/LinkIcon.vue'
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
import SuccessIcon from '@/components/Icons/SuccessIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
-import Toggler from '@/components/Toggler.vue'
import Activities from '@/components/Activities.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
import ContactModal from '@/components/Modals/ContactModal.vue'
import Link from '@/components/Controls/Link.vue'
+import Section from '@/components/Section.vue'
+import SectionFields from '@/components/SectionFields.vue'
import {
dealStatuses,
statusDropdownOptions,
@@ -474,78 +387,53 @@ const tabs = [
},
]
-const detailSections = computed(() => {
- return [
- {
- label: 'Organization',
- opened: true,
- fields: [
- {
- label: 'Organization',
- type: 'link',
- name: 'organization',
- placeholder: 'Select organization',
- doctype: 'CRM Organization',
- change: (data) => updateField('organization', data),
- create: (value, close) => {
- _organization.value.organization_name = value
- showOrganizationModal.value = true
- close()
- },
- link: () => {
- router.push({
- name: 'Organization',
- params: { organizationId: organization.value.name },
- })
- },
- },
- {
- label: 'Website',
- type: 'read_only',
- name: 'website',
- value: organization.value?.website,
- tooltip:
- 'It is a read only field, value is fetched from organization',
- },
- {
- label: 'Amount',
- type: 'read_only',
- name: 'annual_revenue',
- value: organization.value?.annual_revenue,
- tooltip:
- 'It is a read only field, value is fetched from organization',
- },
- {
- label: 'Close date',
- type: 'date',
- name: 'close_date',
- },
- {
- label: 'Probability',
- type: 'data',
- name: 'probability',
- },
- {
- label: 'Next step',
- type: 'data',
- name: 'next_step',
- },
- ],
- },
- {
- label: 'Contacts',
- opened: true,
- contacts: deal.data.contacts.map((contact) => {
- return {
- name: contact.contact,
- is_primary: contact.is_primary,
- opened: false,
- }
- }),
- },
- ]
+const detailSections = createResource({
+ url: 'crm.api.doc.get_doctype_fields',
+ params: { doctype: 'CRM Deal' },
+ cache: 'dealFields',
+ auto: true,
+ transform: (data) => {
+ return getParsedFields(data)
+ },
})
+function getParsedFields(sections) {
+ sections.forEach((section) => {
+ section.fields.forEach((field) => {
+ if (['website', 'annual_revenue'].includes(field.name)) {
+ field.value = organization.value?.[field.name]
+ field.tooltip =
+ 'This field is read-only and is fetched from the organization'
+ } 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: deal.data.organization },
+ })
+ }
+ })
+ })
+
+ let contactSection = {
+ label: 'Contacts',
+ opened: true,
+ contacts: deal.data.contacts.map((contact) => {
+ return {
+ name: contact.contact,
+ is_primary: contact.is_primary,
+ opened: false,
+ }
+ }),
+ }
+
+ return [...sections, contactSection]
+}
+
const showContactModal = ref(false)
const _contact = ref({})
@@ -628,26 +516,3 @@ function updateField(name, value, callback) {
})
}
-
-
diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue
index 7e25019b..82176221 100644
--- a/frontend/src/pages/Lead.vue
+++ b/frontend/src/pages/Lead.vue
@@ -57,7 +57,7 @@
:validateFile="validateFile"
>
-
+
-
-
-
-
- {{ section.label }}
-
-
-
-
-
-
- {{ field.label }}
-
-
-
-
-
-
-
-
- field.change(e)"
- :onCreate="field.create"
- />
- updateField('lead_owner', option.email)
- "
- class="form-control"
- :placeholder="field.placeholder"
- >
-
-
-
-
-
-
-
-
- {{ field.value }}
-
-
-
-
-
-
-
-
+
@@ -298,13 +173,12 @@ import NoteIcon from '@/components/Icons/NoteIcon.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue'
import LinkIcon from '@/components/Icons/LinkIcon.vue'
-import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
-import Toggler from '@/components/Toggler.vue'
import Activities from '@/components/Activities.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
-import Link from '@/components/Controls/Link.vue'
+import Section from '@/components/Section.vue'
+import SectionFields from '@/components/SectionFields.vue'
import {
leadStatuses,
statusDropdownOptions,
@@ -427,98 +301,41 @@ 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.api.doc.get_doctype_fields',
+ params: { doctype: 'CRM Lead' },
+ 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]
+ field.tooltip =
+ 'This field is read-only and is fetched from the organization'
+ } 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)
})
@@ -540,27 +357,3 @@ function updateField(name, value, callback) {
})
}
-
-