From 210a9d8d0635f296a72e65e1ce2899d274b5a6fa Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 11 Jul 2025 16:05:57 +0530 Subject: [PATCH 01/14] fix: added type of deal status field --- .../crm_deal_status/crm_deal_status.json | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json b/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json index d9b5f203..2af130de 100644 --- a/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json +++ b/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json @@ -7,9 +7,11 @@ "engine": "InnoDB", "field_order": [ "deal_status", - "color", + "type", "position", - "probability" + "column_break_ojiu", + "probability", + "color" ], "fields": [ { @@ -39,12 +41,24 @@ "fieldtype": "Percent", "in_list_view": 1, "label": "Probability" + }, + { + "default": "Open", + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "Open\nOngoing\nOn Hold\nWon\nLost" + }, + { + "fieldname": "column_break_ojiu", + "fieldtype": "Column Break" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-01 12:06:42.937440", + "modified": "2025-07-11 16:03:28.077955", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Deal Status", From efc5dd93e91760aa73a4d8a360d642c4031df8ee Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 11 Jul 2025 16:07:52 +0530 Subject: [PATCH 02/14] fix: added type in default deak status while installing --- crm/install.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crm/install.py b/crm/install.py index ae0a437c..1b401db2 100644 --- a/crm/install.py +++ b/crm/install.py @@ -69,36 +69,43 @@ def add_default_deal_statuses(): statuses = { "Qualification": { "color": "gray", + "type": "Open", "probability": 10, "position": 1, }, "Demo/Making": { "color": "orange", + "type": "Ongoing", "probability": 25, "position": 2, }, "Proposal/Quotation": { "color": "blue", + "type": "Ongoing", "probability": 50, "position": 3, }, "Negotiation": { "color": "yellow", + "type": "Ongoing", "probability": 70, "position": 4, }, "Ready to Close": { "color": "purple", + "type": "Ongoing", "probability": 90, "position": 5, }, "Won": { "color": "green", + "type": "Won", "probability": 100, "position": 6, }, "Lost": { "color": "red", + "type": "Lost", "probability": 0, "position": 7, }, @@ -111,6 +118,7 @@ def add_default_deal_statuses(): doc = frappe.new_doc("CRM Deal Status") doc.deal_status = status doc.color = statuses[status]["color"] + doc.type = statuses[status]["type"] doc.probability = statuses[status]["probability"] doc.position = statuses[status]["position"] doc.insert() From 4e6d4a1d77b062c531b8c84634c312b6cb3537fe Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 11 Jul 2025 16:16:40 +0530 Subject: [PATCH 03/14] patch: added patch to update deal status type --- crm/patches.txt | 3 +- crm/patches/v1_0/update_deal_status_type.py | 44 +++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 crm/patches/v1_0/update_deal_status_type.py diff --git a/crm/patches.txt b/crm/patches.txt index 484c73dc..910f0fe1 100644 --- a/crm/patches.txt +++ b/crm/patches.txt @@ -13,4 +13,5 @@ crm.patches.v1_0.update_deal_quick_entry_layout crm.patches.v1_0.update_layouts_to_new_format crm.patches.v1_0.move_twilio_agent_to_telephony_agent crm.patches.v1_0.create_default_scripts # 13-06-2025 -crm.patches.v1_0.update_deal_status_probabilities \ No newline at end of file +crm.patches.v1_0.update_deal_status_probabilities +crm.patches.v1_0.update_deal_status_type \ No newline at end of file diff --git a/crm/patches/v1_0/update_deal_status_type.py b/crm/patches/v1_0/update_deal_status_type.py new file mode 100644 index 00000000..230f3776 --- /dev/null +++ b/crm/patches/v1_0/update_deal_status_type.py @@ -0,0 +1,44 @@ +import frappe + + +def execute(): + deal_statuses = frappe.get_all("CRM Deal Status", fields=["name", "type", "deal_status"]) + + openStatuses = ["New", "Open", "Unassigned", "Qualification"] + ongoingStatuses = [ + "Demo/Making", + "Proposal/Quotation", + "Negotiation", + "Ready to Close", + "Demo Scheduled", + "Follow Up", + ] + onHoldStatuses = ["On Hold", "Paused", "Stalled", "Awaiting Reply"] + wonStatuses = ["Won", "Closed Won", "Successful", "Completed"] + lostStatuses = [ + "Lost", + "Closed", + "Closed Lost", + "Junk", + "Unqualified", + "Disqualified", + "Cancelled", + "No Response", + ] + + for status in deal_statuses: + if status.type is None or status.type == "": + if status.deal_status in openStatuses: + type = "Open" + elif status.deal_status in ongoingStatuses: + type = "Ongoing" + elif status.deal_status in onHoldStatuses: + type = "On Hold" + elif status.deal_status in wonStatuses: + type = "Won" + elif status.deal_status in lostStatuses: + type = "Lost" + else: + type = "Ongoing" + + frappe.db.set_value("CRM Deal Status", status.name, "type", type) From 51530b760830002e9ba3f8bc8cce69d94fd51d9c Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 11 Jul 2025 16:35:47 +0530 Subject: [PATCH 04/14] fix: show lost reason modal if status of type Lost is set --- crm/fcrm/doctype/crm_deal/crm_deal.py | 2 +- frontend/src/pages/Deal.vue | 4 ++-- frontend/src/pages/MobileDeal.vue | 4 ++-- frontend/src/stores/statuses.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py index b82f16db..c3a8488d 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.py +++ b/crm/fcrm/doctype/crm_deal/crm_deal.py @@ -166,7 +166,7 @@ class CRMDeal(Document): """ Validate the lost reason if the status is set to "Lost". """ - if self.status == "Lost": + if self.status and frappe.get_cached_value("CRM Deal Status", self.status, "type") == "Lost": if not self.lost_reason: frappe.throw(_("Please specify a reason for losing the deal."), frappe.ValidationError) elif self.lost_reason == "Other" and not self.lost_notes: diff --git a/frontend/src/pages/Deal.vue b/frontend/src/pages/Deal.vue index a2d8648f..8ebd5361 100644 --- a/frontend/src/pages/Deal.vue +++ b/frontend/src/pages/Deal.vue @@ -783,7 +783,7 @@ const showLostReasonModal = ref(false) function setLostReason() { if ( - document.doc.status !== 'Lost' || + getDealStatus(document.doc.status).type !== 'Lost' || (document.doc.lost_reason && document.doc.lost_reason !== 'Other') || (document.doc.lost_reason === 'Other' && document.doc.lost_notes) ) { @@ -795,7 +795,7 @@ function setLostReason() { } function beforeStatusChange(data) { - if (data?.hasOwnProperty('status') && data.status == 'Lost') { + if (data?.hasOwnProperty('status') && getDealStatus(data.status).type == 'Lost') { setLostReason() } else { document.save.submit(null, { diff --git a/frontend/src/pages/MobileDeal.vue b/frontend/src/pages/MobileDeal.vue index 9a93f304..5c8e8284 100644 --- a/frontend/src/pages/MobileDeal.vue +++ b/frontend/src/pages/MobileDeal.vue @@ -643,7 +643,7 @@ const showLostReasonModal = ref(false) function setLostReason() { if ( - document.doc.status !== 'Lost' || + getDealStatus(document.doc.status).type !== 'Lost' || (document.doc.lost_reason && document.doc.lost_reason !== 'Other') || (document.doc.lost_reason === 'Other' && document.doc.lost_notes) ) { @@ -655,7 +655,7 @@ function setLostReason() { } function beforeStatusChange(data) { - if (data?.hasOwnProperty('status') && data.status == 'Lost') { + if (data?.hasOwnProperty('status') && getDealStatus(data.status).type == 'Lost') { setLostReason() } else { document.save.submit(null, { diff --git a/frontend/src/stores/statuses.js b/frontend/src/stores/statuses.js index 637c8d2f..9f99b785 100644 --- a/frontend/src/stores/statuses.js +++ b/frontend/src/stores/statuses.js @@ -28,7 +28,7 @@ export const statusesStore = defineStore('crm-statuses', () => { const dealStatuses = createListResource({ doctype: 'CRM Deal Status', - fields: ['name', 'color', 'position'], + fields: ['name', 'color', 'position', 'type'], orderBy: 'position asc', cache: 'deal-statuses', initialData: [], From cb1f9f760cbcda42654e99ff5e98583591ae6874 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 11 Jul 2025 17:00:59 +0530 Subject: [PATCH 05/14] fix: use status.type instead of status in all query --- crm/api/dashboard.py | 67 ++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/crm/api/dashboard.py b/crm/api/dashboard.py index 2fe1dfef..26cfd029 100644 --- a/crm/api/dashboard.py +++ b/crm/api/dashboard.py @@ -160,26 +160,29 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals diff = 1 if user: - conds += f" AND deal_owner = '{user}'" + conds += f" AND d.deal_owner = '{user}'" result = frappe.db.sql( f""" SELECT - COUNT(CASE - WHEN creation >= %(from_date)s AND creation < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) AND status = 'Won' - {conds} - THEN name - ELSE NULL - END) as current_month_deals, + COUNT(CASE + WHEN d.creation >= %(from_date)s AND d.creation < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + AND s.type = 'Won' + {conds} + THEN d.name + ELSE NULL + END) as current_month_deals, - COUNT(CASE - WHEN creation >= %(prev_from_date)s AND creation < %(from_date)s AND status = 'Won' - {conds} - THEN name - ELSE NULL - END) as prev_month_deals - FROM `tabCRM Deal` - """, + COUNT(CASE + WHEN d.creation >= %(prev_from_date)s AND d.creation < %(from_date)s + AND s.type = 'Won' + {conds} + THEN d.name + ELSE NULL + END) as prev_month_deals + FROM `tabCRM Deal` d + JOIN `tabCRM Deal Status` s ON d.status = s.name + """, { "from_date": from_date, "to_date": to_date, @@ -218,25 +221,28 @@ def get_average_deal_value(from_date, to_date, user="", conds="", return_result= diff = 1 if user: - conds += f" AND deal_owner = '{user}'" + conds += f" AND d.deal_owner = '{user}'" result = frappe.db.sql( f""" SELECT AVG(CASE - WHEN d.creation >= %(from_date)s AND d.creation < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) AND d.status != 'Lost' - {conds} + WHEN d.creation >= %(from_date)s AND d.creation < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + AND s.type != 'Lost' + {conds} THEN d.deal_value * IFNULL(d.exchange_rate, 1) ELSE NULL END) as current_month_avg, AVG(CASE - WHEN d.creation >= %(prev_from_date)s AND d.creation < %(from_date)s AND d.status != 'Lost' - {conds} + WHEN d.creation >= %(prev_from_date)s AND d.creation < %(from_date)s + AND s.type != 'Lost' + {conds} THEN d.deal_value * IFNULL(d.exchange_rate, 1) ELSE NULL END) as prev_month_avg FROM `tabCRM Deal` AS d + JOIN `tabCRM Deal Status` s ON d.status = s.name """, { "from_date": from_date, @@ -281,12 +287,15 @@ def get_average_time_to_close(from_date, to_date, user="", conds="", return_resu f""" SELECT AVG(CASE WHEN d.closed_on >= %(from_date)s AND d.closed_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + AND s.type = 'Won' THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_on) END) as current_avg, AVG(CASE WHEN d.closed_on >= %(prev_from_date)s AND d.closed_on < %(prev_to_date)s + AND s.type = 'Won' THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_on) END) as prev_avg FROM `tabCRM Deal` AS d + JOIN `tabCRM Deal Status` s ON d.status = s.name LEFT JOIN `tabCRM Lead` l ON d.lead = l.name - WHERE d.status = 'Won' AND d.closed_on IS NOT NULL + WHERE d.closed_on IS NOT NULL {conds} """, { @@ -356,14 +365,15 @@ def get_sales_trend_data(from_date="", to_date="", user="", lead_conds="", deal_ UNION ALL SELECT - DATE(creation) AS date, + DATE(d.creation) AS date, 0 AS leads, COUNT(*) AS deals, - SUM(CASE WHEN status = 'Won' THEN 1 ELSE 0 END) AS won_deals - FROM `tabCRM Deal` - WHERE DATE(creation) BETWEEN %(from)s AND %(to)s + SUM(CASE WHEN s.type = 'Won' THEN 1 ELSE 0 END) AS won_deals + FROM `tabCRM Deal` d + JOIN `tabCRM Deal Status` s ON d.status = s.name + WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s {deal_conds} - GROUP BY DATE(creation) + GROUP BY DATE(d.creation) ) AS daily GROUP BY date ORDER BY date @@ -488,7 +498,8 @@ def get_lost_deal_reasons(from_date="", to_date="", user="", deal_conds=""): d.lost_reason AS reason, COUNT(*) AS count FROM `tabCRM Deal` AS d - WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s AND d.status = 'Lost' + JOIN `tabCRM Deal Status` s ON d.status = s.name + WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s AND s.type = 'Lost' {deal_conds} GROUP BY d.lost_reason HAVING reason IS NOT NULL AND reason != '' @@ -595,7 +606,7 @@ def get_funnel_conversion_data(from_date="", to_date="", user="", lead_conds="", # Get deal stages all_deal_stages = frappe.get_all( - "CRM Deal Status", filters={"name": ["!=", "Lost"]}, order_by="position", pluck="name" + "CRM Deal Status", filters={"type": ["!=", "Lost"]}, order_by="position", pluck="name" ) # Get deal counts for each stage From 81dc4e1138c86a5ab4a80ab57d1a521bc65ecc1b Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 11 Jul 2025 17:09:21 +0530 Subject: [PATCH 06/14] fix: get ongoing deals and won deals based on closed_on date --- crm/api/dashboard.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/crm/api/dashboard.py b/crm/api/dashboard.py index 26cfd029..4606c4b6 100644 --- a/crm/api/dashboard.py +++ b/crm/api/dashboard.py @@ -87,14 +87,13 @@ def get_lead_count(from_date, to_date, user="", conds="", return_result=False): "value": current_month_leads, "delta": delta_in_percentage, "deltaSuffix": "%", - "negativeIsBetter": False, "tooltip": _("Total number of leads"), } def get_deal_count(from_date, to_date, user="", conds="", return_result=False): """ - Get deal count for the dashboard. + Get ongoing deal count for the dashboard. """ diff = frappe.utils.date_diff(to_date, from_date) @@ -102,25 +101,28 @@ def get_deal_count(from_date, to_date, user="", conds="", return_result=False): diff = 1 if user: - conds += f" AND deal_owner = '{user}'" + conds += f" AND d.deal_owner = '{user}'" result = frappe.db.sql( f""" SELECT - COUNT(CASE - WHEN creation >= %(from_date)s AND creation < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) - {conds} - THEN name - ELSE NULL - END) as current_month_deals, + COUNT(CASE + WHEN d.creation >= %(from_date)s AND d.creation < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + AND s.type NOT IN ('Won', 'Lost') + {conds} + THEN d.name + ELSE NULL + END) as current_month_deals, - COUNT(CASE - WHEN creation >= %(prev_from_date)s AND creation < %(from_date)s - {conds} - THEN name - ELSE NULL - END) as prev_month_deals - FROM `tabCRM Deal` + COUNT(CASE + WHEN d.creation >= %(prev_from_date)s AND d.creation < %(from_date)s + AND s.type NOT IN ('Won', 'Lost') + {conds} + THEN d.name + ELSE NULL + END) as prev_month_deals + FROM `tabCRM Deal` d + JOIN `tabCRM Deal Status` s ON d.status = s.name """, { "from_date": from_date, @@ -141,12 +143,11 @@ def get_deal_count(from_date, to_date, user="", conds="", return_result=False): ) return { - "title": _("Total Deals"), + "title": _("Ongoing Deals"), "value": current_month_deals, "delta": delta_in_percentage, "deltaSuffix": "%", - "negativeIsBetter": False, - "tooltip": _("Total number of deals"), + "tooltip": _("Total number of ongoing deals"), } @@ -166,7 +167,7 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals f""" SELECT COUNT(CASE - WHEN d.creation >= %(from_date)s AND d.creation < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + WHEN d.closed_on >= %(from_date)s AND d.closed_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) AND s.type = 'Won' {conds} THEN d.name @@ -174,7 +175,7 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals END) as current_month_deals, COUNT(CASE - WHEN d.creation >= %(prev_from_date)s AND d.creation < %(from_date)s + WHEN d.closed_on >= %(prev_from_date)s AND d.closed_on < %(from_date)s AND s.type = 'Won' {conds} THEN d.name @@ -206,7 +207,6 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals "value": current_month_deals, "delta": delta_in_percentage, "deltaSuffix": "%", - "negativeIsBetter": False, "tooltip": _("Total number of won deals"), } From 2dd2608c094226efe17d5bbdf1520afc667151ca Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 13 Jul 2025 11:35:14 +0530 Subject: [PATCH 07/14] fix: added two more number cards --- crm/api/dashboard.py | 120 +++++++++++++++++++++++-------- frontend/src/pages/Dashboard.vue | 2 +- 2 files changed, 92 insertions(+), 30 deletions(-) diff --git a/crm/api/dashboard.py b/crm/api/dashboard.py index 4606c4b6..0c5f78f2 100644 --- a/crm/api/dashboard.py +++ b/crm/api/dashboard.py @@ -19,18 +19,22 @@ def get_number_card_data(from_date="", to_date="", user="", lead_conds="", deal_ if is_sales_user and not user: user = frappe.session.user - lead_chart_data = get_lead_count(from_date, to_date, user, lead_conds) - deal_chart_data = get_deal_count(from_date, to_date, user, deal_conds) - get_won_deal_count_data = get_won_deal_count(from_date, to_date, user, deal_conds) - get_average_deal_value_data = get_average_deal_value(from_date, to_date, user, deal_conds) - get_average_time_to_close_data = get_average_time_to_close(from_date, to_date, user, deal_conds) + lead_count_data = get_lead_count(from_date, to_date, user, lead_conds) + ongoing_deal_count_data = get_ongoing_deal_count(from_date, to_date, user, deal_conds)[0] + average_ongoing_deal_value_data = get_ongoing_deal_count(from_date, to_date, user, deal_conds)[1] + won_deal_count_data = get_won_deal_count(from_date, to_date, user, deal_conds)[0] + average_won_deal_value_data = get_won_deal_count(from_date, to_date, user, deal_conds)[1] + average_deal_value_data = get_average_deal_value(from_date, to_date, user, deal_conds) + average_time_to_close_data = get_average_time_to_close(from_date, to_date, user, deal_conds) return [ - lead_chart_data, - deal_chart_data, - get_won_deal_count_data, - get_average_deal_value_data, - get_average_time_to_close_data, + lead_count_data, + ongoing_deal_count_data, + average_ongoing_deal_value_data, + won_deal_count_data, + average_won_deal_value_data, + average_deal_value_data, + average_time_to_close_data, ] @@ -91,9 +95,9 @@ def get_lead_count(from_date, to_date, user="", conds="", return_result=False): } -def get_deal_count(from_date, to_date, user="", conds="", return_result=False): +def get_ongoing_deal_count(from_date, to_date, user="", conds="", return_result=False): """ - Get ongoing deal count for the dashboard. + Get ongoing deal count for the dashboard, and also calculate average deal value for ongoing deals. """ diff = frappe.utils.date_diff(to_date, from_date) @@ -120,7 +124,23 @@ def get_deal_count(from_date, to_date, user="", conds="", return_result=False): {conds} THEN d.name ELSE NULL - END) as prev_month_deals + END) as prev_month_deals, + + AVG(CASE + WHEN d.creation >= %(from_date)s AND d.creation < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + AND s.type NOT IN ('Won', 'Lost') + {conds} + THEN d.deal_value * IFNULL(d.exchange_rate, 1) + ELSE NULL + END) as current_month_avg_value, + + AVG(CASE + WHEN d.creation >= %(prev_from_date)s AND d.creation < %(from_date)s + AND s.type NOT IN ('Won', 'Lost') + {conds} + THEN d.deal_value * IFNULL(d.exchange_rate, 1) + ELSE NULL + END) as prev_month_avg_value FROM `tabCRM Deal` d JOIN `tabCRM Deal Status` s ON d.status = s.name """, @@ -137,23 +157,36 @@ def get_deal_count(from_date, to_date, user="", conds="", return_result=False): current_month_deals = result[0].current_month_deals or 0 prev_month_deals = result[0].prev_month_deals or 0 + current_month_avg_value = result[0].current_month_avg_value or 0 + prev_month_avg_value = result[0].prev_month_avg_value or 0 delta_in_percentage = ( (current_month_deals - prev_month_deals) / prev_month_deals * 100 if prev_month_deals else 0 ) + avg_value_delta = current_month_avg_value - prev_month_avg_value if prev_month_avg_value else 0 - return { - "title": _("Ongoing Deals"), - "value": current_month_deals, - "delta": delta_in_percentage, - "deltaSuffix": "%", - "tooltip": _("Total number of ongoing deals"), - } + return [ + { + "title": _("Ongoing Deals"), + "value": current_month_deals, + "delta": delta_in_percentage, + "deltaSuffix": "%", + "tooltip": _("Total number of ongoing deals"), + }, + { + "title": _("Avg Ongoing Deal Value"), + "value": current_month_avg_value, + "delta": avg_value_delta, + "prefix": get_base_currency_symbol(), + # "suffix": "K", + "tooltip": _("Average deal value of ongoing deals"), + }, + ] def get_won_deal_count(from_date, to_date, user="", conds="", return_result=False): """ - Get won deal count for the dashboard. + Get won deal count for the dashboard, and also calculate average deal value for won deals. """ diff = frappe.utils.date_diff(to_date, from_date) @@ -180,7 +213,23 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals {conds} THEN d.name ELSE NULL - END) as prev_month_deals + END) as prev_month_deals, + + AVG(CASE + WHEN d.closed_on >= %(from_date)s AND d.closed_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + AND s.type = 'Won' + {conds} + THEN d.deal_value * IFNULL(d.exchange_rate, 1) + ELSE NULL + END) as current_month_avg_value, + + AVG(CASE + WHEN d.closed_on >= %(prev_from_date)s AND d.closed_on < %(from_date)s + AND s.type = 'Won' + {conds} + THEN d.deal_value * IFNULL(d.exchange_rate, 1) + ELSE NULL + END) as prev_month_avg_value FROM `tabCRM Deal` d JOIN `tabCRM Deal Status` s ON d.status = s.name """, @@ -197,18 +246,31 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals current_month_deals = result[0].current_month_deals or 0 prev_month_deals = result[0].prev_month_deals or 0 + current_month_avg_value = result[0].current_month_avg_value or 0 + prev_month_avg_value = result[0].prev_month_avg_value or 0 delta_in_percentage = ( (current_month_deals - prev_month_deals) / prev_month_deals * 100 if prev_month_deals else 0 ) + avg_value_delta = current_month_avg_value - prev_month_avg_value if prev_month_avg_value else 0 - return { - "title": _("Won Deals"), - "value": current_month_deals, - "delta": delta_in_percentage, - "deltaSuffix": "%", - "tooltip": _("Total number of won deals"), - } + return [ + { + "title": _("Won Deals"), + "value": current_month_deals, + "delta": delta_in_percentage, + "deltaSuffix": "%", + "tooltip": _("Total number of won deals based on its closure date"), + }, + { + "title": _("Avg Won Deal Value"), + "value": current_month_avg_value, + "delta": avg_value_delta, + "prefix": get_base_currency_symbol(), + # "suffix": "K", + "tooltip": _("Average deal value of won deals"), + }, + ] def get_average_deal_value(from_date, to_date, user="", conds="", return_result=False): diff --git a/frontend/src/pages/Dashboard.vue b/frontend/src/pages/Dashboard.vue index 541bec75..8e5da6d5 100644 --- a/frontend/src/pages/Dashboard.vue +++ b/frontend/src/pages/Dashboard.vue @@ -339,7 +339,7 @@ const dealsBySalesperson = createResource({ return { data: r.data || [], title: __('Deals by Salesperson'), - subtitle: 'Number of deals and total value per salesperson', + subtitle: __('Number of deals and total value per salesperson'), xAxis: { title: __('Salesperson'), key: 'salesperson', From 61259f3d2e843f851b7d08e9089de55e01496e6b Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 13 Jul 2025 11:52:04 +0530 Subject: [PATCH 08/14] fix: avg time to close a deal number card --- crm/api/dashboard.py | 108 ++++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 43 deletions(-) diff --git a/crm/api/dashboard.py b/crm/api/dashboard.py index 0c5f78f2..c8984e1c 100644 --- a/crm/api/dashboard.py +++ b/crm/api/dashboard.py @@ -19,22 +19,24 @@ def get_number_card_data(from_date="", to_date="", user="", lead_conds="", deal_ if is_sales_user and not user: user = frappe.session.user - lead_count_data = get_lead_count(from_date, to_date, user, lead_conds) - ongoing_deal_count_data = get_ongoing_deal_count(from_date, to_date, user, deal_conds)[0] - average_ongoing_deal_value_data = get_ongoing_deal_count(from_date, to_date, user, deal_conds)[1] - won_deal_count_data = get_won_deal_count(from_date, to_date, user, deal_conds)[0] - average_won_deal_value_data = get_won_deal_count(from_date, to_date, user, deal_conds)[1] - average_deal_value_data = get_average_deal_value(from_date, to_date, user, deal_conds) - average_time_to_close_data = get_average_time_to_close(from_date, to_date, user, deal_conds) + lead_count = get_lead_count(from_date, to_date, user, lead_conds) + ongoing_deal_count = get_ongoing_deal_count(from_date, to_date, user, deal_conds)["count"] + average_ongoing_deal_value = get_ongoing_deal_count(from_date, to_date, user, deal_conds)["average"] + won_deal_count = get_won_deal_count(from_date, to_date, user, deal_conds)["count"] + average_won_deal_value = get_won_deal_count(from_date, to_date, user, deal_conds)["average"] + average_deal_value = get_average_deal_value(from_date, to_date, user, deal_conds) + average_time_to_close_a_lead = get_average_time_to_close(from_date, to_date, user, deal_conds)["lead"] + average_time_to_close_a_deal = get_average_time_to_close(from_date, to_date, user, deal_conds)["deal"] return [ - lead_count_data, - ongoing_deal_count_data, - average_ongoing_deal_value_data, - won_deal_count_data, - average_won_deal_value_data, - average_deal_value_data, - average_time_to_close_data, + lead_count, + ongoing_deal_count, + average_ongoing_deal_value, + won_deal_count, + average_won_deal_value, + average_deal_value, + average_time_to_close_a_lead, + average_time_to_close_a_deal, ] @@ -87,7 +89,7 @@ def get_lead_count(from_date, to_date, user="", conds="", return_result=False): ) return { - "title": _("Total Leads"), + "title": _("Total leads"), "value": current_month_leads, "delta": delta_in_percentage, "deltaSuffix": "%", @@ -165,23 +167,23 @@ def get_ongoing_deal_count(from_date, to_date, user="", conds="", return_result= ) avg_value_delta = current_month_avg_value - prev_month_avg_value if prev_month_avg_value else 0 - return [ - { - "title": _("Ongoing Deals"), + return { + "count": { + "title": _("Ongoing deals"), "value": current_month_deals, "delta": delta_in_percentage, "deltaSuffix": "%", "tooltip": _("Total number of ongoing deals"), }, - { - "title": _("Avg Ongoing Deal Value"), + "average": { + "title": _("Avg ongoing deal value"), "value": current_month_avg_value, "delta": avg_value_delta, "prefix": get_base_currency_symbol(), # "suffix": "K", "tooltip": _("Average deal value of ongoing deals"), }, - ] + } def get_won_deal_count(from_date, to_date, user="", conds="", return_result=False): @@ -254,23 +256,23 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals ) avg_value_delta = current_month_avg_value - prev_month_avg_value if prev_month_avg_value else 0 - return [ - { - "title": _("Won Deals"), + return { + "count": { + "title": _("Won deals"), "value": current_month_deals, "delta": delta_in_percentage, "deltaSuffix": "%", "tooltip": _("Total number of won deals based on its closure date"), }, - { - "title": _("Avg Won Deal Value"), + "average": { + "title": _("Avg won deal value"), "value": current_month_avg_value, "delta": avg_value_delta, "prefix": get_base_currency_symbol(), # "suffix": "K", "tooltip": _("Average deal value of won deals"), }, - ] + } def get_average_deal_value(from_date, to_date, user="", conds="", return_result=False): @@ -320,7 +322,7 @@ def get_average_deal_value(from_date, to_date, user="", conds="", return_result= delta = current_month_avg - prev_month_avg if prev_month_avg else 0 return { - "title": _("Avg Deal Value"), + "title": _("Avg deal value"), "value": current_month_avg, "tooltip": _("Average deal value of ongoing & won deals"), "prefix": get_base_currency_symbol(), @@ -333,6 +335,9 @@ def get_average_deal_value(from_date, to_date, user="", conds="", return_result= def get_average_time_to_close(from_date, to_date, user="", conds="", return_result=False): """ Get average time to close deals for the dashboard. + Returns both: + - Average time from lead creation to deal closure + - Average time from deal creation to deal closure """ diff = frappe.utils.date_diff(to_date, from_date) @@ -349,15 +354,17 @@ def get_average_time_to_close(from_date, to_date, user="", conds="", return_resu f""" SELECT AVG(CASE WHEN d.closed_on >= %(from_date)s AND d.closed_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) - AND s.type = 'Won' - THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_on) END) as current_avg, + THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_on) END) as current_avg_lead, AVG(CASE WHEN d.closed_on >= %(prev_from_date)s AND d.closed_on < %(prev_to_date)s - AND s.type = 'Won' - THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_on) END) as prev_avg + THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_on) END) as prev_avg_lead, + AVG(CASE WHEN d.closed_on >= %(from_date)s AND d.closed_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + THEN TIMESTAMPDIFF(DAY, d.creation, d.closed_on) END) as current_avg_deal, + AVG(CASE WHEN d.closed_on >= %(prev_from_date)s AND d.closed_on < %(prev_to_date)s + THEN TIMESTAMPDIFF(DAY, d.creation, d.closed_on) END) as prev_avg_deal FROM `tabCRM Deal` AS d JOIN `tabCRM Deal Status` s ON d.status = s.name LEFT JOIN `tabCRM Lead` l ON d.lead = l.name - WHERE d.closed_on IS NOT NULL + WHERE d.closed_on IS NOT NULL AND s.type = 'Won' {conds} """, { @@ -372,18 +379,33 @@ def get_average_time_to_close(from_date, to_date, user="", conds="", return_resu if return_result: return result - current_avg = result[0].current_avg or 0 - prev_avg = result[0].prev_avg or 0 - delta = current_avg - prev_avg if prev_avg else 0 + current_avg_lead = result[0].current_avg_lead or 0 + prev_avg_lead = result[0].prev_avg_lead or 0 + delta_lead = current_avg_lead - prev_avg_lead if prev_avg_lead else 0 + + current_avg_deal = result[0].current_avg_deal or 0 + prev_avg_deal = result[0].prev_avg_deal or 0 + delta_deal = current_avg_deal - prev_avg_deal if prev_avg_deal else 0 return { - "title": _("Avg Time to Close"), - "value": current_avg, - "tooltip": _("Average time taken from lead creation to deal closure"), - "suffix": " days", - "delta": delta, - "deltaSuffix": " days", - "negativeIsBetter": True, + "lead": { + "title": _("Avg time to close a lead"), + "value": current_avg_lead, + "tooltip": _("Average time taken from lead creation to deal closure"), + "suffix": " days", + "delta": delta_lead, + "deltaSuffix": " days", + "negativeIsBetter": True, + }, + "deal": { + "title": _("Avg time to close a deal"), + "value": current_avg_deal, + "tooltip": _("Average time taken from deal creation to deal closure"), + "suffix": " days", + "delta": delta_deal, + "deltaSuffix": " days", + "negativeIsBetter": True, + }, } From dcb1e475646e0f0c7096241fcb5bbeb997685230 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 13 Jul 2025 12:02:34 +0530 Subject: [PATCH 09/14] fix: added closed_date removed closed_on & added expected_deal_value & expected_closure_date field --- crm/fcrm/doctype/crm_deal/crm_deal.json | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json index 14880fbd..4ce81ec4 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.json +++ b/crm/fcrm/doctype/crm_deal/crm_deal.json @@ -18,10 +18,11 @@ "lost_notes", "section_break_jgpm", "probability", + "expected_deal_value", "deal_value", "column_break_kpxa", - "close_date", - "closed_on", + "expected_closure_date", + "closed_date", "contacts_tab", "contacts", "contact", @@ -95,11 +96,6 @@ "fieldtype": "Data", "label": "Website" }, - { - "fieldname": "close_date", - "fieldtype": "Date", - "label": "Close Date" - }, { "fieldname": "next_step", "fieldtype": "Data", @@ -412,23 +408,34 @@ "label": "Lost Notes", "mandatory_depends_on": "eval: doc.lost_reason == \"Other\"" }, - { - "fieldname": "closed_on", - "fieldtype": "Datetime", - "label": "Closed On" - }, { "default": "1", "description": "The rate used to convert the deal\u2019s currency to your crm's base currency (set in CRM Settings). It is set once when the currency is first added and doesn't change automatically.", "fieldname": "exchange_rate", "fieldtype": "Float", "label": "Exchange Rate" + }, + { + "fieldname": "expected_deal_value", + "fieldtype": "Currency", + "label": "Expected Deal Value", + "options": "currency" + }, + { + "fieldname": "expected_closure_date", + "fieldtype": "Date", + "label": "Expected Closure Date" + }, + { + "fieldname": "closed_date", + "fieldtype": "Date", + "label": "Closed Date" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-09 17:58:55.956639", + "modified": "2025-07-13 11:54:20.608489", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Deal", From 7fc26a5202b89476ec5e277601243cf8299ed10f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 13 Jul 2025 12:03:17 +0530 Subject: [PATCH 10/14] fix: used expected closed date & deal value for forecasting --- crm/api/dashboard.py | 13 +++++++------ .../doctype/crm_fields_layout/crm_fields_layout.py | 2 +- crm/fcrm/doctype/fcrm_settings/fcrm_settings.json | 4 ++-- crm/fcrm/doctype/fcrm_settings/fcrm_settings.py | 8 ++++---- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/crm/api/dashboard.py b/crm/api/dashboard.py index c8984e1c..d94172c7 100644 --- a/crm/api/dashboard.py +++ b/crm/api/dashboard.py @@ -615,23 +615,24 @@ def get_forecasted_revenue(user="", deal_conds=""): result = frappe.db.sql( f""" SELECT - DATE_FORMAT(d.close_date, '%Y-%m') AS month, + DATE_FORMAT(d.expected_closure_date, '%Y-%m') AS month, SUM( CASE - WHEN d.status = 'Lost' THEN d.deal_value * IFNULL(d.exchange_rate, 1) - ELSE d.deal_value * IFNULL(d.probability, 0) / 100 * IFNULL(d.exchange_rate, 1) -- forecasted + WHEN s.type = 'Lost' THEN d.expected_deal_value * IFNULL(d.exchange_rate, 1) + ELSE d.expected_deal_value * IFNULL(d.probability, 0) / 100 * IFNULL(d.exchange_rate, 1) -- forecasted END ) AS forecasted, SUM( CASE - WHEN d.status = 'Won' THEN d.deal_value * IFNULL(d.exchange_rate, 1) -- actual + WHEN s.type = 'Won' THEN d.deal_value * IFNULL(d.exchange_rate, 1) -- actual ELSE 0 END ) AS actual FROM `tabCRM Deal` AS d - WHERE d.close_date >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH) + JOIN `tabCRM Deal Status` s ON d.status = s.name + WHERE d.expected_closure_date >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH) {deal_conds} - GROUP BY DATE_FORMAT(d.close_date, '%Y-%m') + GROUP BY DATE_FORMAT(d.expected_closure_date, '%Y-%m') ORDER BY month """, as_dict=True, diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py index 34a361e9..8a714d38 100644 --- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py +++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py @@ -160,7 +160,7 @@ def add_forecasting_section(layout, doctype): "columns": [ { "name": "column_" + str(random_string(4)), - "fields": ["close_date", "probability", "deal_value"], + "fields": ["expected_closure_date", "probability", "expected_deal_value"], } ], }, diff --git a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json index 00907793..635c02d3 100644 --- a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json +++ b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json @@ -61,7 +61,7 @@ }, { "default": "0", - "description": "It will make deal's \"Close Date\" & \"Deal Value\" mandatory to get accurate forecasting insights", + "description": "It will make deal's \"Expected Closure Date\" & \"Expected Deal Value\" mandatory to get accurate forecasting insights", "fieldname": "enable_forecasting", "fieldtype": "Check", "label": "Enable Forecasting" @@ -77,7 +77,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-07-10 16:35:25.030011", + "modified": "2025-07-13 11:58:34.857638", "modified_by": "Administrator", "module": "FCRM", "name": "FCRM Settings", diff --git a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py index 0b4e2932..7897bb1a 100644 --- a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py +++ b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py @@ -38,24 +38,24 @@ class FCRMSettings(Document): delete_property_setter( "CRM Deal", "reqd", - "close_date", + "expected_closure_date", ) delete_property_setter( "CRM Deal", "reqd", - "deal_value", + "expected_deal_value", ) else: make_property_setter( "CRM Deal", - "close_date", + "expected_closure_date", "reqd", 1 if self.enable_forecasting else 0, "Check", ) make_property_setter( "CRM Deal", - "deal_value", + "expected_deal_value", "reqd", 1 if self.enable_forecasting else 0, "Check", From f82019e510612bd9803f9947240d155479d5d665 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 13 Jul 2025 12:11:50 +0530 Subject: [PATCH 11/14] fix: used closed_date instead of closed_on and set closed_date if status type is Won --- crm/api/dashboard.py | 26 +++++++++++++------------- crm/fcrm/doctype/crm_deal/crm_deal.py | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crm/api/dashboard.py b/crm/api/dashboard.py index d94172c7..3a5d31b7 100644 --- a/crm/api/dashboard.py +++ b/crm/api/dashboard.py @@ -202,7 +202,7 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals f""" SELECT COUNT(CASE - WHEN d.closed_on >= %(from_date)s AND d.closed_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + WHEN d.closed_date >= %(from_date)s AND d.closed_date < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) AND s.type = 'Won' {conds} THEN d.name @@ -210,7 +210,7 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals END) as current_month_deals, COUNT(CASE - WHEN d.closed_on >= %(prev_from_date)s AND d.closed_on < %(from_date)s + WHEN d.closed_date >= %(prev_from_date)s AND d.closed_date < %(from_date)s AND s.type = 'Won' {conds} THEN d.name @@ -218,7 +218,7 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals END) as prev_month_deals, AVG(CASE - WHEN d.closed_on >= %(from_date)s AND d.closed_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + WHEN d.closed_date >= %(from_date)s AND d.closed_date < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) AND s.type = 'Won' {conds} THEN d.deal_value * IFNULL(d.exchange_rate, 1) @@ -226,7 +226,7 @@ def get_won_deal_count(from_date, to_date, user="", conds="", return_result=Fals END) as current_month_avg_value, AVG(CASE - WHEN d.closed_on >= %(prev_from_date)s AND d.closed_on < %(from_date)s + WHEN d.closed_date >= %(prev_from_date)s AND d.closed_date < %(from_date)s AND s.type = 'Won' {conds} THEN d.deal_value * IFNULL(d.exchange_rate, 1) @@ -353,18 +353,18 @@ def get_average_time_to_close(from_date, to_date, user="", conds="", return_resu result = frappe.db.sql( f""" SELECT - AVG(CASE WHEN d.closed_on >= %(from_date)s AND d.closed_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) - THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_on) END) as current_avg_lead, - AVG(CASE WHEN d.closed_on >= %(prev_from_date)s AND d.closed_on < %(prev_to_date)s - THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_on) END) as prev_avg_lead, - AVG(CASE WHEN d.closed_on >= %(from_date)s AND d.closed_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) - THEN TIMESTAMPDIFF(DAY, d.creation, d.closed_on) END) as current_avg_deal, - AVG(CASE WHEN d.closed_on >= %(prev_from_date)s AND d.closed_on < %(prev_to_date)s - THEN TIMESTAMPDIFF(DAY, d.creation, d.closed_on) END) as prev_avg_deal + AVG(CASE WHEN d.closed_date >= %(from_date)s AND d.closed_date < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_date) END) as current_avg_lead, + AVG(CASE WHEN d.closed_date >= %(prev_from_date)s AND d.closed_date < %(prev_to_date)s + THEN TIMESTAMPDIFF(DAY, COALESCE(l.creation, d.creation), d.closed_date) END) as prev_avg_lead, + AVG(CASE WHEN d.closed_date >= %(from_date)s AND d.closed_date < DATE_ADD(%(to_date)s, INTERVAL 1 DAY) + THEN TIMESTAMPDIFF(DAY, d.creation, d.closed_date) END) as current_avg_deal, + AVG(CASE WHEN d.closed_date >= %(prev_from_date)s AND d.closed_date < %(prev_to_date)s + THEN TIMESTAMPDIFF(DAY, d.creation, d.closed_date) END) as prev_avg_deal FROM `tabCRM Deal` AS d JOIN `tabCRM Deal Status` s ON d.status = s.name LEFT JOIN `tabCRM Lead` l ON d.lead = l.name - WHERE d.closed_on IS NOT NULL AND s.type = 'Won' + WHERE d.closed_date IS NOT NULL AND s.type = 'Won' {conds} """, { diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py index c3a8488d..f46f6475 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.py +++ b/crm/fcrm/doctype/crm_deal/crm_deal.py @@ -25,8 +25,8 @@ class CRMDeal(Document): self.assign_agent(self.deal_owner) if self.has_value_changed("status"): add_status_change_log(self) - if self.status == "Won": - self.closed_on = frappe.utils.now_datetime() + if frappe.db.get_value("CRM Deal Status", self.status, "type") == "Won": + self.closed_date = frappe.utils.nowdate() self.validate_forcasting_fields() self.validate_lost_reason() self.update_exchange_rate() From de85ccfc51fdf7ac5c87084baccd602930102504 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 13 Jul 2025 12:35:16 +0530 Subject: [PATCH 12/14] fix: added deals by ongoing & won stages bar --- crm/api/dashboard.py | 4 +- frontend/src/pages/Dashboard.vue | 66 ++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/crm/api/dashboard.py b/crm/api/dashboard.py index 3a5d31b7..94fbd623 100644 --- a/crm/api/dashboard.py +++ b/crm/api/dashboard.py @@ -734,8 +734,10 @@ def get_deals_by_stage(from_date="", to_date="", user="", deal_conds=""): f""" SELECT d.status AS stage, - COUNT(*) AS count + COUNT(*) AS count, + s.type AS status_type FROM `tabCRM Deal` AS d + JOIN `tabCRM Deal Status` s ON d.status = s.name WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s {deal_conds} GROUP BY d.status diff --git a/frontend/src/pages/Dashboard.vue b/frontend/src/pages/Dashboard.vue index 8e5da6d5..e118a694 100644 --- a/frontend/src/pages/Dashboard.vue +++ b/frontend/src/pages/Dashboard.vue @@ -59,7 +59,7 @@ doctype="User" :filters="{ name: ['in', users.data.crmUsers?.map((u) => u.name)] }" @change="(v) => updateFilter('user', v)" - :placeholder="__('Sales User')" + :placeholder="__('Sales user')" :hideMe="true" >