Merge pull request #24 from shariquerik/create-deal-doctype
refactor: Create deal doctype & fix code accordingly
This commit is contained in:
commit
168a3b37d7
226
crm/api/activities.py
Normal file
226
crm/api/activities.py
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.desk.form.load import get_docinfo
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_activities(name):
|
||||||
|
if frappe.db.exists("CRM Deal", name):
|
||||||
|
return get_deal_activities(name)
|
||||||
|
elif frappe.db.exists("CRM Lead", name):
|
||||||
|
return get_lead_activities(name)
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Document not found"), frappe.DoesNotExistError)
|
||||||
|
|
||||||
|
def get_deal_activities(name):
|
||||||
|
get_docinfo('', "CRM Deal", name)
|
||||||
|
docinfo = frappe.response["docinfo"]
|
||||||
|
deal_fields_meta = frappe.get_meta("CRM Deal").fields
|
||||||
|
|
||||||
|
doc = frappe.db.get_values("CRM Deal", name, ["creation", "owner", "lead"])[0]
|
||||||
|
lead = doc[2]
|
||||||
|
|
||||||
|
activities = []
|
||||||
|
creation_text = "created this deal"
|
||||||
|
|
||||||
|
if lead:
|
||||||
|
activities = get_lead_activities(lead)
|
||||||
|
creation_text = "converted the lead to this deal"
|
||||||
|
|
||||||
|
activities.append({
|
||||||
|
"activity_type": "creation",
|
||||||
|
"creation": doc[0],
|
||||||
|
"owner": doc[1],
|
||||||
|
"data": creation_text,
|
||||||
|
"is_lead": False,
|
||||||
|
})
|
||||||
|
|
||||||
|
docinfo.versions.reverse()
|
||||||
|
|
||||||
|
for version in docinfo.versions:
|
||||||
|
data = json.loads(version.data)
|
||||||
|
if not data.get("changed"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
field_option = None
|
||||||
|
|
||||||
|
if change := data.get("changed")[0]:
|
||||||
|
field_label, field_option = next(((f.label, f.options) for f in deal_fields_meta if f.fieldname == change[0]), None)
|
||||||
|
|
||||||
|
if field_label == "Lead" or (not change[1] and not change[2]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
activity_type = "changed"
|
||||||
|
data = {
|
||||||
|
"field": change[0],
|
||||||
|
"field_label": field_label,
|
||||||
|
"old_value": change[1],
|
||||||
|
"value": change[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
if not change[1] and change[2]:
|
||||||
|
activity_type = "added"
|
||||||
|
data = {
|
||||||
|
"field": change[0],
|
||||||
|
"field_label": field_label,
|
||||||
|
"value": change[2],
|
||||||
|
}
|
||||||
|
elif change[1] and not change[2]:
|
||||||
|
activity_type = "removed"
|
||||||
|
data = {
|
||||||
|
"field": change[0],
|
||||||
|
"field_label": field_label,
|
||||||
|
"value": change[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
activity = {
|
||||||
|
"activity_type": activity_type,
|
||||||
|
"creation": version.creation,
|
||||||
|
"owner": version.owner,
|
||||||
|
"data": data,
|
||||||
|
"is_lead": False,
|
||||||
|
"options": field_option,
|
||||||
|
}
|
||||||
|
activities.append(activity)
|
||||||
|
|
||||||
|
for communication in docinfo.communications:
|
||||||
|
activity = {
|
||||||
|
"activity_type": "communication",
|
||||||
|
"creation": communication.creation,
|
||||||
|
"data": {
|
||||||
|
"subject": communication.subject,
|
||||||
|
"content": communication.content,
|
||||||
|
"sender_full_name": communication.sender_full_name,
|
||||||
|
"sender": communication.sender,
|
||||||
|
"recipients": communication.recipients,
|
||||||
|
"cc": communication.cc,
|
||||||
|
"bcc": communication.bcc,
|
||||||
|
"read_by_recipient": communication.read_by_recipient,
|
||||||
|
},
|
||||||
|
"is_lead": False,
|
||||||
|
}
|
||||||
|
activities.append(activity)
|
||||||
|
|
||||||
|
activities.sort(key=lambda x: x["creation"], reverse=True)
|
||||||
|
activities = handle_multiple_versions(activities)
|
||||||
|
|
||||||
|
return activities
|
||||||
|
|
||||||
|
def get_lead_activities(name):
|
||||||
|
get_docinfo('', "CRM Lead", name)
|
||||||
|
docinfo = frappe.response["docinfo"]
|
||||||
|
lead_fields_meta = frappe.get_meta("CRM Lead").fields
|
||||||
|
|
||||||
|
doc = frappe.db.get_values("CRM Lead", name, ["creation", "owner"])[0]
|
||||||
|
activities = [{
|
||||||
|
"activity_type": "creation",
|
||||||
|
"creation": doc[0],
|
||||||
|
"owner": doc[1],
|
||||||
|
"data": "created this lead",
|
||||||
|
"is_lead": True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
docinfo.versions.reverse()
|
||||||
|
|
||||||
|
for version in docinfo.versions:
|
||||||
|
data = json.loads(version.data)
|
||||||
|
if not data.get("changed"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
field_option = None
|
||||||
|
|
||||||
|
if change := data.get("changed")[0]:
|
||||||
|
field_label, field_option = next(((f.label, f.options) for f in lead_fields_meta if f.fieldname == change[0]), None)
|
||||||
|
|
||||||
|
if field_label == "Converted" or (not change[1] and not change[2]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
activity_type = "changed"
|
||||||
|
data = {
|
||||||
|
"field": change[0],
|
||||||
|
"field_label": field_label,
|
||||||
|
"old_value": change[1],
|
||||||
|
"value": change[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
if not change[1] and change[2]:
|
||||||
|
activity_type = "added"
|
||||||
|
data = {
|
||||||
|
"field": change[0],
|
||||||
|
"field_label": field_label,
|
||||||
|
"value": change[2],
|
||||||
|
}
|
||||||
|
elif change[1] and not change[2]:
|
||||||
|
activity_type = "removed"
|
||||||
|
data = {
|
||||||
|
"field": change[0],
|
||||||
|
"field_label": field_label,
|
||||||
|
"value": change[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
activity = {
|
||||||
|
"activity_type": activity_type,
|
||||||
|
"creation": version.creation,
|
||||||
|
"owner": version.owner,
|
||||||
|
"data": data,
|
||||||
|
"is_lead": True,
|
||||||
|
"options": field_option,
|
||||||
|
}
|
||||||
|
activities.append(activity)
|
||||||
|
|
||||||
|
for communication in docinfo.communications:
|
||||||
|
activity = {
|
||||||
|
"activity_type": "communication",
|
||||||
|
"creation": communication.creation,
|
||||||
|
"data": {
|
||||||
|
"subject": communication.subject,
|
||||||
|
"content": communication.content,
|
||||||
|
"sender_full_name": communication.sender_full_name,
|
||||||
|
"sender": communication.sender,
|
||||||
|
"recipients": communication.recipients,
|
||||||
|
"cc": communication.cc,
|
||||||
|
"bcc": communication.bcc,
|
||||||
|
"read_by_recipient": communication.read_by_recipient,
|
||||||
|
},
|
||||||
|
"is_lead": True,
|
||||||
|
}
|
||||||
|
activities.append(activity)
|
||||||
|
|
||||||
|
activities.sort(key=lambda x: x["creation"], reverse=True)
|
||||||
|
activities = handle_multiple_versions(activities)
|
||||||
|
|
||||||
|
return activities
|
||||||
|
|
||||||
|
def handle_multiple_versions(versions):
|
||||||
|
activities = []
|
||||||
|
grouped_versions = []
|
||||||
|
old_version = None
|
||||||
|
for version in versions:
|
||||||
|
is_version = version["activity_type"] in ["changed", "added", "removed"]
|
||||||
|
if not is_version:
|
||||||
|
activities.append(version)
|
||||||
|
if not old_version:
|
||||||
|
old_version = version
|
||||||
|
if is_version: grouped_versions.append(version)
|
||||||
|
continue
|
||||||
|
if is_version and old_version.get("owner") and version["owner"] == old_version["owner"]:
|
||||||
|
grouped_versions.append(version)
|
||||||
|
else:
|
||||||
|
if grouped_versions:
|
||||||
|
activities.append(parse_grouped_versions(grouped_versions))
|
||||||
|
grouped_versions = []
|
||||||
|
if is_version: grouped_versions.append(version)
|
||||||
|
old_version = version
|
||||||
|
if version == versions[-1] and grouped_versions:
|
||||||
|
activities.append(parse_grouped_versions(grouped_versions))
|
||||||
|
|
||||||
|
return activities
|
||||||
|
|
||||||
|
def parse_grouped_versions(versions):
|
||||||
|
version = versions[0]
|
||||||
|
if len(versions) == 1:
|
||||||
|
return version
|
||||||
|
other_versions = versions[1:]
|
||||||
|
version["other_versions"] = other_versions
|
||||||
|
return version
|
||||||
@ -14,7 +14,8 @@
|
|||||||
"duration",
|
"duration",
|
||||||
"medium",
|
"medium",
|
||||||
"start_time",
|
"start_time",
|
||||||
"lead",
|
"reference_doctype",
|
||||||
|
"reference_docname",
|
||||||
"column_break_ufnp",
|
"column_break_ufnp",
|
||||||
"to",
|
"to",
|
||||||
"type",
|
"type",
|
||||||
@ -89,12 +90,6 @@
|
|||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"label": "End Time"
|
"label": "End Time"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "lead",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Lead/Deal",
|
|
||||||
"options": "CRM Lead"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "note",
|
"fieldname": "note",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -114,11 +109,24 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Caller",
|
"label": "Caller",
|
||||||
"options": "User"
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "CRM Lead",
|
||||||
|
"fieldname": "reference_doctype",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Reference Document Type",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_docname",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Reference Name",
|
||||||
|
"options": "reference_doctype"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-30 15:39:46.613734",
|
"modified": "2023-11-07 13:52:40.504747",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM Call Log",
|
"name": "CRM Call Log",
|
||||||
|
|||||||
0
crm/fcrm/doctype/crm_deal/__init__.py
Normal file
0
crm/fcrm/doctype/crm_deal/__init__.py
Normal file
25
crm/fcrm/doctype/crm_deal/api.py
Normal file
25
crm/fcrm/doctype/crm_deal/api.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.desk.form.load import get_docinfo
|
||||||
|
from crm.fcrm.doctype.crm_lead.api import get_activities as get_lead_activities
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_deal(name):
|
||||||
|
Deal = frappe.qb.DocType("CRM Deal")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(Deal)
|
||||||
|
.select("*")
|
||||||
|
.where(Deal.name == name)
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
deal = query.run(as_dict=True)
|
||||||
|
if not len(deal):
|
||||||
|
frappe.throw(_("Deal not found"), frappe.DoesNotExistError)
|
||||||
|
deal = deal.pop()
|
||||||
|
|
||||||
|
return deal
|
||||||
8
crm/fcrm/doctype/crm_deal/crm_deal.js
Normal file
8
crm/fcrm/doctype/crm_deal/crm_deal.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("CRM Deal", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
145
crm/fcrm/doctype/crm_deal/crm_deal.json
Normal file
145
crm/fcrm/doctype/crm_deal/crm_deal.json
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "naming_series:",
|
||||||
|
"creation": "2023-11-06 17:56:25.210449",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"naming_series",
|
||||||
|
"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"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "organization",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Organization",
|
||||||
|
"options": "CRM Organization"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "probability",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"label": "Probability"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "organization.annual_revenue",
|
||||||
|
"fieldname": "annual_revenue",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Annual Revenue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_afce",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "organization.website",
|
||||||
|
"fieldname": "website",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Website",
|
||||||
|
"options": "URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "close_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Close Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "next_step",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Next Step"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "lead",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Lead",
|
||||||
|
"options": "CRM Lead"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_eepu",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_bqvs",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "deal_owner",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Deal Owner",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Naming Series",
|
||||||
|
"options": "CRM-DEAL-.YYYY.-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "contacts_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Contacts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Email",
|
||||||
|
"options": "Email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "mobile_no",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Mobile No",
|
||||||
|
"options": "Phone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Qualification",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Qualification\nDemo/Making\nProposal/Quotation\nNegotiation\nReady to Close\nWon\nLost",
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-11-06 21:53:50.442404",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "FCRM",
|
||||||
|
"name": "CRM Deal",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
|
"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": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
19
crm/fcrm/doctype/crm_deal/crm_deal.py
Normal file
19
crm/fcrm/doctype/crm_deal/crm_deal.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class CRMDeal(Document):
|
||||||
|
@staticmethod
|
||||||
|
def sort_options():
|
||||||
|
return [
|
||||||
|
{ "label": 'Created', "value": 'creation' },
|
||||||
|
{ "label": 'Modified', "value": 'modified' },
|
||||||
|
{ "label": 'Status', "value": 'status' },
|
||||||
|
{ "label": 'Deal owner', "value": 'deal_owner' },
|
||||||
|
{ "label": 'Organization', "value": 'organization' },
|
||||||
|
{ "label": 'Email', "value": 'email' },
|
||||||
|
{ "label": 'Mobile no', "value": 'mobile_no' },
|
||||||
|
]
|
||||||
9
crm/fcrm/doctype/crm_deal/test_crm_deal.py
Normal file
9
crm/fcrm/doctype/crm_deal/test_crm_deal.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestCRMDeal(FrappeTestCase):
|
||||||
|
pass
|
||||||
@ -22,126 +22,3 @@ def get_lead(name):
|
|||||||
lead = lead.pop()
|
lead = lead.pop()
|
||||||
|
|
||||||
return lead
|
return lead
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_activities(name):
|
|
||||||
get_docinfo('', "CRM Lead", name)
|
|
||||||
docinfo = frappe.response["docinfo"]
|
|
||||||
lead_fields_meta = frappe.get_meta("CRM Lead").fields
|
|
||||||
|
|
||||||
doc = frappe.db.get_values("CRM Lead", name, ["creation", "owner", "created_as_deal"])[0]
|
|
||||||
created_as_deal = doc[2]
|
|
||||||
is_lead = False if created_as_deal else True
|
|
||||||
activities = [{
|
|
||||||
"activity_type": "creation",
|
|
||||||
"creation": doc[0],
|
|
||||||
"owner": doc[1],
|
|
||||||
"data": "created this " + ("deal" if created_as_deal else "lead"),
|
|
||||||
"is_lead": is_lead,
|
|
||||||
}]
|
|
||||||
|
|
||||||
docinfo.versions.reverse()
|
|
||||||
|
|
||||||
for version in docinfo.versions:
|
|
||||||
data = json.loads(version.data)
|
|
||||||
if not data.get("changed"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
field_option = None
|
|
||||||
|
|
||||||
if change := data.get("changed")[0]:
|
|
||||||
field_label, field_option = next(((f.label, f.options) for f in lead_fields_meta if f.fieldname == change[0]), None)
|
|
||||||
activity_type = "changed"
|
|
||||||
if field_label == "Lead Owner" and (created_as_deal or not is_lead):
|
|
||||||
field_label = "Deal Owner"
|
|
||||||
data = {
|
|
||||||
"field": change[0],
|
|
||||||
"field_label": field_label,
|
|
||||||
"old_value": change[1],
|
|
||||||
"value": change[2],
|
|
||||||
}
|
|
||||||
if not change[1] and not change[2]:
|
|
||||||
continue
|
|
||||||
if not change[1] and change[2]:
|
|
||||||
activity_type = "added"
|
|
||||||
data = {
|
|
||||||
"field": change[0],
|
|
||||||
"field_label": field_label,
|
|
||||||
"value": change[2],
|
|
||||||
}
|
|
||||||
if field_label == "Is Deal" and change[2] and is_lead:
|
|
||||||
activity_type = "deal"
|
|
||||||
is_lead = False
|
|
||||||
elif change[1] and not change[2]:
|
|
||||||
activity_type = "removed"
|
|
||||||
data = {
|
|
||||||
"field": change[0],
|
|
||||||
"field_label": field_label,
|
|
||||||
"value": change[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
activity = {
|
|
||||||
"activity_type": activity_type,
|
|
||||||
"creation": version.creation,
|
|
||||||
"owner": version.owner,
|
|
||||||
"data": data,
|
|
||||||
"is_lead": is_lead,
|
|
||||||
"options": field_option,
|
|
||||||
}
|
|
||||||
activities.append(activity)
|
|
||||||
|
|
||||||
for communication in docinfo.communications:
|
|
||||||
activity = {
|
|
||||||
"activity_type": "communication",
|
|
||||||
"creation": communication.creation,
|
|
||||||
"data": {
|
|
||||||
"subject": communication.subject,
|
|
||||||
"content": communication.content,
|
|
||||||
"sender_full_name": communication.sender_full_name,
|
|
||||||
"sender": communication.sender,
|
|
||||||
"recipients": communication.recipients,
|
|
||||||
"cc": communication.cc,
|
|
||||||
"bcc": communication.bcc,
|
|
||||||
"read_by_recipient": communication.read_by_recipient,
|
|
||||||
},
|
|
||||||
"is_lead": is_lead,
|
|
||||||
}
|
|
||||||
activities.append(activity)
|
|
||||||
|
|
||||||
activities.sort(key=lambda x: x["creation"], reverse=True)
|
|
||||||
activities = handle_multiple_versions(activities)
|
|
||||||
|
|
||||||
return activities
|
|
||||||
|
|
||||||
def handle_multiple_versions(versions):
|
|
||||||
activities = []
|
|
||||||
grouped_versions = []
|
|
||||||
old_version = None
|
|
||||||
for version in versions:
|
|
||||||
is_version = version["activity_type"] in ["changed", "added", "removed"]
|
|
||||||
if not is_version:
|
|
||||||
activities.append(version)
|
|
||||||
if not old_version:
|
|
||||||
old_version = version
|
|
||||||
if is_version: grouped_versions.append(version)
|
|
||||||
continue
|
|
||||||
if is_version and old_version.get("owner") and version["owner"] == old_version["owner"]:
|
|
||||||
grouped_versions.append(version)
|
|
||||||
else:
|
|
||||||
if grouped_versions:
|
|
||||||
activities.append(parse_grouped_versions(grouped_versions))
|
|
||||||
grouped_versions = []
|
|
||||||
if is_version: grouped_versions.append(version)
|
|
||||||
old_version = version
|
|
||||||
if version == versions[-1] and grouped_versions:
|
|
||||||
activities.append(parse_grouped_versions(grouped_versions))
|
|
||||||
|
|
||||||
return activities
|
|
||||||
|
|
||||||
def parse_grouped_versions(versions):
|
|
||||||
version = versions[0]
|
|
||||||
if len(versions) == 1:
|
|
||||||
return version
|
|
||||||
other_versions = versions[1:]
|
|
||||||
version["other_versions"] = other_versions
|
|
||||||
return version
|
|
||||||
@ -13,20 +13,15 @@
|
|||||||
"first_name",
|
"first_name",
|
||||||
"middle_name",
|
"middle_name",
|
||||||
"last_name",
|
"last_name",
|
||||||
"is_deal",
|
|
||||||
"created_as_deal",
|
|
||||||
"column_break_izjs",
|
"column_break_izjs",
|
||||||
"lead_name",
|
"lead_name",
|
||||||
"gender",
|
"gender",
|
||||||
"lead_owner",
|
|
||||||
"status",
|
|
||||||
"image",
|
"image",
|
||||||
"column_break_lcuv",
|
"column_break_lcuv",
|
||||||
|
"lead_owner",
|
||||||
|
"status",
|
||||||
"source",
|
"source",
|
||||||
"deal_status",
|
"converted",
|
||||||
"close_date",
|
|
||||||
"probability",
|
|
||||||
"next_step",
|
|
||||||
"organization_tab",
|
"organization_tab",
|
||||||
"section_break_uixv",
|
"section_break_uixv",
|
||||||
"organization",
|
"organization",
|
||||||
@ -93,7 +88,7 @@
|
|||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Lead Status",
|
"label": "Status",
|
||||||
"options": "Open\nContacted\nNurture\nQualified\nUnqualified\nJunk",
|
"options": "Open\nContacted\nNurture\nQualified\nUnqualified\nJunk",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
@ -123,8 +118,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "mobile_no",
|
"fieldname": "mobile_no",
|
||||||
"fieldtype": "Phone",
|
"fieldtype": "Data",
|
||||||
"label": "Mobile No"
|
"label": "Mobile No",
|
||||||
|
"options": "Phone"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_sjtw",
|
"fieldname": "column_break_sjtw",
|
||||||
@ -132,8 +128,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "phone",
|
"fieldname": "phone",
|
||||||
"fieldtype": "Phone",
|
"fieldtype": "Data",
|
||||||
"label": "Phone"
|
"label": "Phone",
|
||||||
|
"options": "Phone"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_uixv",
|
"fieldname": "section_break_uixv",
|
||||||
@ -189,25 +186,6 @@
|
|||||||
"label": "Full Name",
|
"label": "Full Name",
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "Qualification",
|
|
||||||
"fieldname": "deal_status",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Deal Status",
|
|
||||||
"options": "Qualification\nDemo/Making\nProposal/Quotation\nNegotiation\nReady to Close\nWon\nLost",
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "is_deal",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Is Deal",
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fetch_from": "organization.job_title",
|
"fetch_from": "organization.job_title",
|
||||||
"fieldname": "job_title",
|
"fieldname": "job_title",
|
||||||
@ -225,21 +203,6 @@
|
|||||||
"fieldtype": "Tab Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Contact"
|
"label": "Contact"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "close_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"label": "Close Date"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "probability",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Probability"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "next_step",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Next Step"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "contacts",
|
"fieldname": "contacts",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
@ -250,24 +213,25 @@
|
|||||||
"fieldname": "section_break_jyxr",
|
"fieldname": "section_break_jyxr",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "created_as_deal",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Created as Deal"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "organization",
|
"fieldname": "organization",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Organization",
|
"label": "Organization",
|
||||||
"options": "CRM Organization"
|
"options": "CRM Organization"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "converted",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Converted"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-06 15:29:56.868755",
|
"modified": "2023-11-06 21:53:32.542503",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM Lead",
|
"name": "CRM Lead",
|
||||||
|
|||||||
@ -9,7 +9,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"title",
|
"title",
|
||||||
"content",
|
"content",
|
||||||
"lead"
|
"reference_doctype",
|
||||||
|
"reference_docname"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -27,10 +28,17 @@
|
|||||||
"label": "Content"
|
"label": "Content"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "lead",
|
"default": "CRM Lead",
|
||||||
|
"fieldname": "reference_doctype",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Lead",
|
"label": "Reference Document Type",
|
||||||
"options": "CRM Lead"
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_docname",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Reference Doc",
|
||||||
|
"options": "reference_doctype"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
@ -40,7 +48,7 @@
|
|||||||
"link_fieldname": "note"
|
"link_fieldname": "note"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-08-28 11:48:42.100802",
|
"modified": "2023-11-07 13:41:11.249515",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM Note",
|
"name": "CRM Note",
|
||||||
|
|||||||
@ -10,7 +10,8 @@
|
|||||||
"title",
|
"title",
|
||||||
"priority",
|
"priority",
|
||||||
"start_date",
|
"start_date",
|
||||||
"lead",
|
"reference_doctype",
|
||||||
|
"reference_docname",
|
||||||
"column_break_cqua",
|
"column_break_cqua",
|
||||||
"assigned_to",
|
"assigned_to",
|
||||||
"status",
|
"status",
|
||||||
@ -70,15 +71,22 @@
|
|||||||
"label": "Description"
|
"label": "Description"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "lead",
|
"default": "CRM Lead",
|
||||||
|
"fieldname": "reference_doctype",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Lead",
|
"label": "Reference Document Type",
|
||||||
"options": "CRM Lead"
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_docname",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Reference Doc",
|
||||||
|
"options": "reference_doctype"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-28 15:05:27.986420",
|
"modified": "2023-11-07 13:41:18.277998",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM Task",
|
"name": "CRM Task",
|
||||||
|
|||||||
@ -86,9 +86,10 @@ def update_call_log(call_sid, status=None):
|
|||||||
call_log.duration = call_details.duration
|
call_log.duration = call_details.duration
|
||||||
call_log.start_time = get_datetime_from_timestamp(call_details.start_time)
|
call_log.start_time = get_datetime_from_timestamp(call_details.start_time)
|
||||||
call_log.end_time = get_datetime_from_timestamp(call_details.end_time)
|
call_log.end_time = get_datetime_from_timestamp(call_details.end_time)
|
||||||
call_log.lead = get_lead_from_number(call_log)
|
call_log.reference_docname, call_log.reference_doctype = get_lead_or_deal_from_number(call_log)
|
||||||
if call_log.note and call_log.lead:
|
if call_log.note and call_log.reference_docname:
|
||||||
frappe.db.set_value("CRM Note", call_log.note, "lead", call_log.lead)
|
frappe.db.set_value("CRM Note", call_log.note, "reference_doctype", call_log.reference_doctype)
|
||||||
|
frappe.db.set_value("CRM Note", call_log.note, "reference_docname", call_log.reference_docname)
|
||||||
call_log.flags.ignore_permissions = True
|
call_log.flags.ignore_permissions = True
|
||||||
call_log.save()
|
call_log.save()
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
@ -145,13 +146,20 @@ def add_note_to_call_log(call_sid, note):
|
|||||||
frappe.db.set_value("CRM Call Log", call_details.parent_call_sid, "note", note)
|
frappe.db.set_value("CRM Call Log", call_details.parent_call_sid, "note", note)
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
def get_lead_from_number(call):
|
def get_lead_or_deal_from_number(call):
|
||||||
"""Get lead from the given number.
|
"""Get lead/deal from the given number.
|
||||||
"""
|
"""
|
||||||
lead = None
|
doctype = "CRM Lead"
|
||||||
|
doc = None
|
||||||
if call.type == 'Outgoing':
|
if call.type == 'Outgoing':
|
||||||
lead = frappe.db.get_value("CRM Lead", { "mobile_no": call.get('to') })
|
doc = frappe.get_cached_value(doctype, { "mobile_no": call.get('to') })
|
||||||
|
if not doc:
|
||||||
|
doctype = "CRM Deal"
|
||||||
|
doc = frappe.get_cached_value(doctype, { "mobile_no": call.get('to') })
|
||||||
else:
|
else:
|
||||||
lead = frappe.db.get_value("CRM Lead", { "mobile_no": call.get('from') })
|
doc = frappe.get_cached_value(doctype, { "mobile_no": call.get('from') })
|
||||||
|
if not doc:
|
||||||
|
doctype = "CRM Deal"
|
||||||
|
doc = frappe.get_cached_value(doctype, { "mobile_no": call.get('from') })
|
||||||
|
|
||||||
return lead
|
return doc, doctype
|
||||||
@ -6,7 +6,7 @@
|
|||||||
<Button
|
<Button
|
||||||
v-if="title == 'Calls'"
|
v-if="title == 'Calls'"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@click="makeCall(lead.data.mobile_no)"
|
@click="makeCall(doc.data.mobile_no)"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<PhoneIcon class="h-4 w-4" />
|
<PhoneIcon class="h-4 w-4" />
|
||||||
@ -561,7 +561,7 @@
|
|||||||
v-if="title == 'Calls'"
|
v-if="title == 'Calls'"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
label="Make a call"
|
label="Make a call"
|
||||||
@click="makeCall(lead.data.mobile_no)"
|
@click="makeCall(doc.data.mobile_no)"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
v-else-if="title == 'Notes'"
|
v-else-if="title == 'Notes'"
|
||||||
@ -585,20 +585,22 @@
|
|||||||
<CommunicationArea
|
<CommunicationArea
|
||||||
ref="emailBox"
|
ref="emailBox"
|
||||||
v-if="['Emails', 'Activity'].includes(title)"
|
v-if="['Emails', 'Activity'].includes(title)"
|
||||||
v-model="lead"
|
v-model="doc"
|
||||||
v-model:reload="reload_email"
|
v-model:reload="reload_email"
|
||||||
/>
|
/>
|
||||||
<NoteModal
|
<NoteModal
|
||||||
v-model="showNoteModal"
|
v-model="showNoteModal"
|
||||||
v-model:reloadNotes="notes"
|
v-model:reloadNotes="notes"
|
||||||
:note="note"
|
:note="note"
|
||||||
:lead="lead.data?.name"
|
:doctype="doctype"
|
||||||
|
:doc="doc.data?.name"
|
||||||
/>
|
/>
|
||||||
<TaskModal
|
<TaskModal
|
||||||
v-model="showTaskModal"
|
v-model="showTaskModal"
|
||||||
v-model:reloadTasks="tasks"
|
v-model:reloadTasks="tasks"
|
||||||
:task="task"
|
:task="task"
|
||||||
:lead="lead.data?.name"
|
:doctype="doctype"
|
||||||
|
:doc="doc.data?.name"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -652,24 +654,28 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'Activity',
|
default: 'Activity',
|
||||||
},
|
},
|
||||||
|
doctype: {
|
||||||
|
type: String,
|
||||||
|
default: 'CRM Lead',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const lead = defineModel()
|
const doc = defineModel()
|
||||||
const reload = defineModel('reload')
|
const reload = defineModel('reload')
|
||||||
|
|
||||||
const reload_email = ref(false)
|
const reload_email = ref(false)
|
||||||
|
|
||||||
const versions = createResource({
|
const versions = createResource({
|
||||||
url: 'crm.fcrm.doctype.crm_lead.api.get_activities',
|
url: 'crm.api.activities.get_activities',
|
||||||
params: { name: lead.value.data.name },
|
params: { name: doc.value.data.name },
|
||||||
cache: ['activity', lead.value.data.name],
|
cache: ['activity', doc.value.data.name],
|
||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const calls = createListResource({
|
const calls = createListResource({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
doctype: 'CRM Call Log',
|
doctype: 'CRM Call Log',
|
||||||
cache: ['Call Logs', lead.value.data.name],
|
cache: ['Call Logs', doc.value.data.name],
|
||||||
fields: [
|
fields: [
|
||||||
'name',
|
'name',
|
||||||
'caller',
|
'caller',
|
||||||
@ -685,7 +691,7 @@ const calls = createListResource({
|
|||||||
'creation',
|
'creation',
|
||||||
'note',
|
'note',
|
||||||
],
|
],
|
||||||
filters: { lead: lead.value.data.name },
|
filters: { reference_docname: doc.value.data.name },
|
||||||
orderBy: 'creation desc',
|
orderBy: 'creation desc',
|
||||||
pageLength: 999,
|
pageLength: 999,
|
||||||
auto: true,
|
auto: true,
|
||||||
@ -722,9 +728,9 @@ const calls = createListResource({
|
|||||||
const notes = createListResource({
|
const notes = createListResource({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
doctype: 'CRM Note',
|
doctype: 'CRM Note',
|
||||||
cache: ['Notes', lead.value.data.name],
|
cache: ['Notes', doc.value.data.name],
|
||||||
fields: ['name', 'title', 'content', 'owner', 'modified'],
|
fields: ['name', 'title', 'content', 'owner', 'modified'],
|
||||||
filters: { lead: lead.value.data.name },
|
filters: { reference_docname: doc.value.data.name },
|
||||||
orderBy: 'modified desc',
|
orderBy: 'modified desc',
|
||||||
pageLength: 999,
|
pageLength: 999,
|
||||||
auto: true,
|
auto: true,
|
||||||
@ -733,7 +739,7 @@ const notes = createListResource({
|
|||||||
const tasks = createListResource({
|
const tasks = createListResource({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
doctype: 'CRM Task',
|
doctype: 'CRM Task',
|
||||||
cache: ['Tasks', lead.value.data.name],
|
cache: ['Tasks', doc.value.data.name],
|
||||||
fields: [
|
fields: [
|
||||||
'name',
|
'name',
|
||||||
'title',
|
'title',
|
||||||
@ -745,7 +751,7 @@ const tasks = createListResource({
|
|||||||
'status',
|
'status',
|
||||||
'modified',
|
'modified',
|
||||||
],
|
],
|
||||||
filters: { lead: lead.value.data.name },
|
filters: { reference_docname: doc.value.data.name },
|
||||||
orderBy: 'modified desc',
|
orderBy: 'modified desc',
|
||||||
pageLength: 999,
|
pageLength: 999,
|
||||||
auto: true,
|
auto: true,
|
||||||
@ -805,10 +811,6 @@ function update_activities_details(activity) {
|
|||||||
|
|
||||||
if (activity.activity_type == 'creation') {
|
if (activity.activity_type == 'creation') {
|
||||||
activity.type = activity.data
|
activity.type = activity.data
|
||||||
} else if (activity.activity_type == 'deal') {
|
|
||||||
activity.type = 'converted the lead to this deal'
|
|
||||||
activity.data.field_label = ''
|
|
||||||
activity.data.value = ''
|
|
||||||
} else if (activity.activity_type == 'added') {
|
} else if (activity.activity_type == 'added') {
|
||||||
activity.type = 'added'
|
activity.type = 'added'
|
||||||
activity.value = 'as'
|
activity.value = 'as'
|
||||||
@ -823,7 +825,7 @@ function update_activities_details(activity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const emptyText = computed(() => {
|
const emptyText = computed(() => {
|
||||||
let text = 'No emails communications'
|
let text = 'No email communications'
|
||||||
if (props.title == 'Calls') {
|
if (props.title == 'Calls') {
|
||||||
text = 'No call logs'
|
text = 'No call logs'
|
||||||
} else if (props.title == 'Notes') {
|
} else if (props.title == 'Notes') {
|
||||||
|
|||||||
@ -42,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
:editable="showCommunicationBox"
|
:editable="showCommunicationBox"
|
||||||
v-model="lead.data"
|
v-model="doc.data"
|
||||||
placeholder="Add a reply..."
|
placeholder="Add a reply..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -56,7 +56,7 @@ import { usersStore } from '@/stores/users'
|
|||||||
import { call } from 'frappe-ui'
|
import { call } from 'frappe-ui'
|
||||||
import { ref, watch, computed, defineModel } from 'vue'
|
import { ref, watch, computed, defineModel } from 'vue'
|
||||||
|
|
||||||
const lead = defineModel()
|
const doc = defineModel()
|
||||||
const reload = defineModel('reload')
|
const reload = defineModel('reload')
|
||||||
|
|
||||||
const { getUser } = usersStore()
|
const { getUser } = usersStore()
|
||||||
@ -84,14 +84,19 @@ const onNewEmailChange = (value) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendMail() {
|
async function sendMail() {
|
||||||
|
let doctype = 'CRM Lead'
|
||||||
|
if (doc.value.data.lead) {
|
||||||
|
doctype = 'CRM Deal'
|
||||||
|
}
|
||||||
|
|
||||||
await call('frappe.core.doctype.communication.email.make', {
|
await call('frappe.core.doctype.communication.email.make', {
|
||||||
recipients: lead.value.data.email,
|
recipients: doc.value.data.email,
|
||||||
cc: '',
|
cc: '',
|
||||||
bcc: '',
|
bcc: '',
|
||||||
subject: 'Email from Agent',
|
subject: 'Email from Agent',
|
||||||
content: newEmail.value,
|
content: newEmail.value,
|
||||||
doctype: 'CRM Lead',
|
doctype: doctype,
|
||||||
name: lead.value.data.name,
|
name: doc.value.data.name,
|
||||||
send_email: 1,
|
send_email: 1,
|
||||||
sender: getUser().name,
|
sender: getUser().name,
|
||||||
sender_full_name: getUser()?.full_name || undefined,
|
sender_full_name: getUser()?.full_name || undefined,
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
>
|
>
|
||||||
<ListRowItem :item="item">
|
<ListRowItem :item="item">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<div v-if="column.key === 'deal_status'">
|
<div v-if="column.key === 'status'">
|
||||||
<IndicatorIcon :class="item.color" />
|
<IndicatorIcon :class="item.color" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="column.key === 'organization'">
|
<div v-else-if="column.key === 'organization'">
|
||||||
@ -30,7 +30,7 @@
|
|||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="column.key === 'lead_owner'">
|
<div v-else-if="column.key === 'deal_owner'">
|
||||||
<Avatar
|
<Avatar
|
||||||
v-if="item.full_name"
|
v-if="item.full_name"
|
||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
|
|||||||
@ -50,7 +50,11 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
lead: {
|
doctype: {
|
||||||
|
type: String,
|
||||||
|
default: 'CRM Lead',
|
||||||
|
},
|
||||||
|
doc: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
@ -85,7 +89,8 @@ async function updateNote(close) {
|
|||||||
doctype: 'CRM Note',
|
doctype: 'CRM Note',
|
||||||
title: _note.value.title,
|
title: _note.value.title,
|
||||||
content: _note.value.content,
|
content: _note.value.content,
|
||||||
lead: props.lead || '',
|
reference_doctype: props.doctype,
|
||||||
|
reference_docname: props.doc || '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (d.name) {
|
if (d.name) {
|
||||||
|
|||||||
@ -92,15 +92,18 @@ import {
|
|||||||
DatePicker,
|
DatePicker,
|
||||||
call,
|
call,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, defineModel, h, watch, nextTick } from 'vue'
|
import { ref, defineModel, watch, nextTick } from 'vue'
|
||||||
import { get } from '@vueuse/core'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
task: {
|
task: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
lead: {
|
doctype: {
|
||||||
|
type: String,
|
||||||
|
default: 'CRM Lead',
|
||||||
|
},
|
||||||
|
doc: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
@ -149,7 +152,8 @@ async function updateTask(close) {
|
|||||||
let d = await call('frappe.client.insert', {
|
let d = await call('frappe.client.insert', {
|
||||||
doc: {
|
doc: {
|
||||||
doctype: 'CRM Task',
|
doctype: 'CRM Task',
|
||||||
lead: props.lead || null,
|
reference_doctype: props.doctype,
|
||||||
|
reference_docname: props.doc || null,
|
||||||
..._task.value,
|
..._task.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
:options="field.options"
|
:options="field.options"
|
||||||
v-model="newDeal[field.name]"
|
v-model="newDeal[field.name]"
|
||||||
>
|
>
|
||||||
<template v-if="field.name == 'deal_status'" #prefix>
|
<template v-if="field.name == 'status'" #prefix>
|
||||||
<IndicatorIcon :class="dealStatuses[newDeal[field.name]].color" />
|
<IndicatorIcon :class="dealStatuses[newDeal[field.name]].color" />
|
||||||
</template>
|
</template>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -153,7 +153,7 @@ const allFields = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
name: 'deal_status',
|
name: 'status',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: statusDropdownOptions(props.newDeal, 'deal'),
|
options: statusDropdownOptions(props.newDeal, 'deal'),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -148,7 +148,7 @@ const allFields = [
|
|||||||
placeholder: 'Organization',
|
placeholder: 'Organization',
|
||||||
options: getOrganizationOptions(),
|
options: getOrganizationOptions(),
|
||||||
change: (option) => {
|
change: (option) => {
|
||||||
newLead.organization = option.name
|
props.newLead.organization = option.value
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -174,11 +174,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
<ContactModal
|
|
||||||
v-model="showContactModal"
|
|
||||||
v-model:reloadContacts="contacts"
|
|
||||||
:contact="contact"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -197,7 +192,6 @@ import {
|
|||||||
createListResource,
|
createListResource,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||||
import ContactModal from '@/components/ContactModal.vue'
|
|
||||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||||
import CameraIcon from '@/components/Icons/CameraIcon.vue'
|
import CameraIcon from '@/components/Icons/CameraIcon.vue'
|
||||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
||||||
@ -223,8 +217,6 @@ const { getContactByName, contacts } = contactsStore()
|
|||||||
const { getUser } = usersStore()
|
const { getUser } = usersStore()
|
||||||
const { getOrganization, getOrganizationOptions } = organizationsStore()
|
const { getOrganization, getOrganizationOptions } = organizationsStore()
|
||||||
|
|
||||||
const showContactModal = ref(false)
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
contactId: {
|
contactId: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -314,7 +306,7 @@ const leads = createListResource({
|
|||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
email: contact.value.email_id,
|
email: contact.value.email_id,
|
||||||
is_deal: 0,
|
converted: 0,
|
||||||
},
|
},
|
||||||
orderBy: 'modified desc',
|
orderBy: 'modified desc',
|
||||||
pageLength: 20,
|
pageLength: 20,
|
||||||
@ -329,7 +321,7 @@ const deals = createListResource({
|
|||||||
'name',
|
'name',
|
||||||
'organization',
|
'organization',
|
||||||
'annual_revenue',
|
'annual_revenue',
|
||||||
'deal_status',
|
'status',
|
||||||
'email',
|
'email',
|
||||||
'mobile_no',
|
'mobile_no',
|
||||||
'lead_owner',
|
'lead_owner',
|
||||||
@ -337,7 +329,7 @@ const deals = createListResource({
|
|||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
email: contact.value.email_id,
|
email: contact.value.email_id,
|
||||||
is_deal: 1,
|
converted: 1,
|
||||||
},
|
},
|
||||||
orderBy: 'modified desc',
|
orderBy: 'modified desc',
|
||||||
pageLength: 20,
|
pageLength: 20,
|
||||||
@ -396,9 +388,9 @@ function getDealRowObject(deal) {
|
|||||||
logo: getOrganization(deal.organization)?.organization_logo,
|
logo: getOrganization(deal.organization)?.organization_logo,
|
||||||
},
|
},
|
||||||
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
|
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
|
||||||
deal_status: {
|
status: {
|
||||||
label: deal.deal_status,
|
label: deal.status,
|
||||||
color: dealStatuses[deal.deal_status]?.color,
|
color: dealStatuses[deal.status]?.color,
|
||||||
},
|
},
|
||||||
email: deal.email,
|
email: deal.email,
|
||||||
mobile_no: deal.mobile_no,
|
mobile_no: deal.mobile_no,
|
||||||
@ -464,7 +456,7 @@ const dealColumns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
key: 'deal_status',
|
key: 'status',
|
||||||
width: '10rem',
|
width: '10rem',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||||
import ContactModal from '@/components/ContactModal.vue'
|
import ContactModal from '@/components/Modals/ContactModal.vue'
|
||||||
import ContactsListView from '@/components/ListViews/ContactsListView.vue'
|
import ContactsListView from '@/components/ListViews/ContactsListView.vue'
|
||||||
import SortBy from '@/components/SortBy.vue'
|
import SortBy from '@/components/SortBy.vue'
|
||||||
import Filter from '@/components/Filter.vue'
|
import Filter from '@/components/Filter.vue'
|
||||||
|
|||||||
@ -7,12 +7,12 @@
|
|||||||
<FormControl
|
<FormControl
|
||||||
type="autocomplete"
|
type="autocomplete"
|
||||||
:options="activeAgents"
|
:options="activeAgents"
|
||||||
:value="getUser(deal.data.lead_owner).full_name"
|
:value="getUser(deal.data.deal_owner).full_name"
|
||||||
@change="(option) => updateAssignedAgent(option.email)"
|
@change="(option) => updateAssignedAgent(option.email)"
|
||||||
placeholder="Deal owner"
|
placeholder="Deal owner"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<UserAvatar class="mr-2" :user="deal.data.lead_owner" size="sm" />
|
<UserAvatar class="mr-2" :user="deal.data.deal_owner" size="sm" />
|
||||||
</template>
|
</template>
|
||||||
<template #item-prefix="{ option }">
|
<template #item-prefix="{ option }">
|
||||||
<UserAvatar class="mr-2" :user="option.email" size="sm" />
|
<UserAvatar class="mr-2" :user="option.email" size="sm" />
|
||||||
@ -20,17 +20,16 @@
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<Dropdown :options="statusDropdownOptions(deal.data, 'deal', updateDeal)">
|
<Dropdown :options="statusDropdownOptions(deal.data, 'deal', updateDeal)">
|
||||||
<template #default="{ open }">
|
<template #default="{ open }">
|
||||||
<Button :label="deal.data.deal_status">
|
<Button :label="deal.data.status">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<IndicatorIcon
|
<IndicatorIcon :class="dealStatuses[deal.data.status].color" />
|
||||||
:class="dealStatuses[deal.data.deal_status].color"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #suffix
|
<template #suffix>
|
||||||
><FeatherIcon
|
<FeatherIcon
|
||||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||||
class="h-4 text-gray-600"
|
class="h-4 text-gray-600"
|
||||||
/></template>
|
/>
|
||||||
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -38,7 +37,12 @@
|
|||||||
</LayoutHeader>
|
</LayoutHeader>
|
||||||
<div v-if="deal.data" class="flex h-full overflow-hidden">
|
<div v-if="deal.data" class="flex h-full overflow-hidden">
|
||||||
<Tabs v-model="tabIndex" v-slot="{ tab }" :tabs="tabs">
|
<Tabs v-model="tabIndex" v-slot="{ tab }" :tabs="tabs">
|
||||||
<Activities :title="tab.label" v-model:reload="reload" v-model="deal" />
|
<Activities
|
||||||
|
doctype="CRM Deal"
|
||||||
|
:title="tab.label"
|
||||||
|
v-model:reload="reload"
|
||||||
|
v-model="deal"
|
||||||
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div class="flex w-[352px] flex-col justify-between border-l">
|
<div class="flex w-[352px] flex-col justify-between border-l">
|
||||||
<div
|
<div
|
||||||
@ -254,7 +258,8 @@
|
|||||||
:debounce="500"
|
:debounce="500"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
/>
|
/>
|
||||||
<Tooltip :text="field.tooltip"
|
<Tooltip
|
||||||
|
:text="field.tooltip"
|
||||||
class="flex h-7 cursor-pointer items-center px-2 py-1"
|
class="flex h-7 cursor-pointer items-center px-2 py-1"
|
||||||
v-else-if="field.type === 'read_only'"
|
v-else-if="field.type === 'read_only'"
|
||||||
>
|
>
|
||||||
@ -272,7 +277,11 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ExternalLinkIcon
|
<ExternalLinkIcon
|
||||||
v-if="field.type === 'link' && field.link && deal.data[field.name]"
|
v-if="
|
||||||
|
field.type === 'link' &&
|
||||||
|
field.link &&
|
||||||
|
deal.data[field.name]
|
||||||
|
"
|
||||||
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
|
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
|
||||||
@click="field.link(deal.data[field.name])"
|
@click="field.link(deal.data[field.name])"
|
||||||
/>
|
/>
|
||||||
@ -335,7 +344,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const deal = createResource({
|
const deal = createResource({
|
||||||
url: 'crm.fcrm.doctype.crm_lead.api.get_lead',
|
url: 'crm.fcrm.doctype.crm_deal.api.get_deal',
|
||||||
params: { name: props.dealId },
|
params: { name: props.dealId },
|
||||||
cache: ['deal', props.dealId],
|
cache: ['deal', props.dealId],
|
||||||
auto: true,
|
auto: true,
|
||||||
@ -347,7 +356,7 @@ function updateDeal(fieldname, value) {
|
|||||||
createResource({
|
createResource({
|
||||||
url: 'frappe.client.set_value',
|
url: 'frappe.client.set_value',
|
||||||
params: {
|
params: {
|
||||||
doctype: 'CRM Lead',
|
doctype: 'CRM Deal',
|
||||||
name: props.dealId,
|
name: props.dealId,
|
||||||
fieldname,
|
fieldname,
|
||||||
value,
|
value,
|
||||||
@ -377,7 +386,7 @@ function updateDeal(fieldname, value) {
|
|||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
let items = [{ label: 'Deals', route: { name: 'Deals' } }]
|
let items = [{ label: 'Deals', route: { name: 'Deals' } }]
|
||||||
items.push({
|
items.push({
|
||||||
label: organization.value.name,
|
label: organization.value?.name,
|
||||||
route: { name: 'Deal', params: { dealId: deal.data.name } },
|
route: { name: 'Deal', params: { dealId: deal.data.name } },
|
||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
@ -435,12 +444,16 @@ const detailSections = computed(() => {
|
|||||||
type: 'read_only',
|
type: 'read_only',
|
||||||
name: 'website',
|
name: 'website',
|
||||||
value: organization.value?.website,
|
value: organization.value?.website,
|
||||||
tooltip: 'It is a read only field, value is fetched from organization',
|
tooltip:
|
||||||
|
'It is a read only field, value is fetched from organization',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Amount',
|
label: 'Amount',
|
||||||
type: 'number',
|
type: 'read_only',
|
||||||
name: 'annual_revenue',
|
name: 'annual_revenue',
|
||||||
|
value: organization.value?.annual_revenue,
|
||||||
|
tooltip:
|
||||||
|
'It is a read only field, value is fetched from organization',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Close date',
|
label: 'Close date',
|
||||||
@ -514,8 +527,8 @@ const organization = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function updateAssignedAgent(email) {
|
function updateAssignedAgent(email) {
|
||||||
deal.data.lead_owner = email
|
deal.data.deal_owner = email
|
||||||
updateDeal('lead_owner', email)
|
updateDeal('deal_owner', email)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -28,8 +28,8 @@
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Filter doctype="CRM Lead" />
|
<Filter doctype="CRM Deal" />
|
||||||
<SortBy doctype="CRM Lead" />
|
<SortBy doctype="CRM Deal" />
|
||||||
<Button icon="more-horizontal" />
|
<Button icon="more-horizontal" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -96,23 +96,20 @@ const currentView = ref({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function getFilter() {
|
function getFilter() {
|
||||||
return {
|
return getArgs() || {}
|
||||||
...(getArgs() || {}),
|
|
||||||
is_deal: 1,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const leads = createListResource({
|
const deals = createListResource({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
doctype: 'CRM Lead',
|
doctype: 'CRM Deal',
|
||||||
fields: [
|
fields: [
|
||||||
'name',
|
'name',
|
||||||
'organization',
|
'organization',
|
||||||
'annual_revenue',
|
'annual_revenue',
|
||||||
'deal_status',
|
'status',
|
||||||
'email',
|
'email',
|
||||||
'mobile_no',
|
'mobile_no',
|
||||||
'lead_owner',
|
'deal_owner',
|
||||||
'modified',
|
'modified',
|
||||||
],
|
],
|
||||||
filters: getFilter(),
|
filters: getFilter(),
|
||||||
@ -125,8 +122,8 @@ watch(
|
|||||||
() => getOrderBy(),
|
() => getOrderBy(),
|
||||||
(value, old_value) => {
|
(value, old_value) => {
|
||||||
if (!value && !old_value) return
|
if (!value && !old_value) return
|
||||||
leads.orderBy = getOrderBy() || 'modified desc'
|
deals.orderBy = getOrderBy() || 'modified desc'
|
||||||
leads.reload()
|
deals.reload()
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
@ -135,8 +132,8 @@ watch(
|
|||||||
storage,
|
storage,
|
||||||
useDebounceFn((value, old_value) => {
|
useDebounceFn((value, old_value) => {
|
||||||
if (JSON.stringify([...value]) === JSON.stringify([...old_value])) return
|
if (JSON.stringify([...value]) === JSON.stringify([...old_value])) return
|
||||||
leads.filters = getFilter()
|
deals.filters = getFilter()
|
||||||
leads.reload()
|
deals.reload()
|
||||||
}, 300),
|
}, 300),
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
@ -154,7 +151,7 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
key: 'deal_status',
|
key: 'status',
|
||||||
width: '10rem',
|
width: '10rem',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -168,8 +165,8 @@ const columns = [
|
|||||||
width: '11rem',
|
width: '11rem',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Lead owner',
|
label: 'Deal owner',
|
||||||
key: 'lead_owner',
|
key: 'deal_owner',
|
||||||
width: '10rem',
|
width: '10rem',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -180,28 +177,28 @@ const columns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const rows = computed(() => {
|
const rows = computed(() => {
|
||||||
if (!leads.data) return []
|
if (!deals.data) return []
|
||||||
return leads.data.map((lead) => {
|
return deals.data.map((deal) => {
|
||||||
return {
|
return {
|
||||||
name: lead.name,
|
name: deal.name,
|
||||||
organization: {
|
organization: {
|
||||||
label: lead.organization,
|
label: deal.organization,
|
||||||
logo: getOrganization(lead.organization)?.organization_logo,
|
logo: getOrganization(deal.organization)?.organization_logo,
|
||||||
},
|
},
|
||||||
annual_revenue: formatNumberIntoCurrency(lead.annual_revenue),
|
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
|
||||||
deal_status: {
|
status: {
|
||||||
label: lead.deal_status,
|
label: deal.status,
|
||||||
color: dealStatuses[lead.deal_status]?.color,
|
color: dealStatuses[deal.status]?.color,
|
||||||
},
|
},
|
||||||
email: lead.email,
|
email: deal.email,
|
||||||
mobile_no: lead.mobile_no,
|
mobile_no: deal.mobile_no,
|
||||||
lead_owner: {
|
deal_owner: {
|
||||||
label: lead.lead_owner && getUser(lead.lead_owner).full_name,
|
label: deal.deal_owner && getUser(deal.deal_owner).full_name,
|
||||||
...(lead.lead_owner && getUser(lead.lead_owner)),
|
...(deal.deal_owner && getUser(deal.deal_owner)),
|
||||||
},
|
},
|
||||||
modified: {
|
modified: {
|
||||||
label: dateFormat(lead.modified, dateTooltipFormat),
|
label: dateFormat(deal.modified, dateTooltipFormat),
|
||||||
timeAgo: timeAgo(lead.modified),
|
timeAgo: timeAgo(deal.modified),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -253,25 +250,19 @@ const viewsDropdownOptions = [
|
|||||||
const showNewDialog = ref(false)
|
const showNewDialog = ref(false)
|
||||||
|
|
||||||
let newDeal = reactive({
|
let newDeal = reactive({
|
||||||
salutation: '',
|
|
||||||
first_name: '',
|
|
||||||
last_name: '',
|
|
||||||
lead_name: '',
|
|
||||||
organization: '',
|
organization: '',
|
||||||
deal_status: 'Qualification',
|
status: 'Qualification',
|
||||||
email: '',
|
email: '',
|
||||||
mobile_no: '',
|
mobile_no: '',
|
||||||
lead_owner: getUser().email,
|
deal_owner: getUser().email,
|
||||||
is_deal: 1,
|
|
||||||
created_as_deal: 1,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const createLead = createResource({
|
const createDeal = createResource({
|
||||||
url: 'frappe.client.insert',
|
url: 'frappe.client.insert',
|
||||||
makeParams(values) {
|
makeParams(values) {
|
||||||
return {
|
return {
|
||||||
doc: {
|
doc: {
|
||||||
doctype: 'CRM Lead',
|
doctype: 'CRM Deal',
|
||||||
...values,
|
...values,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -281,7 +272,7 @@ const createLead = createResource({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
function createNewDeal(close) {
|
function createNewDeal(close) {
|
||||||
createLead
|
createDeal
|
||||||
.submit(newDeal, {
|
.submit(newDeal, {
|
||||||
validate() {
|
validate() {
|
||||||
if (!newDeal.first_name) {
|
if (!newDeal.first_name) {
|
||||||
|
|||||||
@ -41,7 +41,12 @@
|
|||||||
</LayoutHeader>
|
</LayoutHeader>
|
||||||
<div v-if="lead?.data" class="flex h-full overflow-hidden">
|
<div v-if="lead?.data" class="flex h-full overflow-hidden">
|
||||||
<Tabs v-model="tabIndex" v-slot="{ tab }" :tabs="tabs">
|
<Tabs v-model="tabIndex" v-slot="{ tab }" :tabs="tabs">
|
||||||
<Activities :title="tab.label" v-model:reload="reload" v-model="lead" />
|
<Activities
|
||||||
|
doctype="CRM Lead"
|
||||||
|
:title="tab.label"
|
||||||
|
v-model:reload="reload"
|
||||||
|
v-model="lead"
|
||||||
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div class="flex w-[352px] flex-col justify-between border-l">
|
<div class="flex w-[352px] flex-col justify-between border-l">
|
||||||
<div
|
<div
|
||||||
@ -336,6 +341,7 @@ import {
|
|||||||
Avatar,
|
Avatar,
|
||||||
Tabs,
|
Tabs,
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
call,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@ -372,9 +378,6 @@ function updateLead(fieldname, value) {
|
|||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
if (fieldname == 'is_deal') {
|
|
||||||
router.push({ name: 'Deal', params: { dealId: lead.data.name } })
|
|
||||||
}
|
|
||||||
lead.reload()
|
lead.reload()
|
||||||
contacts.reload()
|
contacts.reload()
|
||||||
reload.value = true
|
reload.value = true
|
||||||
@ -565,8 +568,23 @@ const organization = computed(() => {
|
|||||||
|
|
||||||
function convertToDeal() {
|
function convertToDeal() {
|
||||||
lead.data.status = 'Qualified'
|
lead.data.status = 'Qualified'
|
||||||
lead.data.is_deal = 1
|
lead.data.converted = 1
|
||||||
updateLead('is_deal', 1)
|
createDeal(lead.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createDeal(lead) {
|
||||||
|
let d = await call('frappe.client.insert', {
|
||||||
|
doc: {
|
||||||
|
doctype: 'CRM Deal',
|
||||||
|
organization: lead.organization,
|
||||||
|
email: lead.email,
|
||||||
|
mobile_no: lead.mobile_no,
|
||||||
|
lead: lead.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (d.name) {
|
||||||
|
router.push({ name: 'Deal', params: { dealId: d.name } })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAssignedAgent(email) {
|
function updateAssignedAgent(email) {
|
||||||
|
|||||||
@ -91,7 +91,7 @@ const currentView = ref({
|
|||||||
function getFilter() {
|
function getFilter() {
|
||||||
return {
|
return {
|
||||||
...(getArgs() || {}),
|
...(getArgs() || {}),
|
||||||
is_deal: 0,
|
converted: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -298,7 +298,7 @@ const leads = createListResource({
|
|||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
organization: props.organization.name,
|
organization: props.organization.name,
|
||||||
is_deal: 0,
|
converted: 0,
|
||||||
},
|
},
|
||||||
orderBy: 'modified desc',
|
orderBy: 'modified desc',
|
||||||
pageLength: 20,
|
pageLength: 20,
|
||||||
@ -313,7 +313,7 @@ const deals = createListResource({
|
|||||||
'name',
|
'name',
|
||||||
'organization',
|
'organization',
|
||||||
'annual_revenue',
|
'annual_revenue',
|
||||||
'deal_status',
|
'status',
|
||||||
'email',
|
'email',
|
||||||
'mobile_no',
|
'mobile_no',
|
||||||
'lead_owner',
|
'lead_owner',
|
||||||
@ -321,7 +321,7 @@ const deals = createListResource({
|
|||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
organization: props.organization.name,
|
organization: props.organization.name,
|
||||||
is_deal: 1,
|
converted: 1,
|
||||||
},
|
},
|
||||||
orderBy: 'modified desc',
|
orderBy: 'modified desc',
|
||||||
pageLength: 20,
|
pageLength: 20,
|
||||||
@ -409,9 +409,9 @@ function getDealRowObject(deal) {
|
|||||||
logo: props.organization?.organization_logo,
|
logo: props.organization?.organization_logo,
|
||||||
},
|
},
|
||||||
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
|
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
|
||||||
deal_status: {
|
status: {
|
||||||
label: deal.deal_status,
|
label: deal.status,
|
||||||
color: dealStatuses[deal.deal_status]?.color,
|
color: dealStatuses[deal.status]?.color,
|
||||||
},
|
},
|
||||||
email: deal.email,
|
email: deal.email,
|
||||||
mobile_no: deal.mobile_no,
|
mobile_no: deal.mobile_no,
|
||||||
@ -498,7 +498,7 @@ const dealColumns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
key: 'deal_status',
|
key: 'status',
|
||||||
width: '10rem',
|
width: '10rem',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -71,23 +71,15 @@ export const dealStatuses = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function statusDropdownOptions(data, doctype, action) {
|
export function statusDropdownOptions(data, doctype, action) {
|
||||||
let statuses = leadStatuses
|
let statuses = doctype == 'deal' ? dealStatuses : leadStatuses
|
||||||
if (doctype == 'deal') {
|
|
||||||
statuses = dealStatuses
|
|
||||||
}
|
|
||||||
let options = []
|
let options = []
|
||||||
for (const status in statuses) {
|
for (const status in statuses) {
|
||||||
options.push({
|
options.push({
|
||||||
label: statuses[status].label,
|
label: statuses[status].label,
|
||||||
icon: () => h(IndicatorIcon, { class: statuses[status].color }),
|
icon: () => h(IndicatorIcon, { class: statuses[status].color }),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (doctype == 'deal') {
|
data.status = statuses[status].label
|
||||||
data.deal_status = statuses[status].label
|
action && action('status', statuses[status].label)
|
||||||
} else {
|
|
||||||
data.status = statuses[status].label
|
|
||||||
}
|
|
||||||
let field = doctype == 'deal' ? 'deal_status' : 'status'
|
|
||||||
action && action(field, statuses[status].label)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user