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 @@ + + 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 @@ + + + + + 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.value }} - - -
- -
-
-
-
- +
-
-
- 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" >