Merge pull request #36 from shariquerik/make-lead-deal-status-link-field
This commit is contained in:
commit
d3a8336f28
@ -111,10 +111,10 @@
|
||||
{
|
||||
"default": "Qualification",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "Qualification\nDemo/Making\nProposal/Quotation\nNegotiation\nReady to Close\nWon\nLost",
|
||||
"options": "CRM Deal Status",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@ -138,7 +138,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-24 16:50:07.177125",
|
||||
"modified": "2023-11-29 11:31:46.968519",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Deal",
|
||||
|
||||
0
crm/fcrm/doctype/crm_deal_status/__init__.py
Normal file
0
crm/fcrm/doctype/crm_deal_status/__init__.py
Normal file
8
crm/fcrm/doctype/crm_deal_status/crm_deal_status.js
Normal file
8
crm/fcrm/doctype/crm_deal_status/crm_deal_status.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 Status", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
62
crm/fcrm/doctype/crm_deal_status/crm_deal_status.json
Normal file
62
crm/fcrm/doctype/crm_deal_status/crm_deal_status.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:deal_status",
|
||||
"creation": "2023-11-29 11:24:55.543387",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"deal_status",
|
||||
"color",
|
||||
"position"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "gray",
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Color",
|
||||
"options": "black\ngray\nblue\ngreen\nred\npink\norange\namber\nyellow\ncyan\nteal\nviolet\npurple"
|
||||
},
|
||||
{
|
||||
"fieldname": "deal_status",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "position",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Position"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-29 12:52:03.070218",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Deal Status",
|
||||
"naming_rule": "By fieldname",
|
||||
"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": []
|
||||
}
|
||||
9
crm/fcrm/doctype/crm_deal_status/crm_deal_status.py
Normal file
9
crm/fcrm/doctype/crm_deal_status/crm_deal_status.py
Normal file
@ -0,0 +1,9 @@
|
||||
# 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 CRMDealStatus(Document):
|
||||
pass
|
||||
9
crm/fcrm/doctype/crm_deal_status/test_crm_deal_status.py
Normal file
9
crm/fcrm/doctype/crm_deal_status/test_crm_deal_status.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 TestCRMDealStatus(FrappeTestCase):
|
||||
pass
|
||||
@ -74,10 +74,10 @@
|
||||
{
|
||||
"default": "Open",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "Open\nContacted\nNurture\nQualified\nUnqualified\nJunk",
|
||||
"options": "CRM Lead Status",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@ -201,7 +201,7 @@
|
||||
"image_field": "image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-24 16:38:36.695965",
|
||||
"modified": "2023-11-29 11:31:08.555096",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Lead",
|
||||
|
||||
0
crm/fcrm/doctype/crm_lead_status/__init__.py
Normal file
0
crm/fcrm/doctype/crm_lead_status/__init__.py
Normal file
8
crm/fcrm/doctype/crm_lead_status/crm_lead_status.js
Normal file
8
crm/fcrm/doctype/crm_lead_status/crm_lead_status.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 Lead Status", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
63
crm/fcrm/doctype/crm_lead_status/crm_lead_status.json
Normal file
63
crm/fcrm/doctype/crm_lead_status/crm_lead_status.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:lead_status",
|
||||
"creation": "2023-11-29 11:09:53.678414",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"lead_status",
|
||||
"color",
|
||||
"position"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "gray",
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Color",
|
||||
"options": "black\ngray\nblue\ngreen\nred\npink\norange\namber\nyellow\ncyan\nteal\nviolet\npurple"
|
||||
},
|
||||
{
|
||||
"fieldname": "lead_status",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "position",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Position"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-29 12:52:25.641581",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Lead Status",
|
||||
"naming_rule": "By fieldname",
|
||||
"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": []
|
||||
}
|
||||
9
crm/fcrm/doctype/crm_lead_status/crm_lead_status.py
Normal file
9
crm/fcrm/doctype/crm_lead_status/crm_lead_status.py
Normal file
@ -0,0 +1,9 @@
|
||||
# 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 CRMLeadStatus(Document):
|
||||
pass
|
||||
9
crm/fcrm/doctype/crm_lead_status/test_crm_lead_status.py
Normal file
9
crm/fcrm/doctype/crm_lead_status/test_crm_lead_status.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 TestCRMLeadStatus(FrappeTestCase):
|
||||
pass
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"charts": [],
|
||||
"content": "[{\"id\":\"1nr6UkvDiL\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h5\\\"><b>PORTAL</b></span>\",\"col\":12}},{\"id\":\"1hyi8SysUY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"CRM Portal Page\",\"col\":3}},{\"id\":\"ktENiGaqXQ\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"VgeWLYOuAS\",\"type\":\"paragraph\",\"data\":{\"text\":\"<b>SHORTCUTS</b>\",\"col\":12}},{\"id\":\"A66FpG-K3T\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leads\",\"col\":3}},{\"id\":\"n9b6N5ebOj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Deals\",\"col\":3}},{\"id\":\"sGHTXrludH\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Organizations\",\"col\":3}},{\"id\":\"uXZNCdqxy0\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Contacts\",\"col\":3}}]",
|
||||
"content": "[{\"id\":\"1nr6UkvDiL\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h5\\\"><b>PORTAL</b></span>\",\"col\":12}},{\"id\":\"1hyi8SysUY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"CRM Portal Page\",\"col\":3}},{\"id\":\"ktENiGaqXQ\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"VgeWLYOuAS\",\"type\":\"paragraph\",\"data\":{\"text\":\"<b>SHORTCUTS</b>\",\"col\":12}},{\"id\":\"A66FpG-K3T\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leads\",\"col\":3}},{\"id\":\"n9b6N5ebOj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Deals\",\"col\":3}},{\"id\":\"sGHTXrludH\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Organizations\",\"col\":3}},{\"id\":\"uXZNCdqxy0\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Contacts\",\"col\":3}},{\"id\":\"TZ7cULX3Tk\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"zpySv0nGVQ\",\"type\":\"paragraph\",\"data\":{\"text\":\"<b>META</b>\",\"col\":12}},{\"id\":\"fa-uKzobpp\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Lead Statuses\",\"col\":3}},{\"id\":\"hxoZghUHP2\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Deal Statuses\",\"col\":3}},{\"id\":\"HbgghUpc8N\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Lead Sources\",\"col\":3}},{\"id\":\"8cPs7Fohb4\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Industries\",\"col\":3}}]",
|
||||
"creation": "2023-11-27 13:55:17.090361",
|
||||
"custom_blocks": [],
|
||||
"docstatus": 0,
|
||||
@ -13,8 +13,8 @@
|
||||
"is_hidden": 0,
|
||||
"label": "Frappe CRM",
|
||||
"links": [],
|
||||
"modified": "2023-11-27 14:29:01.217327",
|
||||
"modified_by": "shariq@frappe.io",
|
||||
"modified": "2023-11-29 13:37:56.731645",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "Frappe CRM",
|
||||
"number_cards": [],
|
||||
@ -40,6 +40,14 @@
|
||||
"type": "URL",
|
||||
"url": "/crm"
|
||||
},
|
||||
{
|
||||
"color": "Grey",
|
||||
"doc_view": "List",
|
||||
"label": "Lead Statuses",
|
||||
"link_to": "CRM Lead Status",
|
||||
"stats_filter": "[]",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"color": "Grey",
|
||||
"doc_view": "List",
|
||||
@ -48,6 +56,22 @@
|
||||
"stats_filter": "[]",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"color": "Grey",
|
||||
"doc_view": "List",
|
||||
"label": "Deal Statuses",
|
||||
"link_to": "CRM Deal Status",
|
||||
"stats_filter": "[]",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"color": "Grey",
|
||||
"doc_view": "List",
|
||||
"label": "Lead Sources",
|
||||
"link_to": "CRM Lead Source",
|
||||
"stats_filter": "[]",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"color": "Grey",
|
||||
"doc_view": "List",
|
||||
@ -63,6 +87,14 @@
|
||||
"link_to": "Contact",
|
||||
"stats_filter": "[]",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"color": "Grey",
|
||||
"doc_view": "List",
|
||||
"label": "Industries",
|
||||
"link_to": "CRM Industry",
|
||||
"stats_filter": "[]",
|
||||
"type": "DocType"
|
||||
}
|
||||
],
|
||||
"title": "Frappe CRM"
|
||||
|
||||
@ -70,8 +70,8 @@ website_route_rules = [
|
||||
# Installation
|
||||
# ------------
|
||||
|
||||
# before_install = "crm.install.before_install"
|
||||
# after_install = "crm.install.after_install"
|
||||
before_install = "crm.install.before_install"
|
||||
after_install = "crm.install.after_install"
|
||||
|
||||
# Uninstallation
|
||||
# ------------
|
||||
|
||||
93
crm/install.py
Normal file
93
crm/install.py
Normal file
@ -0,0 +1,93 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def before_install():
|
||||
pass
|
||||
|
||||
def after_install():
|
||||
add_default_lead_statuses()
|
||||
add_default_deal_statuses()
|
||||
frappe.db.commit()
|
||||
|
||||
def add_default_lead_statuses():
|
||||
statuses = {
|
||||
"Open": {
|
||||
"color": "gray",
|
||||
"position": 1,
|
||||
},
|
||||
"Contacted": {
|
||||
"color": "orange",
|
||||
"position": 2,
|
||||
},
|
||||
"Nurture": {
|
||||
"color": "blue",
|
||||
"position": 3,
|
||||
},
|
||||
"Qualified": {
|
||||
"color": "green",
|
||||
"position": 4,
|
||||
},
|
||||
"Unqualified": {
|
||||
"color": "red",
|
||||
"position": 5,
|
||||
},
|
||||
"Junk": {
|
||||
"color": "purple",
|
||||
"position": 6,
|
||||
},
|
||||
}
|
||||
|
||||
for status in statuses:
|
||||
if frappe.db.exists("CRM Lead Status", status):
|
||||
continue
|
||||
|
||||
doc = frappe.new_doc("CRM Lead Status")
|
||||
doc.lead_status = status
|
||||
doc.color = statuses[status]["color"]
|
||||
doc.position = statuses[status]["position"]
|
||||
doc.insert()
|
||||
|
||||
def add_default_deal_statuses():
|
||||
statuses = {
|
||||
"Qualification": {
|
||||
"color": "gray",
|
||||
"position": 1,
|
||||
},
|
||||
"Demo/Making": {
|
||||
"color": "orange",
|
||||
"position": 2,
|
||||
},
|
||||
"Proposal/Quotation": {
|
||||
"color": "blue",
|
||||
"position": 3,
|
||||
},
|
||||
"Negotiation": {
|
||||
"color": "yellow",
|
||||
"position": 4,
|
||||
},
|
||||
"Ready to Close": {
|
||||
"color": "purple",
|
||||
"position": 5,
|
||||
},
|
||||
"Won": {
|
||||
"color": "green",
|
||||
"position": 6,
|
||||
},
|
||||
"Lost": {
|
||||
"color": "red",
|
||||
"position": 7,
|
||||
},
|
||||
}
|
||||
|
||||
for status in statuses:
|
||||
if frappe.db.exists("CRM Deal Status", status):
|
||||
continue
|
||||
|
||||
doc = frappe.new_doc("CRM Deal Status")
|
||||
doc.deal_status = status
|
||||
doc.color = statuses[status]["color"]
|
||||
doc.position = statuses[status]["position"]
|
||||
doc.insert()
|
||||
@ -11,7 +11,7 @@
|
||||
v-model="newDeal[field.name]"
|
||||
>
|
||||
<template v-if="field.name == 'status'" #prefix>
|
||||
<IndicatorIcon :class="dealStatuses[newDeal[field.name]].color" />
|
||||
<IndicatorIcon :class="getDealStatus(newDeal[field.name]).color" />
|
||||
</template>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
@ -45,7 +45,7 @@
|
||||
</FormControl>
|
||||
<Dropdown
|
||||
v-else-if="field.type === 'dropdown'"
|
||||
:options="statusDropdownOptions(newDeal, 'deal')"
|
||||
:options="statusOptions('deal')"
|
||||
class="w-full flex-1"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
@ -55,7 +55,7 @@
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="dealStatuses[newDeal[field.name]].color"
|
||||
:class="getDealStatus(newDeal[field.name]).color"
|
||||
/>
|
||||
</template>
|
||||
<template #default>{{ newDeal[field.name] }}</template>
|
||||
@ -89,11 +89,13 @@ import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { dealStatuses, statusDropdownOptions, activeAgents } from '@/utils'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { activeAgents } from '@/utils'
|
||||
import { FormControl, Button, Dropdown, FeatherIcon } from 'frappe-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const { getUser } = usersStore()
|
||||
const { getDealStatus, statusOptions } = statusesStore()
|
||||
|
||||
const props = defineProps({
|
||||
newDeal: {
|
||||
@ -170,7 +172,7 @@ const allFields = [
|
||||
label: 'Status',
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
options: statusDropdownOptions(props.newDeal, 'deal'),
|
||||
options: statusOptions('deal'),
|
||||
},
|
||||
{
|
||||
label: 'Deal Owner',
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
v-model="newLead[field.name]"
|
||||
>
|
||||
<template v-if="field.name == 'status'" #prefix>
|
||||
<IndicatorIcon :class="leadStatuses[newLead[field.name]].color" />
|
||||
<IndicatorIcon :class="getLeadStatus(newLead[field.name]).color" />
|
||||
</template>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
@ -45,7 +45,7 @@
|
||||
</FormControl>
|
||||
<Dropdown
|
||||
v-else-if="field.type === 'dropdown'"
|
||||
:options="statusDropdownOptions(newLead)"
|
||||
:options="statusOptions('lead')"
|
||||
class="w-full flex-1"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
@ -55,7 +55,7 @@
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="leadStatuses[newLead[field.name]].color"
|
||||
:class="getLeadStatus(newLead[field.name]).color"
|
||||
/>
|
||||
</template>
|
||||
<template #default>{{ newLead[field.name] }}</template>
|
||||
@ -89,11 +89,13 @@ import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { leadStatuses, statusDropdownOptions, activeAgents } from '@/utils'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { activeAgents } from '@/utils'
|
||||
import { FormControl, Button, Dropdown, FeatherIcon } from 'frappe-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const { getUser } = usersStore()
|
||||
const { getLeadStatus, statusOptions } = statusesStore()
|
||||
|
||||
const props = defineProps({
|
||||
newLead: {
|
||||
@ -170,7 +172,7 @@ const allFields = [
|
||||
label: 'Status',
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
options: statusDropdownOptions(props.newLead),
|
||||
options: statusOptions('lead'),
|
||||
},
|
||||
{
|
||||
label: 'Lead Owner',
|
||||
|
||||
@ -143,7 +143,7 @@
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import DurationIcon from '@/components/Icons/DurationIcon.vue'
|
||||
import NoteModal from '@/components/Modals/NoteModal.vue'
|
||||
import { dateFormat, timeAgo, dateTooltipFormat } from '@/utils'
|
||||
import { dateFormat, timeAgo, dateTooltipFormat, secondsToDuration } from '@/utils'
|
||||
import {
|
||||
TextEditor,
|
||||
Avatar,
|
||||
@ -156,7 +156,6 @@ import {
|
||||
} from 'frappe-ui'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
import { secondsToDuration } from '@/utils'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
|
||||
@ -237,18 +237,18 @@ import {
|
||||
dateTooltipFormat,
|
||||
timeAgo,
|
||||
formatNumberIntoCurrency,
|
||||
dealStatuses,
|
||||
leadStatuses,
|
||||
} from '@/utils'
|
||||
import { usersStore } from '@/stores/users.js'
|
||||
import { contactsStore } from '@/stores/contacts.js'
|
||||
import { organizationsStore } from '@/stores/organizations.js'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { ref, computed, h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { getContactByName, contacts } = contactsStore()
|
||||
const { getUser } = usersStore()
|
||||
const { getOrganization } = organizationsStore()
|
||||
const { getLeadStatus, getDealStatus } = statusesStore()
|
||||
|
||||
const props = defineProps({
|
||||
contactId: {
|
||||
@ -390,7 +390,7 @@ function getLeadRowObject(lead) {
|
||||
},
|
||||
status: {
|
||||
label: lead.status,
|
||||
color: leadStatuses[lead.status]?.color,
|
||||
color: getLeadStatus(lead.status)?.color,
|
||||
},
|
||||
email: lead.email,
|
||||
mobile_no: lead.mobile_no,
|
||||
@ -415,7 +415,7 @@ function getDealRowObject(deal) {
|
||||
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
|
||||
status: {
|
||||
label: deal.status,
|
||||
color: dealStatuses[deal.status]?.color,
|
||||
color: getDealStatus(deal.status)?.color,
|
||||
},
|
||||
email: deal.email,
|
||||
mobile_no: deal.mobile_no,
|
||||
|
||||
@ -18,13 +18,11 @@
|
||||
<UserAvatar class="mr-2" :user="option.email" size="sm" />
|
||||
</template>
|
||||
</FormControl>
|
||||
<Dropdown
|
||||
:options="statusDropdownOptions(deal.data, 'deal', updateField)"
|
||||
>
|
||||
<Dropdown :options="statusOptions('deal', updateField)">
|
||||
<template #default="{ open }">
|
||||
<Button :label="deal.data.status">
|
||||
<template #prefix>
|
||||
<IndicatorIcon :class="dealStatuses[deal.data.status].color" />
|
||||
<IndicatorIcon :class="getDealStatus(deal.data.status).color" />
|
||||
</template>
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
@ -163,7 +161,9 @@
|
||||
:image="getContactByName(contact.name).image"
|
||||
size="md"
|
||||
/>
|
||||
<div class="truncate">{{ getContactByName(contact.name).full_name }}</div>
|
||||
<div class="truncate">
|
||||
{{ getContactByName(contact.name).full_name }}
|
||||
</div>
|
||||
<Badge
|
||||
v-if="contact.is_primary"
|
||||
class="ml-2"
|
||||
@ -272,16 +272,11 @@ import ContactModal from '@/components/Modals/ContactModal.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import Section from '@/components/Section.vue'
|
||||
import SectionFields from '@/components/SectionFields.vue'
|
||||
import {
|
||||
dealStatuses,
|
||||
statusDropdownOptions,
|
||||
openWebsite,
|
||||
createToast,
|
||||
activeAgents,
|
||||
} from '@/utils'
|
||||
import { openWebsite, createToast, activeAgents } from '@/utils'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import {
|
||||
createResource,
|
||||
FeatherIcon,
|
||||
@ -300,6 +295,7 @@ import { useRouter } from 'vue-router'
|
||||
const { getUser } = usersStore()
|
||||
const { getContactByName, contacts } = contactsStore()
|
||||
const { organizations, getOrganization } = organizationsStore()
|
||||
const { statusOptions, getDealStatus } = statusesStore()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<Filter doctype="CRM Deal" />
|
||||
<SortBy doctype="CRM Deal" />
|
||||
<ViewSettings doctype="CRM Deal" v-model="deals"/>
|
||||
<ViewSettings doctype="CRM Deal" v-model="deals" />
|
||||
</div>
|
||||
</div>
|
||||
<DealsListView v-if="deals.data" :rows="rows" :columns="deals.data.columns" />
|
||||
@ -62,11 +62,11 @@ import Filter from '@/components/Filter.vue'
|
||||
import ViewSettings from '@/components/ViewSettings.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { useOrderBy } from '@/composables/orderby'
|
||||
import { useFilter } from '@/composables/filter'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import {
|
||||
dealStatuses,
|
||||
dateFormat,
|
||||
dateTooltipFormat,
|
||||
timeAgo,
|
||||
@ -87,6 +87,7 @@ const breadcrumbs = [{ label: 'Deals', route: { name: 'Deals' } }]
|
||||
|
||||
const { getUser } = usersStore()
|
||||
const { getOrganization } = organizationsStore()
|
||||
const { getDealStatus } = statusesStore()
|
||||
const { get: getOrderBy } = useOrderBy()
|
||||
const { getArgs, storage } = useFilter()
|
||||
|
||||
@ -149,7 +150,7 @@ const rows = computed(() => {
|
||||
} else if (row == 'status') {
|
||||
_rows[row] = {
|
||||
label: deal.status,
|
||||
color: dealStatuses[deal.status]?.color,
|
||||
color: getDealStatus(deal.status)?.color,
|
||||
}
|
||||
} else if (row == 'deal_owner') {
|
||||
_rows[row] = {
|
||||
|
||||
@ -18,13 +18,11 @@
|
||||
<UserAvatar class="mr-2" :user="option.email" size="sm" />
|
||||
</template>
|
||||
</FormControl>
|
||||
<Dropdown
|
||||
:options="statusDropdownOptions(lead.data, 'lead', updateField)"
|
||||
>
|
||||
<Dropdown :options="statusOptions('lead', updateField)">
|
||||
<template #default="{ open }">
|
||||
<Button :label="lead.data.status">
|
||||
<template #prefix>
|
||||
<IndicatorIcon :class="leadStatuses[lead.data.status].color" />
|
||||
<IndicatorIcon :class="getLeadStatus(lead.data.status).color" />
|
||||
</template>
|
||||
<template #suffix
|
||||
><FeatherIcon
|
||||
@ -179,16 +177,11 @@ import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
|
||||
import Section from '@/components/Section.vue'
|
||||
import SectionFields from '@/components/SectionFields.vue'
|
||||
import {
|
||||
leadStatuses,
|
||||
statusDropdownOptions,
|
||||
openWebsite,
|
||||
createToast,
|
||||
activeAgents,
|
||||
} from '@/utils'
|
||||
import { openWebsite, createToast, activeAgents } from '@/utils'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import {
|
||||
createResource,
|
||||
FileUploader,
|
||||
@ -208,6 +201,7 @@ import { useRouter } from 'vue-router'
|
||||
const { getUser } = usersStore()
|
||||
const { contacts } = contactsStore()
|
||||
const { organizations, getOrganization } = organizationsStore()
|
||||
const { statusOptions, getLeadStatus } = statusesStore()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@ -29,14 +29,10 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<Filter doctype="CRM Lead" />
|
||||
<SortBy doctype="CRM Lead" />
|
||||
<ViewSettings doctype="CRM Lead" v-model="leads"/>
|
||||
<ViewSettings doctype="CRM Lead" v-model="leads" />
|
||||
</div>
|
||||
</div>
|
||||
<LeadsListView
|
||||
v-if="leads.data"
|
||||
:rows="rows"
|
||||
:columns="leads.data.columns"
|
||||
/>
|
||||
<LeadsListView v-if="leads.data" :rows="rows" :columns="leads.data.columns" />
|
||||
<Dialog
|
||||
v-model="showNewDialog"
|
||||
:options="{
|
||||
@ -65,10 +61,11 @@ import Filter from '@/components/Filter.vue'
|
||||
import ViewSettings from '@/components/ViewSettings.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { useOrderBy } from '@/composables/orderby'
|
||||
import { useFilter } from '@/composables/filter'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { leadStatuses, dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
|
||||
import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
|
||||
import {
|
||||
FeatherIcon,
|
||||
Dialog,
|
||||
@ -84,6 +81,7 @@ const breadcrumbs = [{ label: 'Leads', route: { name: 'Leads' } }]
|
||||
|
||||
const { getUser } = usersStore()
|
||||
const { getOrganization } = organizationsStore()
|
||||
const { getLeadStatus } = statusesStore()
|
||||
const { get: getOrderBy } = useOrderBy()
|
||||
const { getArgs, storage } = useFilter()
|
||||
|
||||
@ -154,7 +152,7 @@ const rows = computed(() => {
|
||||
} else if (row == 'status') {
|
||||
_rows[row] = {
|
||||
label: lead.status,
|
||||
color: leadStatuses[lead.status]?.color,
|
||||
color: getLeadStatus(lead.status)?.color,
|
||||
}
|
||||
} else if (row == 'lead_owner') {
|
||||
_rows[row] = {
|
||||
|
||||
@ -248,16 +248,15 @@ import CameraIcon from '@/components/Icons/CameraIcon.vue'
|
||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
||||
import DealsIcon from '@/components/Icons/DealsIcon.vue'
|
||||
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { organizationsStore } from '@/stores/organizations.js'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import {
|
||||
dateFormat,
|
||||
dateTooltipFormat,
|
||||
timeAgo,
|
||||
leadStatuses,
|
||||
dealStatuses,
|
||||
formatNumberIntoCurrency,
|
||||
} from '@/utils'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { h, computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
@ -268,6 +267,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const { organizations, getOrganization } = organizationsStore()
|
||||
const { getLeadStatus, getDealStatus } = statusesStore()
|
||||
const showOrganizationModal = ref(false)
|
||||
const detailMode = ref(false)
|
||||
|
||||
@ -454,7 +454,7 @@ function getLeadRowObject(lead) {
|
||||
},
|
||||
status: {
|
||||
label: lead.status,
|
||||
color: leadStatuses[lead.status]?.color,
|
||||
color: getLeadStatus(lead.status)?.color,
|
||||
},
|
||||
email: lead.email,
|
||||
mobile_no: lead.mobile_no,
|
||||
@ -479,7 +479,7 @@ function getDealRowObject(deal) {
|
||||
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
|
||||
status: {
|
||||
label: deal.status,
|
||||
color: dealStatuses[deal.status]?.color,
|
||||
color: getDealStatus(deal.status)?.color,
|
||||
},
|
||||
email: deal.email,
|
||||
mobile_no: deal.mobile_no,
|
||||
|
||||
79
frontend/src/stores/statuses.js
Normal file
79
frontend/src/stores/statuses.js
Normal file
@ -0,0 +1,79 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { createListResource } from 'frappe-ui'
|
||||
import { reactive, h } from 'vue'
|
||||
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||
|
||||
export const statusesStore = defineStore('crm-statuses', () => {
|
||||
let leadStatusesByName = reactive({})
|
||||
let dealStatusesByName = reactive({})
|
||||
|
||||
const leadStatuses = createListResource({
|
||||
doctype: 'CRM Lead Status',
|
||||
fields: ['name', 'color', 'position'],
|
||||
orderBy: 'position asc',
|
||||
cache: 'lead-statuses',
|
||||
initialData: [],
|
||||
auto: true,
|
||||
transform(statuses) {
|
||||
for (let status of statuses) {
|
||||
status.color =
|
||||
status.color == 'black'
|
||||
? '!text-gray-900'
|
||||
: `!text-${status.color}-600`
|
||||
leadStatusesByName[status.name] = status
|
||||
}
|
||||
return statuses
|
||||
},
|
||||
})
|
||||
|
||||
const dealStatuses = createListResource({
|
||||
doctype: 'CRM Deal Status',
|
||||
fields: ['name', 'color', 'position'],
|
||||
orderBy: 'position asc',
|
||||
cache: 'deal-statuses',
|
||||
initialData: [],
|
||||
auto: true,
|
||||
transform(statuses) {
|
||||
for (let status of statuses) {
|
||||
status.color =
|
||||
status.color == 'black'
|
||||
? '!text-gray-900'
|
||||
: `!text-${status.color}-600`
|
||||
dealStatusesByName[status.name] = status
|
||||
}
|
||||
return statuses
|
||||
},
|
||||
})
|
||||
|
||||
function getLeadStatus(name) {
|
||||
return leadStatusesByName[name]
|
||||
}
|
||||
|
||||
function getDealStatus(name) {
|
||||
return dealStatusesByName[name]
|
||||
}
|
||||
|
||||
function statusOptions(doctype, action) {
|
||||
let statusesByName =
|
||||
doctype == 'deal' ? dealStatusesByName : leadStatusesByName
|
||||
let options = []
|
||||
for (const status in statusesByName) {
|
||||
options.push({
|
||||
label: statusesByName[status].name,
|
||||
icon: () => h(IndicatorIcon, { class: statusesByName[status].color }),
|
||||
onClick: () => {
|
||||
action && action('status', statusesByName[status].name)
|
||||
},
|
||||
})
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
return {
|
||||
leadStatuses,
|
||||
dealStatuses,
|
||||
getLeadStatus,
|
||||
getDealStatus,
|
||||
statusOptions,
|
||||
}
|
||||
})
|
||||
@ -1,4 +1,3 @@
|
||||
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||
import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue'
|
||||
import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
@ -24,67 +23,6 @@ export function timeAgo(date) {
|
||||
|
||||
export const dateTooltipFormat = 'ddd, MMM D, YYYY h:mm A'
|
||||
|
||||
export const leadStatuses = {
|
||||
Open: { label: 'Open', color: '!text-gray-600' },
|
||||
Contacted: {
|
||||
label: 'Contacted',
|
||||
color: '!text-orange-600',
|
||||
},
|
||||
Nurture: {
|
||||
label: 'Nurture',
|
||||
color: '!text-blue-600',
|
||||
},
|
||||
Qualified: {
|
||||
label: 'Qualified',
|
||||
color: '!text-green-600',
|
||||
},
|
||||
Unqualified: {
|
||||
label: 'Unqualified',
|
||||
color: '!text-red-600',
|
||||
},
|
||||
Junk: { label: 'Junk', color: '!text-purple-600' },
|
||||
}
|
||||
|
||||
export const dealStatuses = {
|
||||
Qualification: {
|
||||
label: 'Qualification',
|
||||
color: '!text-gray-600',
|
||||
},
|
||||
'Demo/Making': {
|
||||
label: 'Demo/Making',
|
||||
color: '!text-orange-600',
|
||||
},
|
||||
'Proposal/Quotation': {
|
||||
label: 'Proposal/Quotation',
|
||||
color: '!text-blue-600',
|
||||
},
|
||||
Negotiation: {
|
||||
label: 'Negotiation',
|
||||
color: '!text-yellow-600',
|
||||
},
|
||||
'Ready to Close': {
|
||||
label: 'Ready to Close',
|
||||
color: '!text-purple-600',
|
||||
},
|
||||
Won: { label: 'Won', color: '!text-green-600' },
|
||||
Lost: { label: 'Lost', color: '!text-red-600' },
|
||||
}
|
||||
|
||||
export function statusDropdownOptions(data, doctype, action) {
|
||||
let statuses = doctype == 'deal' ? dealStatuses : leadStatuses
|
||||
let options = []
|
||||
for (const status in statuses) {
|
||||
options.push({
|
||||
label: statuses[status].label,
|
||||
icon: () => h(IndicatorIcon, { class: statuses[status].color }),
|
||||
onClick: () => {
|
||||
action && action('status', statuses[status].label)
|
||||
},
|
||||
})
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
export function taskStatusOptions(action, data) {
|
||||
return ['Backlog', 'Todo', 'In Progress', 'Done', 'Canceled'].map(
|
||||
(status) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user