diff --git a/crm/api/doc.py b/crm/api/doc.py
index fae49446..f8e20b85 100644
--- a/crm/api/doc.py
+++ b/crm/api/doc.py
@@ -1,4 +1,5 @@
import frappe
+import json
from frappe import _
from frappe.model.document import get_controller
from frappe.model import no_value_fields
@@ -156,6 +157,43 @@ def get_group_by_fields(doctype: str):
return fields
+@frappe.whitelist()
+def get_quick_entry_fields(doctype: str):
+ sections = []
+ if frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": "Quick Entry"}):
+ layout = frappe.get_doc("CRM Fields Layout", {"dt": doctype, "type": "Quick Entry"})
+ else:
+ return []
+
+ if layout.layout:
+ sections = json.loads(layout.layout)
+
+ allowed_fields = []
+ for section in sections:
+ allowed_fields.extend(section.get("fields"))
+
+ fields = frappe.get_meta(doctype).fields
+ fields = [field for field in fields if field.fieldname in allowed_fields]
+
+ for section in sections:
+ for field in section.get("fields"):
+ field = next((f for f in fields if f.fieldname == field), None)
+ if field:
+ if field.fieldtype == "Select":
+ field.options = field.options.split("\n")
+ field.options = [{"label": _(option), "value": option} for option in field.options]
+ field.options.insert(0, {"label": "", "value": ""})
+ field = {
+ "label": _(field.label),
+ "name": field.fieldname,
+ "type": field.fieldtype,
+ "options": field.options,
+ "mandatory": field.reqd,
+ }
+ section["fields"][section.get("fields").index(field["name"])] = field
+
+ return sections or []
+
def get_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fields):
parent = "parent" if DocField._table_name == "tabDocField" else "dt"
return (
diff --git a/crm/fcrm/doctype/crm_fields_layout/__init__.py b/crm/fcrm/doctype/crm_fields_layout/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.js b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.js
new file mode 100644
index 00000000..6a3abeb8
--- /dev/null
+++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+// frappe.ui.form.on("CRM Fields Layout", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
new file mode 100644
index 00000000..c780557a
--- /dev/null
+++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
@@ -0,0 +1,73 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "format:{dt}-{type}",
+ "creation": "2024-06-07 16:42:05.495324",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "dt",
+ "column_break_post",
+ "type",
+ "section_break_ttpm",
+ "layout"
+ ],
+ "fields": [
+ {
+ "fieldname": "dt",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Document Type",
+ "options": "DocType",
+ "unique": 1
+ },
+ {
+ "fieldname": "type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Type",
+ "options": "Quick Entry\nSide Panel"
+ },
+ {
+ "fieldname": "section_break_ttpm",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "layout",
+ "fieldtype": "Code",
+ "label": "Layout",
+ "options": "JS"
+ },
+ {
+ "fieldname": "column_break_post",
+ "fieldtype": "Column Break"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2024-06-07 17:01:20.250697",
+ "modified_by": "Administrator",
+ "module": "FCRM",
+ "name": "CRM Fields Layout",
+ "naming_rule": "Expression",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "creation",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
new file mode 100644
index 00000000..36d8bb72
--- /dev/null
+++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class CRMFieldsLayout(Document):
+ pass
diff --git a/crm/fcrm/doctype/crm_fields_layout/test_crm_fields_layout.py b/crm/fcrm/doctype/crm_fields_layout/test_crm_fields_layout.py
new file mode 100644
index 00000000..bdc71c0a
--- /dev/null
+++ b/crm/fcrm/doctype/crm_fields_layout/test_crm_fields_layout.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestCRMFieldsLayout(FrappeTestCase):
+ pass
diff --git a/crm/install.py b/crm/install.py
index 351dd8ce..923cbd4e 100644
--- a/crm/install.py
+++ b/crm/install.py
@@ -13,6 +13,7 @@ def after_install():
add_default_lead_statuses()
add_default_deal_statuses()
add_default_communication_statuses()
+ add_default_fields_layout()
add_property_setter()
add_email_template_custom_fields()
frappe.db.commit()
@@ -108,6 +109,36 @@ def add_default_communication_statuses():
doc.status = status
doc.insert()
+def add_default_fields_layout():
+ layouts = {
+ "CRM Lead-Quick Entry": {
+ "doctype": "CRM Lead",
+ "layout": '[\n{\n"label": "Person",\n\t"fields": ["salutation", "first_name", "last_name", "email", "mobile_no", "gender"]\n},\n{\n"label": "Organization",\n\t"fields": ["organization", "website", "no_of_employees", "territory", "annual_revenue", "industry"]\n},\n{\n"label": "Other",\n"columns": 2,\n\t"fields": ["status", "lead_owner"]\n}\n]'
+ },
+ "CRM Deal-Quick Entry": {
+ "doctype": "CRM Deal",
+ "layout": '[\n{\n"label": "Select Organization",\n\t"fields": ["organization"]\n},\n{\n"label": "Organization Details",\n\t"fields": [{"label": "Organization Name", "name": "organization_name", "type": "Data"}, "website", "no_of_employees", "territory", "annual_revenue", {"label": "Industry", "name": "industry", "type": "Link", "options": "CRM Industry"}]\n},\n{\n"label": "Select Contact",\n\t"fields": [{"label": "Contact", "name": "contact", "type": "Link", "options": "Contact"}]\n},\n{\n"label": "Contact Details",\n\t"fields": [{"label": "Salutation", "name": "salutation", "type": "Link", "options": "Salutation"}, {"label": "First Name", "name": "first_name", "type": "Data"}, {"label": "Last Name", "name": "last_name", "type": "Data"}, "email", "mobile_no", {"label": "Gender", "name": "gender", "type": "Link", "options": "Gender"}]\n},\n{\n"label": "Other",\n"columns": 2,\n\t"fields": ["status", "deal_owner"]\n}\n]'
+ },
+ "Contact-Quick Entry": {
+ "doctype": "Contact",
+ "layout": '[\n{\n"label": "Salutation",\n"columns": 1,\n"fields": ["salutation"]\n},\n{\n"label": "Full Name",\n"columns": 2,\n"hideBorder": true,\n"fields": ["first_name", "last_name"]\n},\n{\n"label": "Email",\n"columns": 1,\n"hideBorder": true,\n"fields": ["email_id"]\n},\n{\n"label": "Mobile No. & Gender",\n"columns": 2,\n"hideBorder": true,\n"fields": ["mobile_no", "gender"]\n},\n{\n"label": "Organization",\n"columns": 1,\n"hideBorder": true,\n"fields": ["company_name"]\n},\n{\n"label": "Designation",\n"columns": 1,\n"hideBorder": true,\n"fields": ["designation"]\n}\n]'
+ },
+ "Organization-Quick Entry": {
+ "doctype": "CRM Organization",
+ "layout": '[\n{\n"label": "Organization Name",\n"columns": 1,\n"fields": ["organization_name"]\n},\n{\n"label": "Website & Revenue",\n"columns": 2,\n"hideBorder": true,\n"fields": ["website", "annual_revenue"]\n},\n{\n"label": "Territory",\n"columns": 1,\n"hideBorder": true,\n"fields": ["territory"]\n},\n{\n"label": "No of Employees & Industry",\n"columns": 2,\n"hideBorder": true,\n"fields": ["no_of_employees", "industry"]\n}\n]'
+ },
+ }
+
+ for layout in layouts:
+ if frappe.db.exists("CRM Fields Layout", layout):
+ continue
+
+ doc = frappe.new_doc("CRM Fields Layout")
+ doc.type = "Quick Entry"
+ doc.dt = layouts[layout]["doctype"]
+ doc.layout = layouts[layout]["layout"]
+ doc.insert()
+
def add_property_setter():
if not frappe.db.exists("Property Setter", {"name": "Contact-main-search_fields"}):
doc = frappe.new_doc("Property Setter")
diff --git a/crm/patches.txt b/crm/patches.txt
index a16089c3..c0204ec5 100644
--- a/crm/patches.txt
+++ b/crm/patches.txt
@@ -5,4 +5,5 @@ crm.patches.v1_0.move_crm_note_data_to_fcrm_note
[post_model_sync]
# Patches added in this section will be executed after doctypes are migrated
-crm.patches.v1_0.create_email_template_custom_fields
\ No newline at end of file
+crm.patches.v1_0.create_email_template_custom_fields
+crm.patches.v1_0.create_default_fields_layout
\ No newline at end of file
diff --git a/crm/patches/v1_0/create_default_fields_layout.py b/crm/patches/v1_0/create_default_fields_layout.py
new file mode 100644
index 00000000..383e7ac2
--- /dev/null
+++ b/crm/patches/v1_0/create_default_fields_layout.py
@@ -0,0 +1,5 @@
+
+from crm.install import add_default_fields_layout
+
+def execute():
+ add_default_fields_layout()
\ No newline at end of file
diff --git a/frontend/src/components/Fields.vue b/frontend/src/components/Fields.vue
index 84a59179..b93c2357 100644
--- a/frontend/src/components/Fields.vue
+++ b/frontend/src/components/Fields.vue
@@ -2,7 +2,7 @@
@@ -20,34 +20,34 @@
*
(data[field.name] = v)"
- :placeholder="__(field.placeholder)"
+ :placeholder="__(field.placeholder || field.label)"
:onCreate="field.create"
/>
(data[field.name] = v)"
- :placeholder="__(field.placeholder)"
+ :placeholder="__(field.placeholder || field.label)"
:hideMe="true"
>
@@ -64,7 +64,7 @@
-
+
diff --git a/frontend/src/components/Modals/ContactModal.vue b/frontend/src/components/Modals/ContactModal.vue
index 335380b0..7ec75161 100644
--- a/frontend/src/components/Modals/ContactModal.vue
+++ b/frontend/src/components/Modals/ContactModal.vue
@@ -57,7 +57,11 @@
{{ field.value }}
-
+
@@ -87,7 +91,7 @@ import AddressIcon from '@/components/Icons/AddressIcon.vue'
import CertificateIcon from '@/components/Icons/CertificateIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
-import { call } from 'frappe-ui'
+import { call, createResource } from 'frappe-ui'
import { ref, nextTick, watch, computed, h } from 'vue'
import { createToast } from '@/utils'
import { useRouter } from 'vue-router'
@@ -230,199 +234,124 @@ const detailFields = computed(() => {
return details.filter((detail) => detail.value)
})
-const sections = computed(() => {
- return [
- {
- section: 'Salutation',
- columns: 1,
- fields: [
- {
- label: 'Salutation',
- name: 'salutation',
- type: 'link',
- placeholder: 'Mr',
- doctype: 'Salutation',
- },
- ],
- },
- {
- section: 'Full Name',
- columns: 2,
- hideBorder: true,
- fields: [
- {
- label: 'First Name',
- name: 'first_name',
- type: 'data',
- mandatory: true,
- placeholder: 'John',
- },
- {
- label: 'Last Name',
- name: 'last_name',
- type: 'data',
- placeholder: 'Doe',
- },
- ],
- },
- {
- section: 'Email',
- columns: 1,
- hideBorder: true,
- fields: [
- {
- label: 'Email',
- name: 'email_id',
- type: props.contact?.data?.name ? 'dropdown' : 'data',
- placeholder: 'john@doe.com',
- options:
- props.contact.data?.email_ids?.map((email) => {
- return {
- name: email.name,
- value: email.email_id,
- selected: email.email_id === props.contact.data.email_id,
- placeholder: 'john@doe.com',
- onClick: () => {
- _contact.value.email_id = email.email_id
- setAsPrimary('email', email.email_id)
- },
- onSave: (option, isNew) => {
- if (isNew) {
- createNew('email', option.value)
- if (props.contact.data.email_ids.length === 1) {
- _contact.value.email_id = option.value
- }
+const sections = createResource({
+ url: 'crm.api.doc.get_quick_entry_fields',
+ cache: ['quickEntryFields', 'Contact'],
+ params: { doctype: 'Contact' },
+ auto: true,
+})
+
+const filteredSections = computed(() => {
+ let allSections = sections.data || []
+ if (!allSections.length) return []
+
+ allSections.forEach((s) => {
+ s.fields.forEach((field) => {
+ if (field.name == 'email_id') {
+ field.type = props.contact?.data?.name ? 'Dropdown' : 'Data'
+ field.options =
+ props.contact.data?.email_ids?.map((email) => {
+ return {
+ name: email.name,
+ value: email.email_id,
+ selected: email.email_id === props.contact.data.email_id,
+ placeholder: 'john@doe.com',
+ onClick: () => {
+ _contact.value.email_id = email.email_id
+ setAsPrimary('email', email.email_id)
+ },
+ onSave: (option, isNew) => {
+ if (isNew) {
+ createNew('email', option.value)
+ if (props.contact.data.email_ids.length === 1) {
+ _contact.value.email_id = option.value
+ }
+ } else {
+ editOption('Contact Email', option.name, option.value)
+ }
+ },
+ onDelete: async (option, isNew) => {
+ props.contact.data.email_ids =
+ props.contact.data.email_ids.filter(
+ (email) => email.name !== option.name
+ )
+ !isNew && (await deleteOption('Contact Email', option.name))
+ if (_contact.value.email_id === option.value) {
+ if (props.contact.data.email_ids.length === 0) {
+ _contact.value.email_id = ''
} else {
- editOption('Contact Email', option.name, option.value)
+ _contact.value.email_id = props.contact.data.email_ids.find(
+ (email) => email.is_primary
+ )?.email_id
}
- },
- onDelete: async (option, isNew) => {
- props.contact.data.email_ids =
- props.contact.data.email_ids.filter(
- (email) => email.name !== option.name
- )
- !isNew && (await deleteOption('Contact Email', option.name))
- if (_contact.value.email_id === option.value) {
- if (props.contact.data.email_ids.length === 0) {
- _contact.value.email_id = ''
- } else {
- _contact.value.email_id =
- props.contact.data.email_ids.find(
- (email) => email.is_primary
- )?.email_id
- }
+ }
+ },
+ }
+ }) || []
+ field.create = () => {
+ props.contact.data?.email_ids?.push({
+ name: 'new-1',
+ value: '',
+ selected: false,
+ isNew: true,
+ })
+ }
+ } else if (field.name == 'mobile_no' || field.name == 'actual_mobile_no') {
+ field.type = props.contact?.data?.name ? 'Dropdown' : 'Data'
+ field.name = 'actual_mobile_no'
+ field.options =
+ props.contact.data?.phone_nos?.map((phone) => {
+ return {
+ name: phone.name,
+ value: phone.phone,
+ selected: phone.phone === props.contact.data.actual_mobile_no,
+ onClick: () => {
+ _contact.value.actual_mobile_no = phone.phone
+ _contact.value.mobile_no = phone.phone
+ setAsPrimary('mobile_no', phone.phone)
+ },
+ onSave: (option, isNew) => {
+ if (isNew) {
+ createNew('phone', option.value)
+ if (props.contact.data.phone_nos.length === 1) {
+ _contact.value.actual_mobile_no = option.value
}
- },
- }
- }) || [],
- create: () => {
- props.contact.data?.email_ids?.push({
- name: 'new-1',
- value: '',
- selected: false,
- isNew: true,
- })
- },
- },
- ],
- },
- {
- section: 'Mobile No. & Gender',
- columns: 2,
- hideBorder: true,
- fields: [
- {
- label: 'Mobile No.',
- name: 'actual_mobile_no',
- type: props.contact?.data?.name ? 'dropdown' : 'data',
- placeholder: '+91 9876543210',
- options:
- props.contact.data?.phone_nos?.map((phone) => {
- return {
- name: phone.name,
- value: phone.phone,
- selected: phone.phone === props.contact.data.actual_mobile_no,
- placeholder: '+91 1234567890',
- onClick: () => {
- _contact.value.actual_mobile_no = phone.phone
- _contact.value.mobile_no = phone.phone
- setAsPrimary('mobile_no', phone.phone)
- },
- onSave: (option, isNew) => {
- if (isNew) {
- createNew('phone', option.value)
- if (props.contact.data.phone_nos.length === 1) {
- _contact.value.actual_mobile_no = option.value
- }
+ } else {
+ editOption('Contact Phone', option.name, option.value)
+ }
+ },
+ onDelete: async (option, isNew) => {
+ props.contact.data.phone_nos =
+ props.contact.data.phone_nos.filter(
+ (phone) => phone.name !== option.name
+ )
+ !isNew && (await deleteOption('Contact Phone', option.name))
+ if (_contact.value.actual_mobile_no === option.value) {
+ if (props.contact.data.phone_nos.length === 0) {
+ _contact.value.actual_mobile_no = ''
} else {
- editOption('Contact Phone', option.name, option.value)
+ _contact.value.actual_mobile_no =
+ props.contact.data.phone_nos.find(
+ (phone) => phone.is_primary_mobile_no
+ )?.phone
}
- },
- onDelete: async (option, isNew) => {
- props.contact.data.phone_nos =
- props.contact.data.phone_nos.filter(
- (phone) => phone.name !== option.name
- )
- !isNew && (await deleteOption('Contact Phone', option.name))
- if (_contact.value.actual_mobile_no === option.value) {
- if (props.contact.data.phone_nos.length === 0) {
- _contact.value.actual_mobile_no = ''
- } else {
- _contact.value.actual_mobile_no =
- props.contact.data.phone_nos.find(
- (phone) => phone.is_primary_mobile_no
- )?.phone
- }
- }
- },
- }
- }) || [],
- create: () => {
- props.contact.data?.phone_nos?.push({
- name: 'new-1',
- value: '',
- selected: false,
- isNew: true,
- })
- },
- },
- {
- label: 'Gender',
- name: 'gender',
- type: 'link',
- doctype: 'Gender',
- placeholder: 'Male',
- },
- ],
- },
- {
- section: 'Organization',
- columns: 1,
- hideBorder: true,
- fields: [
- {
- label: 'Organization',
- name: 'company_name',
- type: 'link',
- doctype: 'CRM Organization',
- placeholder: 'Frappé Technologies',
- },
- ],
- },
- {
- section: 'Designation',
- columns: 1,
- hideBorder: true,
- fields: [
- {
- label: 'Designation',
- name: 'designation',
- type: 'data',
- placeholder: 'CEO',
- },
- ],
- },
- ]
+ }
+ },
+ }
+ }) || []
+ field.create = () => {
+ props.contact.data?.phone_nos?.push({
+ name: 'new-1',
+ value: '',
+ selected: false,
+ isNew: true,
+ })
+ }
+ }
+ })
+ })
+
+ return allSections
})
async function setAsPrimary(field, value) {
diff --git a/frontend/src/components/Modals/DealModal.vue b/frontend/src/components/Modals/DealModal.vue
index e854033d..bf465ddc 100644
--- a/frontend/src/components/Modals/DealModal.vue
+++ b/frontend/src/components/Modals/DealModal.vue
@@ -7,7 +7,7 @@
}"
>
-
+
{{ __('Choose Existing Organization') }}
@@ -17,7 +17,12 @@
-
+
@@ -71,155 +76,66 @@ const isDealCreating = ref(false)
const chooseExistingContact = ref(false)
const chooseExistingOrganization = ref(false)
-const sections = computed(() => {
- let fields = []
+const sections = createResource({
+ url: 'crm.api.doc.get_quick_entry_fields',
+ cache: ['quickEntryFields', 'CRM Deal'],
+ params: { doctype: 'CRM Deal' },
+ auto: true,
+ transform: (data) => {
+ return data.forEach((section) => {
+ section.fields.forEach((field) => {
+ if (field.name == 'status') {
+ field.type = 'Select'
+ field.options = dealStatuses.value
+ field.prefix = getDealStatus(deal.status).iconColorClass
+ } else if (field.name == 'deal_owner') {
+ field.type = 'User'
+ }
+ })
+ })
+ },
+})
+
+const filteredSections = computed(() => {
+ let allSections = sections.data || []
+ if (!allSections.length) return []
+
+ let _filteredSections = []
+
if (chooseExistingOrganization.value) {
- fields.push({
- section: 'Select Organization',
- fields: [
- {
- label: 'Organization',
- name: 'organization',
- type: 'link',
- placeholder: 'Frappé Technologies',
- doctype: 'CRM Organization',
- },
- ],
- })
+ _filteredSections.push(
+ allSections.find((s) => s.label === 'Select Organization')
+ )
} else {
- fields.push({
- section: 'Organization Details',
- fields: [
- {
- label: 'Organization Name',
- name: 'organization_name',
- type: 'data',
- placeholder: 'Frappé Technologies',
- },
- {
- label: 'Website',
- name: 'website',
- type: 'data',
- placeholder: 'https://frappe.io',
- },
- {
- label: 'No of Employees',
- name: 'no_of_employees',
- type: 'select',
- options: [
- { label: __('1-10'), value: '1-10' },
- { label: __('11-50'), value: '11-50' },
- { label: __('51-200'), value: '51-200' },
- { label: __('201-500'), value: '201-500' },
- { label: __('501-1000'), value: '501-1000' },
- { label: __('1001-5000'), value: '1001-5000' },
- { label: __('5001-10000'), value: '5001-10000' },
- { label: __('10001+'), value: '10001+' },
- ],
- placeholder: '1-10',
- },
- {
- label: 'Territory',
- name: 'territory',
- type: 'link',
- doctype: 'CRM Territory',
- placeholder: 'India',
- },
- {
- label: 'Annual Revenue',
- name: 'annual_revenue',
- type: 'data',
- placeholder: '9,999,999',
- },
- {
- label: 'Industry',
- name: 'industry',
- type: 'link',
- doctype: 'CRM Industry',
- placeholder: 'Technology',
- },
- ],
- })
+ _filteredSections.push(
+ allSections.find((s) => s.label === 'Organization Details')
+ )
}
+
if (chooseExistingContact.value) {
- fields.push({
- section: 'Select Contact',
- fields: [
- {
- label: 'Contact',
- name: 'contact',
- type: 'link',
- placeholder: 'John Doe',
- doctype: 'Contact',
- },
- ],
- })
+ _filteredSections.push(
+ allSections.find((s) => s.label === 'Select Contact')
+ )
} else {
- fields.push({
- section: 'Contact Details',
- fields: [
- {
- label: 'Salutation',
- name: 'salutation',
- type: 'link',
- doctype: 'Salutation',
- placeholder: 'Mr',
- },
- {
- label: 'First Name',
- name: 'first_name',
- type: 'data',
- placeholder: 'John',
- },
- {
- label: 'Last Name',
- name: 'last_name',
- type: 'data',
- placeholder: 'Doe',
- },
- {
- label: 'Email',
- name: 'email',
- type: 'data',
- placeholder: 'john@doe.com',
- },
- {
- label: 'Mobile No',
- name: 'mobile_no',
- type: 'data',
- placeholder: '+91 1234567890',
- },
- {
- label: 'Gender',
- name: 'gender',
- type: 'link',
- doctype: 'Gender',
- placeholder: 'Male',
- },
- ],
- })
+ _filteredSections.push(
+ allSections.find((s) => s.label === 'Contact Details')
+ )
}
- fields.push({
- section: 'Deal Details',
- columns: 2,
- fields: [
- {
- label: 'Status',
- name: 'status',
- type: 'select',
- options: dealStatuses.value,
- prefix: getDealStatus(deal.status).iconColorClass,
- },
- {
- label: 'Deal Owner',
- name: 'deal_owner',
- type: 'user',
- placeholder: 'Deal Owner',
- doctype: 'User',
- },
- ],
+
+ allSections.forEach((s) => {
+ if (
+ ![
+ 'Select Organization',
+ 'Organization Details',
+ 'Select Contact',
+ 'Contact Details',
+ ].includes(s.label)
+ ) {
+ _filteredSections.push(s)
+ }
})
- return fields
+
+ return _filteredSections
})
const dealStatuses = computed(() => {
@@ -255,6 +171,10 @@ function createDeal() {
error.value = __('Invalid Email')
return error.value
}
+ if (!deal.status) {
+ error.value = __('Status is required')
+ return error.value
+ }
isDealCreating.value = true
},
onSuccess(name) {
diff --git a/frontend/src/components/Modals/LeadModal.vue b/frontend/src/components/Modals/LeadModal.vue
index ed92a0ef..54e7566e 100644
--- a/frontend/src/components/Modals/LeadModal.vue
+++ b/frontend/src/components/Modals/LeadModal.vue
@@ -7,7 +7,7 @@
}"
>
-
+
@@ -39,6 +39,26 @@ const router = useRouter()
const error = ref(null)
const isLeadCreating = ref(false)
+const sections = createResource({
+ url: 'crm.api.doc.get_quick_entry_fields',
+ cache: ['quickEntryFields', 'CRM Lead'],
+ params: { doctype: 'CRM Lead' },
+ auto: true,
+ transform: (data) => {
+ return data.forEach((section) => {
+ section.fields.forEach((field) => {
+ if (field.name == 'status') {
+ field.type = 'Select'
+ field.options = leadStatuses.value
+ field.prefix = getLeadStatus(lead.status).iconColorClass
+ } else if (field.name == 'lead_owner') {
+ field.type = 'User'
+ }
+ })
+ })
+ },
+})
+
const lead = reactive({
salutation: '',
first_name: '',
@@ -56,128 +76,6 @@ const lead = reactive({
lead_owner: '',
})
-const sections = computed(() => {
- return [
- {
- section: 'Contact Details',
- fields: [
- {
- label: 'Salutation',
- name: 'salutation',
- type: 'link',
- placeholder: 'Mr',
- doctype: 'Salutation',
- },
- {
- label: 'First Name',
- name: 'first_name',
- mandatory: true,
- type: 'data',
- placeholder: 'John',
- },
- {
- label: 'Last Name',
- name: 'last_name',
- type: 'data',
- placeholder: 'Doe',
- },
- {
- label: 'Email',
- name: 'email',
- type: 'data',
- placeholder: 'john@doe.com',
- },
- {
- label: 'Mobile No',
- name: 'mobile_no',
- type: 'data',
- placeholder: '+91 9876543210',
- },
- {
- label: 'Gender',
- name: 'gender',
- type: 'link',
- doctype: 'Gender',
- placeholder: 'Male',
- },
- ],
- },
- {
- section: 'Organization Details',
- fields: [
- {
- label: 'Organization',
- name: 'organization',
- type: 'data',
- placeholder: 'Frappé Technologies',
- },
- {
- label: 'Website',
- name: 'website',
- type: 'data',
- placeholder: 'https://frappe.io',
- },
- {
- label: 'No of Employees',
- name: 'no_of_employees',
- type: 'select',
- options: [
- { label: __('1-10'), value: '1-10' },
- { label: __('11-50'), value: '11-50' },
- { label: __('51-200'), value: '51-200' },
- { label: __('201-500'), value: '201-500' },
- { label: __('501-1000'), value: '501-1000' },
- { label: __('1001-5000'), value: '1001-5000' },
- { label: __('5001-10000'), value: '5001-10000' },
- { label: __('10001+'), value: '10001+' },
- ],
- placeholder: '1-10',
- },
- {
- label: 'Territory',
- name: 'territory',
- type: 'link',
- doctype: 'CRM Territory',
- placeholder: 'India',
- },
- {
- label: 'Annual Revenue',
- name: 'annual_revenue',
- type: 'data',
- placeholder: '1000000',
- },
- {
- label: 'Industry',
- name: 'industry',
- type: 'link',
- doctype: 'CRM Industry',
- placeholder: 'Technology',
- },
- ],
- },
- {
- section: 'Other Details',
- columns: 2,
- fields: [
- {
- label: 'Status',
- name: 'status',
- type: 'select',
- options: leadStatuses.value,
- prefix: getLeadStatus(lead.status).iconColorClass,
- },
- {
- label: 'Lead Owner',
- name: 'lead_owner',
- type: 'user',
- placeholder: 'Lead Owner',
- doctype: 'User',
- },
- ],
- },
- ]
-})
-
const createLead = createResource({
url: 'frappe.client.insert',
makeParams(values) {
@@ -224,6 +122,10 @@ function createNewLead() {
error.value = __('Invalid Email')
return error.value
}
+ if (!lead.status) {
+ error.value = __('Status is required')
+ return error.value
+ }
isLeadCreating.value = true
},
onSuccess(data) {
diff --git a/frontend/src/components/Modals/OrganizationModal.vue b/frontend/src/components/Modals/OrganizationModal.vue
index 0816f7e7..08a7e91a 100644
--- a/frontend/src/components/Modals/OrganizationModal.vue
+++ b/frontend/src/components/Modals/OrganizationModal.vue
@@ -35,7 +35,11 @@
{{ field.value }}
-
+
@@ -60,7 +64,7 @@ import EditIcon from '@/components/Icons/EditIcon.vue'
import WebsiteIcon from '@/components/Icons/WebsiteIcon.vue'
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
import TerritoryIcon from '@/components/Icons/TerritoryIcon.vue'
-import { call, FeatherIcon } from 'frappe-ui'
+import { call, FeatherIcon, createResource } from 'frappe-ui'
import { ref, nextTick, watch, computed, h } from 'vue'
import { useRouter } from 'vue-router'
@@ -220,83 +224,11 @@ const fields = computed(() => {
return details.filter((field) => field.value)
})
-const sections = computed(() => {
- return [
- {
- section: 'Organization Name',
- columns: 1,
- fields: [
- {
- label: 'Organization Name',
- name: 'organization_name',
- type: 'data',
- placeholder: 'Frappé Technologies',
- },
- ],
- },
- {
- section: 'Website & Revenue',
- columns: 2,
- hideBorder: true,
- fields: [
- {
- label: 'Website',
- name: 'website',
- type: 'data',
- placeholder: 'https://example.com',
- },
- {
- label: 'Annual Revenue',
- name: 'annual_revenue',
- type: 'data',
- placeholder: '9,999,999',
- },
- ],
- },
- {
- section: 'Territory',
- columns: 1,
- hideBorder: true,
- fields: [
- {
- label: 'Territory',
- name: 'territory',
- type: 'link',
- doctype: 'CRM Territory',
- placeholder: 'India',
- },
- ],
- },
- {
- section: 'No of Employees & Industry',
- columns: 2,
- hideBorder: true,
- fields: [
- {
- label: 'No of Employees',
- name: 'no_of_employees',
- type: 'select',
- options: [
- { label: __('1-10'), value: '1-10' },
- { label: __('11-50'), value: '11-50' },
- { label: __('51-200'), value: '51-200' },
- { label: __('201-500'), value: '201-500' },
- { label: __('501-1000'), value: '501-1000' },
- { label: __('1001-5000'), value: '1001-5000' },
- { label: __('5001-10000'), value: '5001-10000' },
- { label: __('10001+'), value: '10001+' },
- ],
- },
- {
- label: 'Industry',
- name: 'industry',
- type: 'link',
- doctype: 'CRM Industry',
- placeholder: 'Technology',
- },
- ],
- },
- ]
+const sections = createResource({
+ url: 'crm.api.doc.get_quick_entry_fields',
+ cache: ['quickEntryFields', 'CRM Organization'],
+ params: { doctype: 'CRM Organization' },
+ auto: true,
})
watch(