793 lines
21 KiB
Python
793 lines
21 KiB
Python
import frappe
|
|
from frappe import _
|
|
|
|
from crm.utils import sales_user_only
|
|
|
|
|
|
@frappe.whitelist()
|
|
@sales_user_only
|
|
def get_number_card_data(from_date="", to_date="", user="", lead_conds="", deal_conds=""):
|
|
"""
|
|
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())
|
|
|
|
roles = frappe.get_roles(frappe.session.user)
|
|
is_sales_user = "Sales User" in roles and "Sales Manager" not in roles and "System Manager" not in roles
|
|
if is_sales_user and not user:
|
|
user = frappe.session.user
|
|
|
|
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,
|
|
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,
|
|
]
|
|
|
|
|
|
def get_lead_count(from_date, to_date, user="", conds="", return_result=False):
|
|
"""
|
|
Get lead count for the dashboard.
|
|
"""
|
|
|
|
diff = frappe.utils.date_diff(to_date, from_date)
|
|
if diff == 0:
|
|
diff = 1
|
|
|
|
if user:
|
|
conds += f" AND lead_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_leads,
|
|
|
|
COUNT(CASE
|
|
WHEN creation >= %(prev_from_date)s AND creation < %(from_date)s
|
|
{conds}
|
|
THEN name
|
|
ELSE NULL
|
|
END) as prev_month_leads
|
|
FROM `tabCRM Lead`
|
|
""",
|
|
{
|
|
"from_date": from_date,
|
|
"to_date": to_date,
|
|
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
|
},
|
|
as_dict=1,
|
|
)
|
|
|
|
if return_result:
|
|
return result
|
|
|
|
current_month_leads = result[0].current_month_leads or 0
|
|
prev_month_leads = result[0].prev_month_leads or 0
|
|
|
|
delta_in_percentage = (
|
|
(current_month_leads - prev_month_leads) / prev_month_leads * 100 if prev_month_leads else 0
|
|
)
|
|
|
|
return {
|
|
"title": _("Total leads"),
|
|
"value": current_month_leads,
|
|
"delta": delta_in_percentage,
|
|
"deltaSuffix": "%",
|
|
"tooltip": _("Total number of leads"),
|
|
}
|
|
|
|
|
|
def get_ongoing_deal_count(from_date, to_date, user="", conds="", return_result=False):
|
|
"""
|
|
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)
|
|
if diff == 0:
|
|
diff = 1
|
|
|
|
if user:
|
|
conds += f" AND d.deal_owner = '{user}'"
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
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 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,
|
|
|
|
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
|
|
""",
|
|
{
|
|
"from_date": from_date,
|
|
"to_date": to_date,
|
|
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
|
},
|
|
as_dict=1,
|
|
)
|
|
|
|
if return_result:
|
|
return result
|
|
|
|
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 {
|
|
"count": {
|
|
"title": _("Ongoing deals"),
|
|
"value": current_month_deals,
|
|
"delta": delta_in_percentage,
|
|
"deltaSuffix": "%",
|
|
"tooltip": _("Total number of ongoing deals"),
|
|
},
|
|
"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):
|
|
"""
|
|
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)
|
|
if diff == 0:
|
|
diff = 1
|
|
|
|
if user:
|
|
conds += f" AND d.deal_owner = '{user}'"
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
COUNT(CASE
|
|
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
|
|
ELSE NULL
|
|
END) as current_month_deals,
|
|
|
|
COUNT(CASE
|
|
WHEN d.closed_date >= %(prev_from_date)s AND d.closed_date < %(from_date)s
|
|
AND s.type = 'Won'
|
|
{conds}
|
|
THEN d.name
|
|
ELSE NULL
|
|
END) as prev_month_deals,
|
|
|
|
AVG(CASE
|
|
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)
|
|
ELSE NULL
|
|
END) as current_month_avg_value,
|
|
|
|
AVG(CASE
|
|
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)
|
|
ELSE NULL
|
|
END) as prev_month_avg_value
|
|
FROM `tabCRM Deal` d
|
|
JOIN `tabCRM Deal Status` s ON d.status = s.name
|
|
""",
|
|
{
|
|
"from_date": from_date,
|
|
"to_date": to_date,
|
|
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
|
},
|
|
as_dict=1,
|
|
)
|
|
|
|
if return_result:
|
|
return result
|
|
|
|
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 {
|
|
"count": {
|
|
"title": _("Won deals"),
|
|
"value": current_month_deals,
|
|
"delta": delta_in_percentage,
|
|
"deltaSuffix": "%",
|
|
"tooltip": _("Total number of won deals based on its closure date"),
|
|
},
|
|
"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):
|
|
"""
|
|
Get average deal value for the dashboard.
|
|
"""
|
|
|
|
diff = frappe.utils.date_diff(to_date, from_date)
|
|
if diff == 0:
|
|
diff = 1
|
|
|
|
if 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 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 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,
|
|
"to_date": to_date,
|
|
"prev_from_date": frappe.utils.add_days(from_date, -diff),
|
|
},
|
|
as_dict=1,
|
|
)
|
|
|
|
current_month_avg = result[0].current_month_avg or 0
|
|
prev_month_avg = result[0].prev_month_avg or 0
|
|
|
|
delta = current_month_avg - prev_month_avg if prev_month_avg else 0
|
|
|
|
return {
|
|
"title": _("Avg deal value"),
|
|
"value": current_month_avg,
|
|
"tooltip": _("Average deal value of ongoing & won deals"),
|
|
"prefix": get_base_currency_symbol(),
|
|
# "suffix": "K",
|
|
"delta": delta,
|
|
"deltaSuffix": "%",
|
|
}
|
|
|
|
|
|
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)
|
|
if diff == 0:
|
|
diff = 1
|
|
|
|
if user:
|
|
conds += f" AND d.deal_owner = '{user}'"
|
|
|
|
prev_from_date = frappe.utils.add_days(from_date, -diff)
|
|
prev_to_date = from_date
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
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_date IS NOT NULL AND s.type = 'Won'
|
|
{conds}
|
|
""",
|
|
{
|
|
"from_date": from_date,
|
|
"to_date": to_date,
|
|
"prev_from_date": prev_from_date,
|
|
"prev_to_date": prev_to_date,
|
|
},
|
|
as_dict=1,
|
|
)
|
|
|
|
if return_result:
|
|
return result
|
|
|
|
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 {
|
|
"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,
|
|
},
|
|
}
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_sales_trend_data(from_date="", to_date="", user="", lead_conds="", deal_conds=""):
|
|
"""
|
|
Get sales trend data for the dashboard.
|
|
[
|
|
{ date: new Date('2024-05-01'), leads: 45, deals: 23, won_deals: 12 },
|
|
{ date: new Date('2024-05-02'), leads: 50, deals: 30, won_deals: 15 },
|
|
...
|
|
]
|
|
"""
|
|
|
|
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())
|
|
|
|
if user:
|
|
lead_conds += f" AND lead_owner = '{user}'"
|
|
deal_conds += f" AND deal_owner = '{user}'"
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
DATE_FORMAT(date, '%%Y-%%m-%%d') AS date,
|
|
SUM(leads) AS leads,
|
|
SUM(deals) AS deals,
|
|
SUM(won_deals) AS won_deals
|
|
FROM (
|
|
SELECT
|
|
DATE(creation) AS date,
|
|
COUNT(*) AS leads,
|
|
0 AS deals,
|
|
0 AS won_deals
|
|
FROM `tabCRM Lead`
|
|
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
|
{lead_conds}
|
|
GROUP BY DATE(creation)
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
DATE(d.creation) AS date,
|
|
0 AS leads,
|
|
COUNT(*) AS deals,
|
|
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(d.creation)
|
|
) AS daily
|
|
GROUP BY date
|
|
ORDER BY date
|
|
""",
|
|
{"from": from_date, "to": to_date},
|
|
as_dict=True,
|
|
)
|
|
|
|
return [
|
|
{
|
|
"date": frappe.utils.get_datetime(row.date).strftime("%Y-%m-%d"),
|
|
"leads": row.leads or 0,
|
|
"deals": row.deals or 0,
|
|
"won_deals": row.won_deals or 0,
|
|
}
|
|
for row in result
|
|
]
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_deals_by_salesperson(from_date="", to_date="", user="", deal_conds=""):
|
|
"""
|
|
Get deal data by salesperson for the dashboard.
|
|
[
|
|
{ salesperson: 'John Smith', deals: 45, value: 2300000 },
|
|
{ salesperson: 'Jane Doe', deals: 30, value: 1500000 },
|
|
...
|
|
]
|
|
"""
|
|
|
|
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())
|
|
|
|
if user:
|
|
deal_conds += f" AND d.deal_owner = '{user}'"
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
IFNULL(u.full_name, d.deal_owner) AS salesperson,
|
|
COUNT(*) AS deals,
|
|
SUM(COALESCE(d.deal_value, 0) * IFNULL(d.exchange_rate, 1)) AS value
|
|
FROM `tabCRM Deal` AS d
|
|
LEFT JOIN `tabUser` AS u ON u.name = d.deal_owner
|
|
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
|
{deal_conds}
|
|
GROUP BY d.deal_owner
|
|
ORDER BY value DESC
|
|
""",
|
|
{"from": from_date, "to": to_date},
|
|
as_dict=True,
|
|
)
|
|
|
|
return {
|
|
"data": result or [],
|
|
"currency_symbol": get_base_currency_symbol(),
|
|
}
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_deals_by_territory(from_date="", to_date="", user="", deal_conds=""):
|
|
"""
|
|
Get deal data by territory for the dashboard.
|
|
[
|
|
{ territory: 'North America', deals: 45, value: 2300000 },
|
|
{ territory: 'Europe', deals: 30, value: 1500000 },
|
|
...
|
|
]
|
|
"""
|
|
|
|
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())
|
|
|
|
if user:
|
|
deal_conds += f" AND d.deal_owner = '{user}'"
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
IFNULL(d.territory, 'Empty') AS territory,
|
|
COUNT(*) AS deals,
|
|
SUM(COALESCE(d.deal_value, 0) * IFNULL(d.exchange_rate, 1)) AS value
|
|
FROM `tabCRM Deal` AS d
|
|
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
|
{deal_conds}
|
|
GROUP BY d.territory
|
|
ORDER BY value DESC
|
|
""",
|
|
{"from": from_date, "to": to_date},
|
|
as_dict=True,
|
|
)
|
|
|
|
return {
|
|
"data": result or [],
|
|
"currency_symbol": get_base_currency_symbol(),
|
|
}
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_lost_deal_reasons(from_date="", to_date="", user="", deal_conds=""):
|
|
"""
|
|
Get lost deal reasons for the dashboard.
|
|
[
|
|
{ reason: 'Price too high', count: 20 },
|
|
{ reason: 'Competitor won', count: 15 },
|
|
...
|
|
]
|
|
"""
|
|
|
|
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())
|
|
|
|
if user:
|
|
deal_conds += f" AND d.deal_owner = '{user}'"
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
d.lost_reason AS reason,
|
|
COUNT(*) AS count
|
|
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 AND s.type = 'Lost'
|
|
{deal_conds}
|
|
GROUP BY d.lost_reason
|
|
HAVING reason IS NOT NULL AND reason != ''
|
|
ORDER BY count DESC
|
|
""",
|
|
{"from": from_date, "to": to_date},
|
|
as_dict=True,
|
|
)
|
|
|
|
return result or []
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_forecasted_revenue(user="", deal_conds=""):
|
|
"""
|
|
Get forecasted revenue for the dashboard.
|
|
[
|
|
{ date: new Date('2024-05-01'), forecasted: 1200000, actual: 980000 },
|
|
{ date: new Date('2024-06-01'), forecasted: 1350000, actual: 1120000 },
|
|
{ date: new Date('2024-07-01'), forecasted: 1600000, actual: "" },
|
|
{ date: new Date('2024-08-01'), forecasted: 1500000, actual: "" },
|
|
...
|
|
]
|
|
"""
|
|
|
|
if user:
|
|
deal_conds += f" AND d.deal_owner = '{user}'"
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
DATE_FORMAT(d.expected_closure_date, '%Y-%m') AS month,
|
|
SUM(
|
|
CASE
|
|
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 s.type = 'Won' THEN d.deal_value * IFNULL(d.exchange_rate, 1) -- actual
|
|
ELSE 0
|
|
END
|
|
) AS actual
|
|
FROM `tabCRM Deal` AS d
|
|
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.expected_closure_date, '%Y-%m')
|
|
ORDER BY month
|
|
""",
|
|
as_dict=True,
|
|
)
|
|
if not result:
|
|
return []
|
|
|
|
for row in result:
|
|
row["month"] = frappe.utils.get_datetime(row["month"]).strftime("%Y-%m-01")
|
|
row["forecasted"] = row["forecasted"] or ""
|
|
row["actual"] = row["actual"] or ""
|
|
|
|
return {
|
|
"data": result or [],
|
|
"currency_symbol": get_base_currency_symbol(),
|
|
}
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_funnel_conversion_data(from_date="", to_date="", user="", lead_conds="", deal_conds=""):
|
|
"""
|
|
Get funnel conversion data for the dashboard.
|
|
[
|
|
{ stage: 'Leads', count: 120 },
|
|
{ stage: 'Qualification', count: 100 },
|
|
{ stage: 'Negotiation', count: 80 },
|
|
{ stage: 'Ready to Close', count: 60 },
|
|
{ stage: 'Won', count: 30 },
|
|
...
|
|
]
|
|
"""
|
|
|
|
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())
|
|
|
|
if user:
|
|
lead_conds += f" AND lead_owner = '{user}'"
|
|
deal_conds += f" AND deal_owner = '{user}'"
|
|
|
|
result = []
|
|
|
|
# Get total leads
|
|
total_leads = frappe.db.sql(
|
|
f""" SELECT COUNT(*) AS count
|
|
FROM `tabCRM Lead`
|
|
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
|
{lead_conds}
|
|
""",
|
|
{"from": from_date, "to": to_date},
|
|
as_dict=True,
|
|
)
|
|
total_leads_count = total_leads[0].count if total_leads else 0
|
|
|
|
result.append({"stage": "Leads", "count": total_leads_count})
|
|
|
|
# Get deal stages
|
|
all_deal_stages = frappe.get_all(
|
|
"CRM Deal Status", filters={"type": ["!=", "Lost"]}, order_by="position", pluck="name"
|
|
)
|
|
|
|
# Get deal counts for each stage
|
|
for i, stage in enumerate(all_deal_stages):
|
|
stages_to_count = all_deal_stages[i:]
|
|
placeholders = ", ".join(["%s"] * len(stages_to_count))
|
|
query = f"""
|
|
SELECT COUNT(*) as count
|
|
FROM `tabCRM Deal`
|
|
WHERE DATE(creation) BETWEEN %s AND %s
|
|
AND status IN ({placeholders})
|
|
{deal_conds}
|
|
"""
|
|
params = [from_date, to_date, *stages_to_count]
|
|
row = frappe.db.sql(query, params, as_dict=True)
|
|
result.append({"stage": stage, "count": row[0]["count"] if row else 0})
|
|
|
|
return result or []
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_deals_by_stage(from_date="", to_date="", user="", deal_conds=""):
|
|
"""
|
|
Get deal data by stage for the dashboard.
|
|
[
|
|
{ stage: 'Prospecting', count: 120 },
|
|
{ stage: 'Negotiation', count: 45 },
|
|
...
|
|
]
|
|
"""
|
|
|
|
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())
|
|
|
|
if user:
|
|
deal_conds += f" AND d.deal_owner = '{user}'"
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
d.status AS stage,
|
|
COUNT(*) AS count
|
|
FROM `tabCRM Deal` AS d
|
|
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
|
{deal_conds}
|
|
GROUP BY d.status
|
|
ORDER BY count DESC
|
|
""",
|
|
{"from": from_date, "to": to_date},
|
|
as_dict=True,
|
|
)
|
|
|
|
return result or []
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_leads_by_source(from_date="", to_date="", user="", lead_conds=""):
|
|
"""
|
|
Get lead data by source for the dashboard.
|
|
[
|
|
{ source: 'Website', count: 120 },
|
|
{ source: 'Referral', count: 45 },
|
|
...
|
|
]
|
|
"""
|
|
|
|
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())
|
|
|
|
if user:
|
|
lead_conds += f" AND lead_owner = '{user}'"
|
|
|
|
result = frappe.db.sql(
|
|
f"""
|
|
SELECT
|
|
IFNULL(source, 'Empty') AS source,
|
|
COUNT(*) AS count
|
|
FROM `tabCRM Lead`
|
|
WHERE DATE(creation) BETWEEN %(from)s AND %(to)s
|
|
{lead_conds}
|
|
GROUP BY source
|
|
ORDER BY count DESC
|
|
""",
|
|
{"from": from_date, "to": to_date},
|
|
as_dict=True,
|
|
)
|
|
|
|
return result or []
|
|
|
|
|
|
def get_base_currency_symbol():
|
|
"""
|
|
Get the base currency symbol from the system settings.
|
|
"""
|
|
base_currency = frappe.db.get_single_value("FCRM Settings", "currency") or "USD"
|
|
return frappe.db.get_value("Currency", base_currency, "symbol") or ""
|