1
0
forked from test/crm

Merge pull request #51 from shariquerik/organization-fields

feat: Minor features
This commit is contained in:
Shariq Ansari 2024-01-04 21:54:46 +05:30 committed by GitHub
commit 34e3482090
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 139 additions and 71 deletions

View File

@ -188,11 +188,16 @@ def get_doctype_fields(doctype):
else: else:
section_fields.append(get_field_obj(field)) section_fields.append(get_field_obj(field))
all_fields = [] section_fields = []
for section in sections: for section in sections:
all_fields.append(sections[section]) section_fields.append(sections[section])
return all_fields fields = [field for field in fields if field.fieldtype not in "Tab Break"]
fields_meta = {}
for field in fields:
fields_meta[field.fieldname] = field
return section_fields, fields_meta
def get_field_obj(field): def get_field_obj(field):
@ -200,6 +205,9 @@ def get_field_obj(field):
"label": field.label, "label": field.label,
"type": get_type(field), "type": get_type(field),
"name": field.fieldname, "name": field.fieldname,
"hidden": field.hidden,
"reqd": field.reqd,
"read_only": field.read_only,
} }
obj["placeholder"] = "Add " + field.label + "..." obj["placeholder"] = "Add " + field.label + "..."

View File

@ -27,7 +27,7 @@ def get_deal(name):
fields=["contact", "is_primary"], fields=["contact", "is_primary"],
) )
deal["doctype_fields"] = get_doctype_fields("CRM Deal") deal["doctype_fields"], deal["all_fields"] = get_doctype_fields("CRM Deal")
deal["doctype"] = "CRM Deal" deal["doctype"] = "CRM Deal"
deal["_form_script"] = get_form_script('CRM Deal') deal["_form_script"] = get_form_script('CRM Deal')
return deal return deal

View File

@ -52,19 +52,17 @@
"label": "Probability" "label": "Probability"
}, },
{ {
"fetch_from": "organization.annual_revenue", "fetch_from": ".annual_revenue",
"fieldname": "annual_revenue", "fieldname": "annual_revenue",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Amount", "label": "Amount"
"read_only": 1
}, },
{ {
"fetch_from": "organization.website", "fetch_from": ".website",
"fieldname": "website", "fieldname": "website",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Website", "label": "Website",
"options": "URL", "options": "URL"
"read_only": 1
}, },
{ {
"fieldname": "close_date", "fieldname": "close_date",
@ -212,11 +210,11 @@
"options": "CRM Communication Status" "options": "CRM Communication Status"
}, },
{ {
"fetch_from": "organization.territory", "fetch_from": ".territory",
"fieldname": "territory", "fieldname": "territory",
"fieldtype": "Data", "fieldtype": "Link",
"label": "Territory", "label": "Territory",
"read_only": 1 "options": "CRM Territory"
}, },
{ {
"fieldname": "source", "fieldname": "source",
@ -227,7 +225,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2024-01-04 20:02:23.823826", "modified": "2024-01-04 20:53:31.618792",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "FCRM", "module": "FCRM",
"name": "CRM Deal", "name": "CRM Deal",

View File

@ -17,6 +17,7 @@ class CRMDeal(Document):
def validate(self): def validate(self):
self.set_primary_contact() self.set_primary_contact()
self.set_primary_email_mobile_no() self.set_primary_email_mobile_no()
self.update_organization()
if self.deal_owner and not self.is_new(): if self.deal_owner and not self.is_new():
self.assign_agent(self.deal_owner) self.assign_agent(self.deal_owner)
@ -61,6 +62,20 @@ class CRMDeal(Document):
self.email = "" self.email = ""
self.mobile_no = "" self.mobile_no = ""
def update_organization(self):
if self.organization:
if self.has_value_changed("organization"):
organization = frappe.get_cached_doc("CRM Organization", self.organization)
self.website = organization.website
self.territory = organization.territory
self.annual_revenue = organization.annual_revenue
if self.has_value_changed("website"):
frappe.db.set_value("CRM Organization", self.organization, "website", self.website)
if self.has_value_changed("territory"):
frappe.db.set_value("CRM Organization", self.organization, "territory", self.territory)
if self.has_value_changed("annual_revenue"):
frappe.db.set_value("CRM Organization", self.organization, "annual_revenue", self.annual_revenue)
def assign_agent(self, agent): def assign_agent(self, agent):
if not agent: if not agent:
return return

View File

@ -15,7 +15,7 @@ def get_lead(name):
frappe.throw(_("Lead not found"), frappe.DoesNotExistError) frappe.throw(_("Lead not found"), frappe.DoesNotExistError)
lead = lead.pop() lead = lead.pop()
lead["doctype_fields"] = get_doctype_fields("CRM Lead") lead["doctype_fields"], lead["all_fields"] = get_doctype_fields("CRM Lead")
lead["doctype"] = "CRM Lead" lead["doctype"] = "CRM Lead"
lead["_form_script"] = get_form_script('CRM Lead') lead["_form_script"] = get_form_script('CRM Lead')
return lead return lead

View File

@ -101,12 +101,10 @@
"search_index": 1 "search_index": 1
}, },
{ {
"fetch_from": "organization.website",
"fieldname": "website", "fieldname": "website",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Website", "label": "Website",
"options": "URL", "options": "URL"
"read_only": 1
}, },
{ {
"fieldname": "mobile_no", "fieldname": "mobile_no",
@ -155,11 +153,10 @@
"options": "CRM Lead Source" "options": "CRM Lead Source"
}, },
{ {
"fetch_from": "organization.industry",
"fieldname": "industry", "fieldname": "industry",
"fieldtype": "Data", "fieldtype": "Link",
"label": "Industry", "label": "Industry",
"read_only": 1 "options": "CRM Industry"
}, },
{ {
"fieldname": "image", "fieldname": "image",
@ -187,9 +184,8 @@
}, },
{ {
"fieldname": "organization", "fieldname": "organization",
"fieldtype": "Link", "fieldtype": "Data",
"label": "Organization", "label": "Organization"
"options": "CRM Organization"
}, },
{ {
"default": "0", "default": "0",
@ -273,17 +269,16 @@
"options": "CRM Communication Status" "options": "CRM Communication Status"
}, },
{ {
"fetch_from": "organization.territory",
"fieldname": "territory", "fieldname": "territory",
"fieldtype": "Data", "fieldtype": "Link",
"label": "Territory", "label": "Territory",
"read_only": 1 "options": "CRM Territory"
} }
], ],
"image_field": "image", "image_field": "image",
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2024-01-04 18:58:26.603817", "modified": "2024-01-04 21:34:32.388456",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "FCRM", "module": "FCRM",
"name": "CRM Lead", "name": "CRM Lead",

