fix: added filters and translated titles
This commit is contained in:
parent
9d4106cd81
commit
4b12918ba5
@ -3,16 +3,19 @@ from frappe import _
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_number_card_data(from_date="", to_date="", lead_conds="", deal_conds=""):
|
def get_number_card_data(from_date="", to_date="", user="", lead_conds="", deal_conds=""):
|
||||||
"""
|
"""
|
||||||
Get number card data for the dashboard.
|
Get number card data for the dashboard.
|
||||||
"""
|
"""
|
||||||
|
if not from_date or not to_date:
|
||||||
|
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||||
|
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||||
|
|
||||||
lead_chart_data = get_lead_count(from_date, to_date, lead_conds)
|
lead_chart_data = get_lead_count(from_date, to_date, user, lead_conds)
|
||||||
deal_chart_data = get_deal_count(from_date, to_date, deal_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, 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, 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, deal_conds)
|
get_average_time_to_close_data = get_average_time_to_close(from_date, to_date, user, deal_conds)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
lead_chart_data,
|
lead_chart_data,
|
||||||
@ -23,19 +26,18 @@ def get_number_card_data(from_date="", to_date="", lead_conds="", deal_conds="")
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_lead_count(from_date, to_date, conds="", return_result=False):
|
def get_lead_count(from_date, to_date, user="", conds="", return_result=False):
|
||||||
"""
|
"""
|
||||||
Get lead count for the dashboard.
|
Get lead count for the dashboard.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not from_date or not to_date:
|
|
||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
|
||||||
|
|
||||||
diff = frappe.utils.date_diff(to_date, from_date)
|
diff = frappe.utils.date_diff(to_date, from_date)
|
||||||
if diff == 0:
|
if diff == 0:
|
||||||
diff = 1
|
diff = 1
|
||||||
|
|
||||||
|
if user:
|
||||||
|
conds += f" AND lead_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
@ -73,28 +75,27 @@ def get_lead_count(from_date, to_date, conds="", return_result=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": "Total Leads",
|
"title": _("Total Leads"),
|
||||||
"value": current_month_leads,
|
"value": current_month_leads,
|
||||||
"delta": delta_in_percentage,
|
"delta": delta_in_percentage,
|
||||||
"deltaSuffix": "%",
|
"deltaSuffix": "%",
|
||||||
"negativeIsBetter": False,
|
"negativeIsBetter": False,
|
||||||
"tooltip": "Total number of leads created",
|
"tooltip": _("Total number of leads created"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_deal_count(from_date, to_date, conds="", return_result=False):
|
def get_deal_count(from_date, to_date, user="", conds="", return_result=False):
|
||||||
"""
|
"""
|
||||||
Get deal count for the dashboard.
|
Get deal count for the dashboard.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not from_date or not to_date:
|
|
||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
|
||||||
|
|
||||||
diff = frappe.utils.date_diff(to_date, from_date)
|
diff = frappe.utils.date_diff(to_date, from_date)
|
||||||
if diff == 0:
|
if diff == 0:
|
||||||
diff = 1
|
diff = 1
|
||||||
|
|
||||||
|
if user:
|
||||||
|
conds += f" AND deal_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
@ -132,28 +133,27 @@ def get_deal_count(from_date, to_date, conds="", return_result=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": "Total Deals",
|
"title": _("Total Deals"),
|
||||||
"value": current_month_deals,
|
"value": current_month_deals,
|
||||||
"delta": delta_in_percentage,
|
"delta": delta_in_percentage,
|
||||||
"deltaSuffix": "%",
|
"deltaSuffix": "%",
|
||||||
"negativeIsBetter": False,
|
"negativeIsBetter": False,
|
||||||
"tooltip": "Total number of deals created",
|
"tooltip": _("Total number of deals created"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_won_deal_count(from_date, to_date, conds="", return_result=False):
|
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not from_date or not to_date:
|
|
||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
|
||||||
|
|
||||||
diff = frappe.utils.date_diff(to_date, from_date)
|
diff = frappe.utils.date_diff(to_date, from_date)
|
||||||
if diff == 0:
|
if diff == 0:
|
||||||
diff = 1
|
diff = 1
|
||||||
|
|
||||||
|
if user:
|
||||||
|
conds += f" AND deal_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
@ -191,28 +191,27 @@ def get_won_deal_count(from_date, to_date, conds="", return_result=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": "Won Deals",
|
"title": _("Won Deals"),
|
||||||
"value": current_month_deals,
|
"value": current_month_deals,
|
||||||
"delta": delta_in_percentage,
|
"delta": delta_in_percentage,
|
||||||
"deltaSuffix": "%",
|
"deltaSuffix": "%",
|
||||||
"negativeIsBetter": False,
|
"negativeIsBetter": False,
|
||||||
"tooltip": "Total number of deals created",
|
"tooltip": _("Total number of deals created"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_average_deal_value(from_date, to_date, conds="", return_result=False):
|
def get_average_deal_value(from_date, to_date, user="", conds="", return_result=False):
|
||||||
"""
|
"""
|
||||||
Get average deal value for the dashboard.
|
Get average deal value for the dashboard.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not from_date or not to_date:
|
|
||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
|
||||||
|
|
||||||
diff = frappe.utils.date_diff(to_date, from_date)
|
diff = frappe.utils.date_diff(to_date, from_date)
|
||||||
if diff == 0:
|
if diff == 0:
|
||||||
diff = 1
|
diff = 1
|
||||||
|
|
||||||
|
if user:
|
||||||
|
conds += f" AND deal_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
@ -245,9 +244,9 @@ def get_average_deal_value(from_date, to_date, conds="", return_result=False):
|
|||||||
delta = current_month_avg - prev_month_avg if prev_month_avg else 0
|
delta = current_month_avg - prev_month_avg if prev_month_avg else 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": "Avg Deal Value",
|
"title": _("Avg Deal Value"),
|
||||||
"value": current_month_avg,
|
"value": current_month_avg,
|
||||||
"tooltip": "Average value of deals created",
|
"tooltip": _("Average value of deals created"),
|
||||||
# "prefix": "$",
|
# "prefix": "$",
|
||||||
# "suffix": "K",
|
# "suffix": "K",
|
||||||
"delta": delta,
|
"delta": delta,
|
||||||
@ -255,19 +254,18 @@ def get_average_deal_value(from_date, to_date, conds="", return_result=False):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_average_time_to_close(from_date, to_date, conds="", return_result=False):
|
def get_average_time_to_close(from_date, to_date, user="", conds="", return_result=False):
|
||||||
"""
|
"""
|
||||||
Get average time to close deals for the dashboard.
|
Get average time to close deals for the dashboard.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not from_date or not to_date:
|
|
||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
|
||||||
|
|
||||||
diff = frappe.utils.date_diff(to_date, from_date)
|
diff = frappe.utils.date_diff(to_date, from_date)
|
||||||
if diff == 0:
|
if diff == 0:
|
||||||
diff = 1
|
diff = 1
|
||||||
|
|
||||||
|
if user:
|
||||||
|
conds += f" AND d.deal_owner = '{user}'"
|
||||||
|
|
||||||
prev_from_date = frappe.utils.add_days(from_date, -diff)
|
prev_from_date = frappe.utils.add_days(from_date, -diff)
|
||||||
prev_to_date = from_date
|
prev_to_date = from_date
|
||||||
|
|
||||||
@ -300,9 +298,9 @@ def get_average_time_to_close(from_date, to_date, conds="", return_result=False)
|
|||||||
delta = current_avg - prev_avg if prev_avg else 0
|
delta = current_avg - prev_avg if prev_avg else 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": "Avg Time to Close",
|
"title": _("Avg Time to Close"),
|
||||||
"value": current_avg,
|
"value": current_avg,
|
||||||
"tooltip": "Average time taken to close deals",
|
"tooltip": _("Average time taken to close deals"),
|
||||||
"suffix": " days",
|
"suffix": " days",
|
||||||
"delta": delta,
|
"delta": delta,
|
||||||
"deltaSuffix": " days",
|
"deltaSuffix": " days",
|
||||||
@ -311,7 +309,7 @@ def get_average_time_to_close(from_date, to_date, conds="", return_result=False)
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_sales_trend_data(from_date="", to_date="", lead_conds="", deal_conds=""):
|
def get_sales_trend_data(from_date="", to_date="", user="", lead_conds="", deal_conds=""):
|
||||||
"""
|
"""
|
||||||
Get sales trend data for the dashboard.
|
Get sales trend data for the dashboard.
|
||||||
[
|
[
|
||||||
@ -325,8 +323,12 @@ def get_sales_trend_data(from_date="", to_date="", lead_conds="", deal_conds="")
|
|||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||||
|
|
||||||
|
if user:
|
||||||
|
lead_conds += f" AND lead_owner = '{user}'"
|
||||||
|
deal_conds += f" AND deal_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
DATE_FORMAT(date, '%%Y-%%m-%%d') AS date,
|
DATE_FORMAT(date, '%%Y-%%m-%%d') AS date,
|
||||||
SUM(leads) AS leads,
|
SUM(leads) AS leads,
|
||||||
@ -340,7 +342,7 @@ def get_sales_trend_data(from_date="", to_date="", lead_conds="", deal_conds="")
|
|||||||
0 AS won_deals
|
0 AS won_deals
|
||||||
FROM `tabCRM Lead`
|
FROM `tabCRM Lead`
|
||||||
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
||||||
%(lead_conds)s
|
{lead_conds}
|
||||||
GROUP BY DATE(creation)
|
GROUP BY DATE(creation)
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
@ -352,13 +354,13 @@ def get_sales_trend_data(from_date="", to_date="", lead_conds="", deal_conds="")
|
|||||||
SUM(CASE WHEN status = 'Won' THEN 1 ELSE 0 END) AS won_deals
|
SUM(CASE WHEN status = 'Won' THEN 1 ELSE 0 END) AS won_deals
|
||||||
FROM `tabCRM Deal`
|
FROM `tabCRM Deal`
|
||||||
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
||||||
%(deal_conds)s
|
{deal_conds}
|
||||||
GROUP BY DATE(creation)
|
GROUP BY DATE(creation)
|
||||||
) AS daily
|
) AS daily
|
||||||
GROUP BY date
|
GROUP BY date
|
||||||
ORDER BY date
|
ORDER BY date
|
||||||
""",
|
""",
|
||||||
{"from": from_date, "to": to_date, "lead_conds": lead_conds, "deal_conds": deal_conds},
|
{"from": from_date, "to": to_date},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -374,7 +376,7 @@ def get_sales_trend_data(from_date="", to_date="", lead_conds="", deal_conds="")
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_deals_by_salesperson(from_date="", to_date="", deal_conds=""):
|
def get_deals_by_salesperson(from_date="", to_date="", user="", deal_conds=""):
|
||||||
"""
|
"""
|
||||||
Get deal data by salesperson for the dashboard.
|
Get deal data by salesperson for the dashboard.
|
||||||
[
|
[
|
||||||
@ -388,8 +390,11 @@ def get_deals_by_salesperson(from_date="", to_date="", deal_conds=""):
|
|||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||||
|
|
||||||
|
if user:
|
||||||
|
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
IFNULL(u.full_name, d.deal_owner) AS salesperson,
|
IFNULL(u.full_name, d.deal_owner) AS salesperson,
|
||||||
COUNT(*) AS deals,
|
COUNT(*) AS deals,
|
||||||
@ -397,11 +402,11 @@ def get_deals_by_salesperson(from_date="", to_date="", deal_conds=""):
|
|||||||
FROM `tabCRM Deal` AS d
|
FROM `tabCRM Deal` AS d
|
||||||
LEFT JOIN `tabUser` AS u ON u.name = d.deal_owner
|
LEFT JOIN `tabUser` AS u ON u.name = d.deal_owner
|
||||||
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
||||||
%(deal_conds)s
|
{deal_conds}
|
||||||
GROUP BY d.deal_owner
|
GROUP BY d.deal_owner
|
||||||
ORDER BY value DESC
|
ORDER BY value DESC
|
||||||
""",
|
""",
|
||||||
{"from": from_date, "to": to_date, "deal_conds": deal_conds},
|
{"from": from_date, "to": to_date},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -409,7 +414,7 @@ def get_deals_by_salesperson(from_date="", to_date="", deal_conds=""):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_deals_by_territory(from_date="", to_date="", deal_conds=""):
|
def get_deals_by_territory(from_date="", to_date="", user="", deal_conds=""):
|
||||||
"""
|
"""
|
||||||
Get deal data by territory for the dashboard.
|
Get deal data by territory for the dashboard.
|
||||||
[
|
[
|
||||||
@ -423,19 +428,22 @@ def get_deals_by_territory(from_date="", to_date="", deal_conds=""):
|
|||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||||
|
|
||||||
|
if user:
|
||||||
|
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
IFNULL(d.territory, 'Empty') AS territory,
|
IFNULL(d.territory, 'Empty') AS territory,
|
||||||
COUNT(*) AS deals,
|
COUNT(*) AS deals,
|
||||||
SUM(COALESCE(d.deal_value, 0)) AS value
|
SUM(COALESCE(d.deal_value, 0)) AS value
|
||||||
FROM `tabCRM Deal` AS d
|
FROM `tabCRM Deal` AS d
|
||||||
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
||||||
%(deal_conds)s
|
{deal_conds}
|
||||||
GROUP BY d.territory
|
GROUP BY d.territory
|
||||||
ORDER BY value DESC
|
ORDER BY value DESC
|
||||||
""",
|
""",
|
||||||
{"from": from_date, "to": to_date, "deal_conds": deal_conds},
|
{"from": from_date, "to": to_date},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -443,7 +451,7 @@ def get_deals_by_territory(from_date="", to_date="", deal_conds=""):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_lost_deal_reasons(from_date="", to_date="", deal_conds=""):
|
def get_lost_deal_reasons(from_date="", to_date="", user="", deal_conds=""):
|
||||||
"""
|
"""
|
||||||
Get lost deal reasons for the dashboard.
|
Get lost deal reasons for the dashboard.
|
||||||
[
|
[
|
||||||
@ -452,23 +460,27 @@ def get_lost_deal_reasons(from_date="", to_date="", deal_conds=""):
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not from_date or not to_date:
|
if not from_date or not to_date:
|
||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||||
|
|
||||||
|
if user:
|
||||||
|
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
d.lost_reason AS reason,
|
d.lost_reason AS reason,
|
||||||
COUNT(*) AS count
|
COUNT(*) AS count
|
||||||
FROM `tabCRM Deal` AS d
|
FROM `tabCRM Deal` AS d
|
||||||
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s AND d.status = 'Lost'
|
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s AND d.status = 'Lost'
|
||||||
%(deal_conds)s
|
{deal_conds}
|
||||||
GROUP BY d.lost_reason
|
GROUP BY d.lost_reason
|
||||||
HAVING reason IS NOT NULL AND reason != ''
|
HAVING reason IS NOT NULL AND reason != ''
|
||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
""",
|
""",
|
||||||
{"from": from_date, "to": to_date, "deal_conds": deal_conds},
|
{"from": from_date, "to": to_date},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -476,7 +488,7 @@ def get_lost_deal_reasons(from_date="", to_date="", deal_conds=""):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_forecasted_revenue(deal_conds=""):
|
def get_forecasted_revenue(user="", deal_conds=""):
|
||||||
"""
|
"""
|
||||||
Get forecasted revenue for the dashboard.
|
Get forecasted revenue for the dashboard.
|
||||||
[
|
[
|
||||||
@ -488,6 +500,9 @@ def get_forecasted_revenue(deal_conds=""):
|
|||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if user:
|
||||||
|
deal_conds += f" AND deal_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
@ -523,7 +538,7 @@ def get_forecasted_revenue(deal_conds=""):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_funnel_conversion_data(from_date="", to_date="", lead_conds="", deal_conds=""):
|
def get_funnel_conversion_data(from_date="", to_date="", user="", lead_conds="", deal_conds=""):
|
||||||
"""
|
"""
|
||||||
Get funnel conversion data for the dashboard.
|
Get funnel conversion data for the dashboard.
|
||||||
[
|
[
|
||||||
@ -540,16 +555,20 @@ def get_funnel_conversion_data(from_date="", to_date="", lead_conds="", deal_con
|
|||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||||
|
|
||||||
|
if user:
|
||||||
|
lead_conds += f" AND lead_owner = '{user}'"
|
||||||
|
deal_conds += f" AND deal_owner = '{user}'"
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
# Get total leads
|
# Get total leads
|
||||||
total_leads = frappe.db.sql(
|
total_leads = frappe.db.sql(
|
||||||
""" SELECT COUNT(*) AS count
|
f""" SELECT COUNT(*) AS count
|
||||||
FROM `tabCRM Lead`
|
FROM `tabCRM Lead`
|
||||||
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
||||||
%(lead_conds)s
|
{lead_conds}
|
||||||
""",
|
""",
|
||||||
{"from": from_date, "to": to_date, "lead_conds": lead_conds},
|
{"from": from_date, "to": to_date},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
total_leads_count = total_leads[0].count if total_leads else 0
|
total_leads_count = total_leads[0].count if total_leads else 0
|
||||||
@ -580,7 +599,7 @@ def get_funnel_conversion_data(from_date="", to_date="", lead_conds="", deal_con
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_deals_by_stage(from_date="", to_date="", deal_conds=""):
|
def get_deals_by_stage(from_date="", to_date="", user="", deal_conds=""):
|
||||||
"""
|
"""
|
||||||
Get deal data by stage for the dashboard.
|
Get deal data by stage for the dashboard.
|
||||||
[
|
[
|
||||||
@ -594,18 +613,21 @@ def get_deals_by_stage(from_date="", to_date="", deal_conds=""):
|
|||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||||
|
|
||||||
|
if user:
|
||||||
|
deal_conds += f" AND d.deal_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
d.status AS stage,
|
d.status AS stage,
|
||||||
COUNT(*) AS count
|
COUNT(*) AS count
|
||||||
FROM `tabCRM Deal` AS d
|
FROM `tabCRM Deal` AS d
|
||||||
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
||||||
%(deal_conds)s
|
{deal_conds}
|
||||||
GROUP BY d.status
|
GROUP BY d.status
|
||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
""",
|
""",
|
||||||
{"from": from_date, "to": to_date, "deal_conds": deal_conds},
|
{"from": from_date, "to": to_date},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -613,7 +635,7 @@ def get_deals_by_stage(from_date="", to_date="", deal_conds=""):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_leads_by_source(from_date="", to_date="", lead_conds=""):
|
def get_leads_by_source(from_date="", to_date="", user="", lead_conds=""):
|
||||||
"""
|
"""
|
||||||
Get lead data by source for the dashboard.
|
Get lead data by source for the dashboard.
|
||||||
[
|
[
|
||||||
@ -627,18 +649,21 @@ def get_leads_by_source(from_date="", to_date="", lead_conds=""):
|
|||||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||||
|
|
||||||
|
if user:
|
||||||
|
lead_conds += f" AND lead_owner = '{user}'"
|
||||||
|
|
||||||
result = frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
IFNULL(source, 'Empty') AS source,
|
IFNULL(source, 'Empty') AS source,
|
||||||
COUNT(*) AS count
|
COUNT(*) AS count
|
||||||
FROM `tabCRM Lead`
|
FROM `tabCRM Lead`
|
||||||
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
||||||
%(lead_conds)s
|
{lead_conds}
|
||||||
GROUP BY source
|
GROUP BY source
|
||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
""",
|
""",
|
||||||
{"from": from_date, "to": to_date, "lead_conds": lead_conds},
|
{"from": from_date, "to": to_date},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
>
|
>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<button
|
<button
|
||||||
class="relative flex h-7 w-full items-center justify-between gap-2 rounded bg-surface-gray-2 px-2 py-1 transition-colors hover:bg-surface-gray-3 border border-transparent focus:border-outline-gray-4 focus:outline-none focus:ring-2 focus:ring-outline-gray-3"
|
class="relative flex h-7 w-full items-center justify-between gap-2 rounded px-2 py-1 transition-colors"
|
||||||
:class="inputClasses"
|
:class="inputClasses"
|
||||||
@click="() => togglePopover()"
|
@click="() => togglePopover()"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -6,42 +6,120 @@
|
|||||||
</template>
|
</template>
|
||||||
</LayoutHeader>
|
</LayoutHeader>
|
||||||
|
|
||||||
<div class="p-5 w-full overflow-y-scroll">
|
<div class="p-5 pb-3 flex items-center gap-4">
|
||||||
<div
|
<Dropdown
|
||||||
v-if="!numberCards.loading"
|
v-if="!showDatePicker"
|
||||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4"
|
:options="options"
|
||||||
|
class="form-control"
|
||||||
|
v-model="preset"
|
||||||
|
:placeholder="__('Select Range')"
|
||||||
|
:button="{
|
||||||
|
label: __(preset),
|
||||||
|
class:
|
||||||
|
'!w-full justify-start [&>span]:mr-auto [&>svg]:text-ink-gray-5 ',
|
||||||
|
variant: 'outline',
|
||||||
|
iconRight: 'chevron-down',
|
||||||
|
iconLeft: 'calendar',
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<NumberChart
|
<template #prefix>
|
||||||
v-for="(config, index) in numberCards.data"
|
<LucideCalendar class="size-4 text-ink-gray-5 mr-2" />
|
||||||
:key="index"
|
</template>
|
||||||
class="border rounded-md"
|
</Dropdown>
|
||||||
:config="config"
|
<DateRangePicker
|
||||||
/>
|
v-else
|
||||||
</div>
|
class="!w-48"
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4">
|
ref="datePickerRef"
|
||||||
<div v-if="salesTrend.data" class="border rounded-md min-h-80">
|
:value="filters.period"
|
||||||
<AxisChart :config="salesTrend.data" />
|
variant="outline"
|
||||||
|
:placeholder="__('Period')"
|
||||||
|
@change="
|
||||||
|
(v) =>
|
||||||
|
updateFilter('period', v, () => {
|
||||||
|
showDatePicker = false
|
||||||
|
if (!v) {
|
||||||
|
filters.period = getLastXDays()
|
||||||
|
preset = 'Last 30 Days'
|
||||||
|
} else {
|
||||||
|
preset = formatter(v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
"
|
||||||
|
:formatter="formatRange"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<LucideCalendar class="size-4 text-ink-gray-5 mr-2" />
|
||||||
|
</template>
|
||||||
|
</DateRangePicker>
|
||||||
|
<Link
|
||||||
|
class="form-control w-48"
|
||||||
|
variant="outline"
|
||||||
|
:value="filters.user && getUser(filters.user).full_name"
|
||||||
|
doctype="User"
|
||||||
|
:filters="{ name: ['in', users.data.crmUsers?.map((u) => u.name)] }"
|
||||||
|
@change="(v) => updateFilter('user', v)"
|
||||||
|
:placeholder="__('Sales User')"
|
||||||
|
:hideMe="true"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<UserAvatar
|
||||||
|
v-if="filters.user"
|
||||||
|
class="mr-2"
|
||||||
|
:user="filters.user"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #item-prefix="{ option }">
|
||||||
|
<UserAvatar class="mr-2" :user="option.value" size="sm" />
|
||||||
|
</template>
|
||||||
|
<template #item-label="{ option }">
|
||||||
|
<Tooltip :text="option.value">
|
||||||
|
<div class="cursor-pointer">
|
||||||
|
{{ getUser(option.value).full_name }}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</template>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-5 pt-2 w-full overflow-y-scroll">
|
||||||
|
<div class="transition-all animate-fade-in duration-300">
|
||||||
|
<div
|
||||||
|
v-if="!numberCards.loading"
|
||||||
|
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4"
|
||||||
|
>
|
||||||
|
<NumberChart
|
||||||
|
v-for="(config, index) in numberCards.data"
|
||||||
|
:key="index"
|
||||||
|
class="border rounded-md"
|
||||||
|
:config="config"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="forecastedRevenue.data" class="border rounded-md min-h-80">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4">
|
||||||
<AxisChart :config="forecastedRevenue.data" />
|
<div v-if="salesTrend.data" class="border rounded-md min-h-80">
|
||||||
</div>
|
<AxisChart :config="salesTrend.data" />
|
||||||
<div v-if="funnelConversion.data" class="border rounded-md min-h-80">
|
</div>
|
||||||
<AxisChart :config="funnelConversion.data" />
|
<div v-if="forecastedRevenue.data" class="border rounded-md min-h-80">
|
||||||
</div>
|
<AxisChart :config="forecastedRevenue.data" />
|
||||||
<div v-if="lostDealReasons.data" class="border rounded-md">
|
</div>
|
||||||
<AxisChart :config="lostDealReasons.data" />
|
<div v-if="funnelConversion.data" class="border rounded-md min-h-80">
|
||||||
</div>
|
<AxisChart :config="funnelConversion.data" />
|
||||||
<div v-if="dealsByTerritory.data" class="border rounded-md">
|
</div>
|
||||||
<AxisChart :config="dealsByTerritory.data" />
|
<div v-if="lostDealReasons.data" class="border rounded-md">
|
||||||
</div>
|
<AxisChart :config="lostDealReasons.data" />
|
||||||
<div v-if="dealsBySalesperson.data" class="border rounded-md">
|
</div>
|
||||||
<AxisChart :config="dealsBySalesperson.data" />
|
<div v-if="dealsByTerritory.data" class="border rounded-md">
|
||||||
</div>
|
<AxisChart :config="dealsByTerritory.data" />
|
||||||
<div v-if="dealsByStage.data" class="border rounded-md">
|
</div>
|
||||||
<DonutChart :config="dealsByStage.data" />
|
<div v-if="dealsBySalesperson.data" class="border rounded-md">
|
||||||
</div>
|
<AxisChart :config="dealsBySalesperson.data" />
|
||||||
<div v-if="leadsBySource.data" class="border rounded-md">
|
</div>
|
||||||
<DonutChart :config="leadsBySource.data" />
|
<div v-if="dealsByStage.data" class="border rounded-md">
|
||||||
|
<DonutChart :config="dealsByStage.data" />
|
||||||
|
</div>
|
||||||
|
<div v-if="leadsBySource.data" class="border rounded-md">
|
||||||
|
<DonutChart :config="leadsBySource.data" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -49,39 +127,150 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||||
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
import { usersStore } from '@/stores/users'
|
||||||
|
import { getLastXDays, formatter, formatRange } from '@/utils/dashboard'
|
||||||
import {
|
import {
|
||||||
AxisChart,
|
AxisChart,
|
||||||
DonutChart,
|
DonutChart,
|
||||||
NumberChart,
|
NumberChart,
|
||||||
usePageMeta,
|
usePageMeta,
|
||||||
createResource,
|
createResource,
|
||||||
|
DateRangePicker,
|
||||||
|
Dropdown,
|
||||||
|
Tooltip,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
|
|
||||||
|
const { users, getUser } = usersStore()
|
||||||
|
|
||||||
|
const showDatePicker = ref(false)
|
||||||
|
const datePickerRef = ref(null)
|
||||||
|
const preset = ref('Last 30 Days')
|
||||||
|
|
||||||
|
const filters = reactive({
|
||||||
|
period: getLastXDays(),
|
||||||
|
user: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const fromDate = computed(() => {
|
||||||
|
if (!filters.period) return null
|
||||||
|
return filters.period.split(',')[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
const toDate = computed(() => {
|
||||||
|
if (!filters.period) return null
|
||||||
|
return filters.period.split(',')[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateFilter(key: string, value: any, callback?: () => void) {
|
||||||
|
filters[key] = value
|
||||||
|
callback?.()
|
||||||
|
reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
numberCards.reload()
|
||||||
|
salesTrend.reload()
|
||||||
|
funnelConversion.reload()
|
||||||
|
dealsBySalesperson.reload()
|
||||||
|
dealsByTerritory.reload()
|
||||||
|
lostDealReasons.reload()
|
||||||
|
forecastedRevenue.reload()
|
||||||
|
dealsByStage.reload()
|
||||||
|
leadsBySource.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = computed(() => [
|
||||||
|
{
|
||||||
|
group: 'Presets',
|
||||||
|
hideLabel: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Last 7 Days',
|
||||||
|
onClick: () => {
|
||||||
|
preset.value = 'Last 7 Days'
|
||||||
|
filters.period = getLastXDays(7)
|
||||||
|
reload()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Last 30 Days',
|
||||||
|
onClick: () => {
|
||||||
|
preset.value = 'Last 30 Days'
|
||||||
|
filters.period = getLastXDays(30)
|
||||||
|
reload()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Last 60 Days',
|
||||||
|
onClick: () => {
|
||||||
|
preset.value = 'Last 60 Days'
|
||||||
|
filters.period = getLastXDays(60)
|
||||||
|
reload()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Last 90 Days',
|
||||||
|
onClick: () => {
|
||||||
|
preset.value = 'Last 90 Days'
|
||||||
|
filters.period = getLastXDays(90)
|
||||||
|
reload()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Custom Range',
|
||||||
|
onClick: () => {
|
||||||
|
showDatePicker.value = true
|
||||||
|
setTimeout(() => datePickerRef.value?.open(), 0)
|
||||||
|
preset.value = 'Custom Range'
|
||||||
|
filters.period = null // Reset period to allow custom date selection
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
const numberCards = createResource({
|
const numberCards = createResource({
|
||||||
url: 'crm.api.dashboard.get_number_card_data',
|
url: 'crm.api.dashboard.get_number_card_data',
|
||||||
cache: ['Analytics', 'NumberCards'],
|
cache: ['Analytics', 'NumberCards'],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
from_date: fromDate.value,
|
||||||
|
to_date: toDate.value,
|
||||||
|
user: filters.user,
|
||||||
|
}
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const salesTrend = createResource({
|
const salesTrend = createResource({
|
||||||
url: 'crm.api.dashboard.get_sales_trend_data',
|
url: 'crm.api.dashboard.get_sales_trend_data',
|
||||||
cache: ['Analytics', 'SalesTrend'],
|
cache: ['Analytics', 'SalesTrend'],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
from_date: fromDate.value,
|
||||||
|
to_date: toDate.value,
|
||||||
|
user: filters.user,
|
||||||
|
}
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
transform(data = []) {
|
transform(data = []) {
|
||||||
return {
|
return {
|
||||||
data: data,
|
data: data,
|
||||||
title: 'Sales Trend',
|
title: __('Sales Trend'),
|
||||||
subtitle: 'Daily performance of leads, deals, and wins',
|
subtitle: __('Daily performance of leads, deals, and wins'),
|
||||||
xAxis: {
|
xAxis: {
|
||||||
title: 'Date',
|
title: __('Date'),
|
||||||
key: 'date',
|
key: 'date',
|
||||||
type: 'time' as const,
|
type: 'time' as const,
|
||||||
timeGrain: 'day' as const,
|
timeGrain: 'day' as const,
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
title: 'Count',
|
title: __('Count'),
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{ name: 'leads', type: 'line' as const, showDataPoints: true },
|
{ name: 'leads', type: 'line' as const, showDataPoints: true },
|
||||||
@ -95,19 +284,26 @@ const salesTrend = createResource({
|
|||||||
const funnelConversion = createResource({
|
const funnelConversion = createResource({
|
||||||
url: 'crm.api.dashboard.get_funnel_conversion_data',
|
url: 'crm.api.dashboard.get_funnel_conversion_data',
|
||||||
cache: ['Analytics', 'FunnelConversion'],
|
cache: ['Analytics', 'FunnelConversion'],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
from_date: fromDate.value,
|
||||||
|
to_date: toDate.value,
|
||||||
|
user: filters.user,
|
||||||
|
}
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
transform(data = []) {
|
transform(data = []) {
|
||||||
return {
|
return {
|
||||||
data: data,
|
data: data,
|
||||||
title: 'Funnel Conversion',
|
title: __('Funnel Conversion'),
|
||||||
subtitle: 'Lead to deal conversion pipeline',
|
subtitle: __('Lead to deal conversion pipeline'),
|
||||||
xAxis: {
|
xAxis: {
|
||||||
title: 'Stage',
|
title: __('Stage'),
|
||||||
key: 'stage',
|
key: 'stage',
|
||||||
type: 'category' as const,
|
type: 'category' as const,
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
title: 'Count',
|
title: __('Count'),
|
||||||
},
|
},
|
||||||
swapXY: true,
|
swapXY: true,
|
||||||
series: [
|
series: [
|
||||||
@ -126,22 +322,29 @@ const funnelConversion = createResource({
|
|||||||
const dealsBySalesperson = createResource({
|
const dealsBySalesperson = createResource({
|
||||||
url: 'crm.api.dashboard.get_deals_by_salesperson',
|
url: 'crm.api.dashboard.get_deals_by_salesperson',
|
||||||
cache: ['Analytics', 'DealsBySalesperson'],
|
cache: ['Analytics', 'DealsBySalesperson'],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
from_date: fromDate.value,
|
||||||
|
to_date: toDate.value,
|
||||||
|
user: filters.user,
|
||||||
|
}
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
transform(data = []) {
|
transform(data = []) {
|
||||||
return {
|
return {
|
||||||
data: data,
|
data: data,
|
||||||
title: 'Deals by Salesperson',
|
title: __('Deals by Salesperson'),
|
||||||
subtitle: 'Number of deals and total value per salesperson',
|
subtitle: 'Number of deals and total value per salesperson',
|
||||||
xAxis: {
|
xAxis: {
|
||||||
|
title: __('Salesperson'),
|
||||||
key: 'salesperson',
|
key: 'salesperson',
|
||||||
type: 'category' as const,
|
type: 'category' as const,
|
||||||
title: 'Salesperson',
|
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
title: 'Number of Deals',
|
title: __('Number of Deals'),
|
||||||
},
|
},
|
||||||
y2Axis: {
|
y2Axis: {
|
||||||
title: 'Deal Value ($)',
|
title: __('Deal Value ($)'),
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{ name: 'deals', type: 'bar' as const },
|
{ name: 'deals', type: 'bar' as const },
|
||||||
@ -159,22 +362,29 @@ const dealsBySalesperson = createResource({
|
|||||||
const dealsByTerritory = createResource({
|
const dealsByTerritory = createResource({
|
||||||
url: 'crm.api.dashboard.get_deals_by_territory',
|
url: 'crm.api.dashboard.get_deals_by_territory',
|
||||||
cache: ['Analytics', 'DealsByTerritory'],
|
cache: ['Analytics', 'DealsByTerritory'],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
from_date: fromDate.value,
|
||||||
|
to_date: toDate.value,
|
||||||
|
user: filters.user,
|
||||||
|
}
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
transform(data = []) {
|
transform(data = []) {
|
||||||
return {
|
return {
|
||||||
data: data,
|
data: data,
|
||||||
title: 'Deals by Territory',
|
title: __('Deals by Territory'),
|
||||||
subtitle: 'Geographic distribution of deals and revenue',
|
subtitle: __('Geographic distribution of deals and revenue'),
|
||||||
xAxis: {
|
xAxis: {
|
||||||
|
title: __('Territory'),
|
||||||
key: 'territory',
|
key: 'territory',
|
||||||
type: 'category' as const,
|
type: 'category' as const,
|
||||||
title: 'Territory',
|
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
title: 'Number of Deals',
|
title: __('Number of Deals'),
|
||||||
},
|
},
|
||||||
y2Axis: {
|
y2Axis: {
|
||||||
title: 'Deal Value ($)',
|
title: __('Deal Value ($)'),
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{ name: 'deals', type: 'bar' as const },
|
{ name: 'deals', type: 'bar' as const },
|
||||||
@ -192,19 +402,26 @@ const dealsByTerritory = createResource({
|
|||||||
const lostDealReasons = createResource({
|
const lostDealReasons = createResource({
|
||||||
url: 'crm.api.dashboard.get_lost_deal_reasons',
|
url: 'crm.api.dashboard.get_lost_deal_reasons',
|
||||||
cache: ['Analytics', 'LostDealReasons'],
|
cache: ['Analytics', 'LostDealReasons'],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
from_date: fromDate.value,
|
||||||
|
to_date: toDate.value,
|
||||||
|
user: filters.user,
|
||||||
|
}
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
transform(data = []) {
|
transform(data = []) {
|
||||||
return {
|
return {
|
||||||
data: data,
|
data: data,
|
||||||
title: 'Lost Deal Reasons',
|
title: __('Lost Deal Reasons'),
|
||||||
subtitle: 'Common reasons for losing deals',
|
subtitle: __('Common reasons for losing deals'),
|
||||||
xAxis: {
|
xAxis: {
|
||||||
|
title: __('Reason'),
|
||||||
key: 'reason',
|
key: 'reason',
|
||||||
type: 'category' as const,
|
type: 'category' as const,
|
||||||
title: 'Reason',
|
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
title: 'Count',
|
title: __('Count'),
|
||||||
},
|
},
|
||||||
swapXY: true,
|
swapXY: true,
|
||||||
series: [{ name: 'count', type: 'bar' as const }],
|
series: [{ name: 'count', type: 'bar' as const }],
|
||||||
@ -215,20 +432,23 @@ const lostDealReasons = createResource({
|
|||||||
const forecastedRevenue = createResource({
|
const forecastedRevenue = createResource({
|
||||||
url: 'crm.api.dashboard.get_forecasted_revenue',
|
url: 'crm.api.dashboard.get_forecasted_revenue',
|
||||||
cache: ['Analytics', 'ForecastedRevenue'],
|
cache: ['Analytics', 'ForecastedRevenue'],
|
||||||
|
makeParams() {
|
||||||
|
return { user: filters.user }
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
transform(data = []) {
|
transform(data = []) {
|
||||||
return {
|
return {
|
||||||
data: data,
|
data: data,
|
||||||
title: 'Revenue Forecast',
|
title: __('Revenue Forecast'),
|
||||||
subtitle: 'Projected vs actual revenue based on deal probability',
|
subtitle: __('Projected vs actual revenue based on deal probability'),
|
||||||
xAxis: {
|
xAxis: {
|
||||||
|
title: __('Month'),
|
||||||
key: 'month',
|
key: 'month',
|
||||||
type: 'time' as const,
|
type: 'time' as const,
|
||||||
title: 'Month',
|
|
||||||
timeGrain: 'month' as const,
|
timeGrain: 'month' as const,
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
title: 'Revenue ($)',
|
title: __('Revenue ($)'),
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{ name: 'forecasted', type: 'line' as const, showDataPoints: true },
|
{ name: 'forecasted', type: 'line' as const, showDataPoints: true },
|
||||||
@ -241,12 +461,19 @@ const forecastedRevenue = createResource({
|
|||||||
const dealsByStage = createResource({
|
const dealsByStage = createResource({
|
||||||
url: 'crm.api.dashboard.get_deals_by_stage',
|
url: 'crm.api.dashboard.get_deals_by_stage',
|
||||||
cache: ['Analytics', 'DealsByStage'],
|
cache: ['Analytics', 'DealsByStage'],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
from_date: fromDate.value,
|
||||||
|
to_date: toDate.value,
|
||||||
|
user: filters.user,
|
||||||
|
}
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
transform(data = []) {
|
transform(data = []) {
|
||||||
return {
|
return {
|
||||||
data: data,
|
data: data,
|
||||||
title: 'Deals by Stage',
|
title: __('Deals by Stage'),
|
||||||
subtitle: 'Current pipeline distribution',
|
subtitle: __('Current pipeline distribution'),
|
||||||
categoryColumn: 'stage',
|
categoryColumn: 'stage',
|
||||||
valueColumn: 'count',
|
valueColumn: 'count',
|
||||||
}
|
}
|
||||||
@ -256,12 +483,19 @@ const dealsByStage = createResource({
|
|||||||
const leadsBySource = createResource({
|
const leadsBySource = createResource({
|
||||||
url: 'crm.api.dashboard.get_leads_by_source',
|
url: 'crm.api.dashboard.get_leads_by_source',
|
||||||
cache: ['Analytics', 'LeadsBySource'],
|
cache: ['Analytics', 'LeadsBySource'],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
from_date: fromDate.value,
|
||||||
|
to_date: toDate.value,
|
||||||
|
user: filters.user,
|
||||||
|
}
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
transform(data = []) {
|
transform(data = []) {
|
||||||
return {
|
return {
|
||||||
data: data,
|
data: data,
|
||||||
title: 'Leads by Source',
|
title: __('Leads by Source'),
|
||||||
subtitle: 'Lead generation channel analysis',
|
subtitle: __('Lead generation channel analysis'),
|
||||||
categoryColumn: 'source',
|
categoryColumn: 'source',
|
||||||
valueColumn: 'count',
|
valueColumn: 'count',
|
||||||
}
|
}
|
||||||
@ -269,6 +503,6 @@ const leadsBySource = createResource({
|
|||||||
})
|
})
|
||||||
|
|
||||||
usePageMeta(() => {
|
usePageMeta(() => {
|
||||||
return { title: 'CRM Dashboard' }
|
return { title: __('CRM Dashboard') }
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
28
frontend/src/utils/dashboard.ts
Normal file
28
frontend/src/utils/dashboard.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { dayjs } from "frappe-ui"
|
||||||
|
|
||||||
|
export function getLastXDays(range: number = 30): string | null {
|
||||||
|
const today = new Date()
|
||||||
|
const lastXDate = new Date(today)
|
||||||
|
lastXDate.setDate(today.getDate() - range)
|
||||||
|
|
||||||
|
return `${dayjs(lastXDate).format('YYYY-MM-DD')},${dayjs(today).format(
|
||||||
|
'YYYY-MM-DD',
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatter(range: string) {
|
||||||
|
let [from, to] = range.split(',')
|
||||||
|
return `${formatRange(from)} to ${formatRange(to)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatRange(date: string) {
|
||||||
|
const dateObj = new Date(date)
|
||||||
|
return dateObj.toLocaleDateString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year:
|
||||||
|
dateObj.getFullYear() === new Date().getFullYear()
|
||||||
|
? undefined
|
||||||
|
: 'numeric',
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user