Merge pull request #214 from shariquerik/quick-entry
feat: Add custom fields in quick entry modal
This commit is contained in:
commit
5cb0386fc7
@ -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 (
|
||||
|
||||
0
crm/fcrm/doctype/crm_fields_layout/__init__.py
Normal file
0
crm/fcrm/doctype/crm_fields_layout/__init__.py
Normal file
8
crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.js
Normal file
8
crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.js
Normal file
@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
73
crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
Normal file
73
crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json
Normal file
@ -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": []
|
||||
}
|
||||
9
crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
Normal file
9
crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py
Normal file
@ -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
|
||||
@ -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
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
crm.patches.v1_0.create_email_template_custom_fields
|
||||
crm.patches.v1_0.create_default_fields_layout
|
||||
5
crm/patches/v1_0/create_default_fields_layout.py
Normal file
5
crm/patches/v1_0/create_default_fields_layout.py
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
from crm.install import add_default_fields_layout
|
||||
|
||||
def execute():
|
||||
add_default_fields_layout()
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="flex flex-col gap-4">
|
||||
<div
|
||||
v-for="section in sections"
|
||||
:key="section.section"
|
||||
:key="section.label"
|
||||
class="first:border-t-0 first:pt-0"
|
||||
:class="section.hideBorder ? '' : 'border-t pt-4'"
|
||||
>
|
||||
@ -20,34 +20,34 @@
|
||||
<span class="text-red-500" v-if="field.mandatory">*</span>
|
||||
</div>
|
||||
<FormControl
|
||||
v-if="field.type === 'select'"
|
||||
v-if="field.type === 'Select'"
|
||||
type="select"
|
||||
class="form-control"
|
||||
:class="field.prefix ? 'prefix' : ''"
|
||||
:options="field.options"
|
||||
v-model="data[field.name]"
|
||||
:placeholder="__(field.placeholder)"
|
||||
:placeholder="__(field.placeholder || field.label)"
|
||||
>
|
||||
<template v-if="field.prefix" #prefix>
|
||||
<IndicatorIcon :class="field.prefix" />
|
||||
</template>
|
||||
</FormControl>
|
||||
<Link
|
||||
v-else-if="field.type === 'link'"
|
||||
v-else-if="field.type === 'Link'"
|
||||
class="form-control"
|
||||
:value="data[field.name]"
|
||||
:doctype="field.doctype"
|
||||
:doctype="field.options"
|
||||
@change="(v) => (data[field.name] = v)"
|
||||
:placeholder="__(field.placeholder)"
|
||||
:placeholder="__(field.placeholder || field.label)"
|
||||
:onCreate="field.create"
|
||||
/>
|
||||
<Link
|
||||
v-else-if="field.type === 'user'"
|
||||
v-else-if="field.type === 'User'"
|
||||
class="form-control"
|
||||
:value="getUser(data[field.name]).full_name"
|
||||
:doctype="field.doctype"
|
||||
:doctype="field.options"
|
||||
@change="(v) => (data[field.name] = v)"
|
||||
:placeholder="__(field.placeholder)"
|
||||
:placeholder="__(field.placeholder || field.label)"
|
||||
:hideMe="true"
|
||||
>
|
||||
<template #prefix>
|
||||
@ -64,7 +64,7 @@
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Link>
|
||||
<div v-else-if="field.type === 'dropdown'">
|
||||
<div v-else-if="field.type === 'Dropdown'">
|
||||
<NestedPopover>
|
||||
<template #target="{ open }">
|
||||
<Button
|
||||
@ -116,7 +116,7 @@
|
||||
<FormControl
|
||||
v-else
|
||||
type="text"
|
||||
:placeholder="__(field.placeholder)"
|
||||
:placeholder="__(field.placeholder || field.label)"
|
||||
v-model="data[field.name]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -57,7 +57,11 @@
|
||||
<div v-else>{{ field.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Fields v-else :sections="sections" :data="_contact" />
|
||||
<Fields
|
||||
v-else-if="filteredSections"
|
||||
:sections="filteredSections"
|
||||
:data="_contact"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!detailMode" class="px-4 pb-7 pt-4 sm:px-6">
|
||||
@ -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) {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
}"
|
||||
>
|
||||
<template #body-content>
|
||||
<div class="mb-4 grid sm:grid-cols-3 grid-cols-1 gap-4">
|
||||
<div class="mb-4 grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div class="flex items-center gap-3 text-sm text-gray-600">
|
||||
<div>{{ __('Choose Existing Organization') }}</div>
|
||||
<Switch v-model="chooseExistingOrganization" />
|
||||
@ -17,7 +17,12 @@
|
||||
<Switch v-model="chooseExistingContact" />
|
||||
</div>
|
||||
</div>
|
||||
<Fields class="border-t pt-4" :sections="sections" :data="deal" />
|
||||
<Fields
|
||||
v-if="filteredSections"
|
||||
class="border-t pt-4"
|
||||
:sections="filteredSections"
|
||||
:data="deal"
|
||||
/>
|
||||
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
|
||||
</template>
|
||||
<template #actions>
|
||||
@ -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) {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
}"
|
||||
>
|
||||
<template #body-content>
|
||||
<Fields :sections="sections" :data="lead" />
|
||||
<Fields v-if="sections.data" :sections="sections.data" :data="lead" />
|
||||
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
|
||||
</template>
|
||||
<template #actions>
|
||||
@ -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) {
|
||||
|
||||
@ -35,7 +35,11 @@
|
||||
<div>{{ field.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Fields v-else :sections="sections" :data="_organization" />
|
||||
<Fields
|
||||
v-else-if="sections.data"
|
||||
:sections="sections.data"
|
||||
:data="_organization"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!detailMode" class="px-4 pb-7 pt-4 sm:px-6">
|
||||
@ -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(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user