View File

@ -111,6 +111,26 @@ class CRMLead(Document):
return contact.name return contact.name
def create_organization(self):
if not self.organization:
return
existing_organization = frappe.db.exists("CRM Organization", {"organization_name": self.organization})
if existing_organization:
return existing_organization
organization = frappe.new_doc("CRM Organization")
organization.update(
{
"organization_name": self.organization,
"website": self.website,
"territory": self.territory,
"annual_revenue": self.annual_revenue,
}
)
organization.insert(ignore_permissions=True)
return organization.name
def contact_exists(self, throw=True): def contact_exists(self, throw=True):
email_exist = frappe.db.exists("Contact Email", {"email_id": self.email}) email_exist = frappe.db.exists("Contact Email", {"email_id": self.email})
phone_exist = frappe.db.exists("Contact Phone", {"phone": self.phone}) phone_exist = frappe.db.exists("Contact Phone", {"phone": self.phone})
@ -136,12 +156,12 @@ class CRMLead(Document):
return False return False
def create_deal(self, contact): def create_deal(self, contact, organization):
deal = frappe.new_doc("CRM Deal") deal = frappe.new_doc("CRM Deal")
deal.update( deal.update(
{ {
"lead": self.name, "lead": self.name,
"organization": self.organization, "organization": organization,
"deal_owner": self.lead_owner, "deal_owner": self.lead_owner,
"source": self.source, "source": self.source,
"contacts": [{"contact": contact}], "contacts": [{"contact": contact}],
@ -279,6 +299,7 @@ def convert_to_deal(lead):
lead.communication_status = 'Replied' lead.communication_status = 'Replied'
lead.save() lead.save()
contact = lead.create_contact(False) contact = lead.create_contact(False)
deal = lead.create_deal(contact) organization = lead.create_organization()
deal = lead.create_deal(contact, organization)
return deal return deal

View File

@ -1,11 +1,31 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
# import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class CRMOrganization(Document): class CRMOrganization(Document):
def on_update(self):
self.update_deal_organization_fields()
def update_deal_organization_fields(self):
if (
self.has_value_changed("website")
or self.has_value_changed("territory")
or self.has_value_changed("annual_revenue")
):
for deal in frappe.get_all(
"CRM Deal",
filters={"organization": self.name},
):
if self.has_value_changed("website"):
frappe.db.set_value("CRM Deal", deal.name, "website", self.website)
if self.has_value_changed("territory"):
frappe.db.set_value("CRM Deal", deal.name, "territory", self.territory)
if self.has_value_changed("annual_revenue"):
frappe.db.set_value("CRM Deal", deal.name, "annual_revenue", self.annual_revenue)
@staticmethod @staticmethod
def sort_options(): def sort_options():
return [ return [

View File

@ -3,16 +3,25 @@
<div <div
v-for="field in fields" v-for="field in fields"
:key="field.label" :key="field.label"
:class="[field.hidden && 'hidden']"
class="flex items-center gap-2 px-3 leading-5 first:mt-3" class="flex items-center gap-2 px-3 leading-5 first:mt-3"
> >
<div class="w-[106px] shrink-0 text-sm text-gray-600"> <div class="w-[106px] shrink-0 text-sm text-gray-600">
{{ field.label }} {{ field.label }}
<span class="text-red-500">{{ field.reqd ? ' *' : '' }}</span>
</div> </div>
<div <div
class="grid min-h-[28px] flex-1 items-center overflow-hidden text-base" class="grid min-h-[28px] flex-1 items-center overflow-hidden text-base"
> >
<Tooltip
v-if="field.read_only"
class="flex h-7 cursor-pointer items-center px-2 py-1 text-gray-600"
:text="field.tooltip"
>
{{ data[field.name] }}
</Tooltip>
<FormControl <FormControl
v-if=" v-else-if="
[ [
'email', 'email',
'number', 'number',
@ -70,13 +79,6 @@
@change="(data) => emit('update', field.name, data)" @change="(data) => emit('update', field.name, data)"
:onCreate="field.create" :onCreate="field.create"
/> />
<Tooltip
v-else-if="field.type === 'read_only'"
class="flex h-7 cursor-pointer items-center px-2 py-1"
:text="field.tooltip"
>
{{ field.value }}
</Tooltip>
<FormControl <FormControl
v-else v-else
class="form-control" class="form-control"

View File

@ -355,6 +355,8 @@ const organization = computed(() => {
function updateDeal(fieldname, value, callback) { function updateDeal(fieldname, value, callback) {
value = Array.isArray(fieldname) ? '' : value value = Array.isArray(fieldname) ? '' : value
if (validateRequired(fieldname, value)) return
createResource({ createResource({
url: 'frappe.client.set_value', url: 'frappe.client.set_value',
params: { params: {
@ -386,6 +388,20 @@ function updateDeal(fieldname, value, callback) {
}) })
} }
function validateRequired(fieldname, value) {
let meta = deal.data.all_fields || {}
if (meta[fieldname]?.reqd && !value) {
createToast({
title: 'Error Updating Deal',
text: `${meta[fieldname].label} is a required field`,
icon: 'x',
iconClasses: 'text-red-600',
})
return true
}
return false
}
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Deals', route: { name: 'Deals' } }] let items = [{ label: 'Deals', route: { name: 'Deals' } }]
items.push({ items.push({
@ -428,11 +444,13 @@ const detailSections = computed(() => {
function getParsedFields(sections, contacts) { function getParsedFields(sections, contacts) {
sections.forEach((section) => { sections.forEach((section) => {
section.fields.forEach((field) => { section.fields.forEach((field) => {
if (['website', 'annual_revenue'].includes(field.name)) { if (
field.value = organization.value?.[field.name] !deal.data.organization &&
field.tooltip = ['website', 'territory', 'annual_revenue'].includes(field.name)
'This field is read-only and is fetched from the organization' ) {
} else if (field.name == 'organization') { field.hidden = true
}
if (field.name == 'organization') {
field.create = (value, close) => { field.create = (value, close) => {
_organization.value.organization_name = value _organization.value.organization_name = value
showOrganizationModal.value = true showOrganizationModal.value = true

View File

@ -262,6 +262,8 @@ const organization = computed(() => {
function updateLead(fieldname, value, callback) { function updateLead(fieldname, value, callback) {
value = Array.isArray(fieldname) ? '' : value value = Array.isArray(fieldname) ? '' : value
if (validateRequired(fieldname, value)) return
createResource({ createResource({
url: 'frappe.client.set_value', url: 'frappe.client.set_value',
params: { params: {
@ -292,6 +294,20 @@ function updateLead(fieldname, value, callback) {
}) })
} }
function validateRequired(fieldname, value) {
let meta = lead.data.all_fields || {}
if (meta[fieldname]?.reqd && !value) {
createToast({
title: 'Error Updating Lead',
text: `${meta[fieldname].label} is a required field`,
icon: 'x',
iconClasses: 'text-red-600',
})
return true
}
return false
}
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Leads', route: { name: 'Leads' } }] let items = [{ label: 'Leads', route: { name: 'Leads' } }]
items.push({ items.push({
@ -335,34 +351,9 @@ function validateFile(file) {
const detailSections = computed(() => { const detailSections = computed(() => {
let data = lead.data let data = lead.data
if (!data) return [] if (!data) return []
return getParsedFields(data.doctype_fields, data.contacts) return data.doctype_fields
}) })
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 = (org) =>
router.push({
name: 'Organization',
params: { organizationId: org },
})
}
})
})
return sections
}
async function convertToDeal() { async function convertToDeal() {
let deal = await call('crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', { let deal = await call('crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', {
lead: lead.data.name, lead: lead.data.name,