@ -0,0 +1,47 @@ |
|||
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg |
|||
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
CRM Dashboard |
|||
============== |
|||
* Visual report of CRM through Dashboard. |
|||
|
|||
Configuration |
|||
============= |
|||
* No additional configurations needed |
|||
|
|||
Company |
|||
------- |
|||
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
|||
|
|||
License |
|||
------- |
|||
Affero General Public License, Version 3 (AGPL v3). |
|||
(https://www.gnu.org/licenses/agpl-3.0-standalone.html) |
|||
|
|||
Credits |
|||
------- |
|||
Developer: (V17) Mruthul Raj, |
|||
(V18) Raneesha, |
|||
Contact:odoo@cybrosys.com |
|||
|
|||
Contacts |
|||
-------- |
|||
* Mail Contact : odoo@cybrosys.com |
|||
* Website : https://cybrosys.com |
|||
|
|||
Bug Tracker |
|||
----------- |
|||
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. |
|||
|
|||
Maintainer |
|||
========== |
|||
.. image:: https://cybrosys.com/images/logo.png |
|||
:target: https://cybrosys.com |
|||
|
|||
This module is maintained by Cybrosys Technologies. |
|||
For support and more information, please visit `Our Website <https://cybrosys.com/>`__ |
|||
|
|||
Further information |
|||
=================== |
|||
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,22 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Raneesha (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from . import models |
@ -0,0 +1,53 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Raneesha (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
{ |
|||
'name': "CRM Dashboard", |
|||
'version': '18.0.1.0.0', |
|||
'category': 'Extra Tools', |
|||
'summary': """Get a visual report of CRM through a Dashboard in CRM """, |
|||
'description': """CRM dashboard module brings a multipurpose graphical |
|||
dashboard for CRM module and making the relationship management |
|||
better and easier""", |
|||
'author': 'Cybrosys Techno Solutions', |
|||
'company': 'Cybrosys Techno Solutions', |
|||
'maintainer': 'Cybrosys Techno Solutions', |
|||
'website': "https://www.cybrosys.com", |
|||
'depends': ['crm', 'sale_management'], |
|||
'data': ['views/crm_dashboard_views.xml', |
|||
'views/res_users_views.xml', |
|||
'views/utm_campaign_views.xml', |
|||
'views/crm_team_views.xml', |
|||
], |
|||
'assets': { |
|||
'web.assets_backend': [ |
|||
'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js', |
|||
'crm_dashboard/static/src/css/style.css', |
|||
'crm_dashboard/static/src/js/crm_dashboard.js', |
|||
'crm_dashboard/static/src/xml/dashboard_templates.xml', |
|||
], |
|||
}, |
|||
'images': ['static/description/banner.png'], |
|||
'license': 'AGPL-3', |
|||
'installable': True, |
|||
'application': False, |
|||
'auto_install': False, |
|||
} |
@ -0,0 +1,6 @@ |
|||
## Module <crm_dashboard> |
|||
|
|||
#### 07.01.2025 |
|||
#### Version 18.0.1.0.0 |
|||
#### ADD |
|||
- Initial commit for CRM Dashboard |
@ -0,0 +1,26 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Raneesha (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from . import crm_lead |
|||
from . import crm_team |
|||
from . import res_user |
|||
from . import sale_order |
|||
from . import utm_campaign |
@ -0,0 +1,438 @@ |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Raneesha (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
import calendar |
|||
from dateutil.relativedelta import relativedelta |
|||
from odoo import api, fields, models |
|||
from odoo.tools.safe_eval import datetime |
|||
|
|||
|
|||
def get_period_start_date(period): |
|||
"""Returns the start date for the given period""" |
|||
today = datetime.datetime.now() |
|||
|
|||
if period == 'month': |
|||
start_date = today.replace(day=1) |
|||
elif period == 'quarter': |
|||
current_month = today.month |
|||
start_month = ((current_month - 1) // 3) * 3 + 1 |
|||
start_date = today.replace(month=start_month, day=1) |
|||
elif period == 'year': |
|||
start_date = today.replace(month=1, day=1) |
|||
elif period == 'week': |
|||
start_date = today - datetime.timedelta(days=today.weekday()) |
|||
else: |
|||
raise ValueError("Invalid period specified") |
|||
|
|||
return start_date.date() |
|||
|
|||
|
|||
class CRMLead(models.Model): |
|||
"""Extends crm.lead for adding more functions in it""" |
|||
_inherit = 'crm.lead' |
|||
|
|||
@api.model |
|||
def get_data(self, period): |
|||
"""Returns data to the dashboard tiles""" |
|||
period_days = get_period_start_date(period) |
|||
|
|||
crm_model = self.search([('create_date', '>=', period_days)]) |
|||
|
|||
lead_count = 0 |
|||
opportunity_count = 0 |
|||
win_count = 0 |
|||
active_lead_count = 0 |
|||
active_opportunity_count = 0 |
|||
won_opportunity_count = 0 |
|||
total_seconds = 0 |
|||
expected_revenue = 0 |
|||
revenue = 0 |
|||
unassigned_leads = 0 |
|||
|
|||
for record in crm_model: |
|||
if record.type == 'lead': |
|||
lead_count += 1 |
|||
if not record.user_id: |
|||
unassigned_leads += 1 |
|||
|
|||
if record.type == 'opportunity': |
|||
opportunity_count += 1 |
|||
expected_revenue += record.expected_revenue |
|||
if record.active: |
|||
if record.probability == 0: |
|||
active_opportunity_count += 1 |
|||
elif record.probability == 100: |
|||
won_opportunity_count += 1 |
|||
if record.stage_id.is_won: |
|||
revenue += record.expected_revenue |
|||
|
|||
if record.active: |
|||
if record.probability == 0: |
|||
active_lead_count += 1 |
|||
elif record.probability == 100: |
|||
win_count += 1 |
|||
|
|||
if record.date_conversion: |
|||
total_seconds += ( |
|||
record.date_conversion - record.create_date).seconds |
|||
|
|||
win_ratio = win_count / active_lead_count if active_lead_count else 0 |
|||
opportunity_ratio = won_opportunity_count / active_opportunity_count if active_opportunity_count else 0 |
|||
avg_close_time = round(total_seconds / len(crm_model.filtered( |
|||
lambda l: l.date_conversion))) if total_seconds else 0 |
|||
|
|||
return { |
|||
'leads': lead_count, |
|||
'opportunities': opportunity_count, |
|||
'exp_revenue': expected_revenue, |
|||
'revenue': revenue, |
|||
'win_ratio': win_ratio, |
|||
'opportunity_ratio': opportunity_ratio, |
|||
'avg_close_time': avg_close_time, |
|||
'unassigned_leads': unassigned_leads, |
|||
} |
|||
|
|||
@api.model |
|||
def get_lead_stage_data(self, period): |
|||
"""funnel chart""" |
|||
period_days = get_period_start_date(period) |
|||
crm_model = self.search([('create_date', '>=', period_days)]) |
|||
stage_lead_count = {} |
|||
|
|||
for lead in crm_model: |
|||
stage_name = lead.stage_id.name |
|||
if stage_name in stage_lead_count: |
|||
stage_lead_count[stage_name] += 1 |
|||
else: |
|||
stage_lead_count[stage_name] = 1 |
|||
|
|||
# Convert the dictionary into lists for stages and their counts |
|||
crm_stages = list(stage_lead_count.keys()) |
|||
lead_count = list(stage_lead_count.values()) |
|||
|
|||
# Return the data in the expected format |
|||
return [lead_count, crm_stages] |
|||
|
|||
@api.model |
|||
def get_lead_by_month(self): |
|||
"""pie chart""" |
|||
month_count = [] |
|||
month_value = [] |
|||
for rec in self.search([]): |
|||
month = rec.create_date.month |
|||
if month not in month_value: |
|||
month_value.append(month) |
|||
month_count.append(month) |
|||
month_val = [{'label': calendar.month_name[month], |
|||
'value': month_count.count(month)} for month in |
|||
month_value] |
|||
names = [record['label'] for record in month_val] |
|||
counts = [record['value'] for record in month_val] |
|||
month = [counts, names] |
|||
return month |
|||
|
|||
@api.model |
|||
def get_crm_activities(self, period): |
|||
"""Sales Activity Pie""" |
|||
start_date = get_period_start_date(period) |
|||
self._cr.execute(''' |
|||
SELECT mail_activity_type.name, COUNT(*) |
|||
FROM mail_activity |
|||
INNER JOIN mail_activity_type |
|||
ON mail_activity.activity_type_id = mail_activity_type.id |
|||
INNER JOIN crm_lead |
|||
ON mail_activity.res_id = crm_lead.id |
|||
AND mail_activity.res_model = 'crm.lead' |
|||
WHERE crm_lead.create_date >= %s |
|||
GROUP BY mail_activity_type.name |
|||
''', (start_date,)) |
|||
data = self._cr.dictfetchall() |
|||
names = [record['name']['en_US'] for record in data] |
|||
counts = [record['count'] for record in data] |
|||
return [counts, names] |
|||
|
|||
@api.model |
|||
def get_the_campaign_pie(self, period): |
|||
"""Leads Group By Campaign Pie""" |
|||
start_date = get_period_start_date(period) |
|||
self._cr.execute('''SELECT campaign_id, COUNT(*), |
|||
(SELECT name FROM utm_campaign |
|||
WHERE utm_campaign.id = crm_lead.campaign_id) |
|||
FROM crm_lead WHERE create_date >= %s AND campaign_id IS NOT NULL GROUP BY |
|||
campaign_id''', (start_date,)) |
|||
data = self._cr.dictfetchall() |
|||
names = [record.get('name') for record in data] |
|||
counts = [record.get('count') for record in data] |
|||
final = [counts, names] |
|||
return final |
|||
|
|||
@api.model |
|||
def get_the_source_pie(self, period): |
|||
"""Leads Group By Source Pie""" |
|||
start_date = get_period_start_date(period) |
|||
self._cr.execute('''SELECT source_id, COUNT(*), |
|||
(SELECT name FROM utm_source |
|||
WHERE utm_source.id = crm_lead.source_id) |
|||
FROM crm_lead WHERE create_date >= %s AND source_id IS NOT NULL GROUP BY |
|||
source_id''', (start_date,)) |
|||
data = self._cr.dictfetchall() |
|||
names = [record.get('name') for record in data] |
|||
counts = [record.get('count') for record in data] |
|||
final = [counts, names] |
|||
return final |
|||
|
|||
@api.model |
|||
def get_the_medium_pie(self, period): |
|||
"""Leads Group By Medium Pie""" |
|||
start_date = get_period_start_date(period) |
|||
self._cr.execute('''SELECT medium_id, COUNT(*), |
|||
(SELECT name FROM utm_medium |
|||
WHERE utm_medium.id = crm_lead.medium_id) |
|||
FROM crm_lead WHERE create_date >= %s AND medium_id IS NOT NULL GROUP BY medium_id''', |
|||
(start_date,)) |
|||
data = self._cr.dictfetchall() |
|||
names = [record.get('name') for record in data] |
|||
counts = [record.get('count') for record in data] |
|||
final = [counts, names] |
|||
return final |
|||
|
|||
@api.model |
|||
def get_total_lost_crm(self, period): |
|||
"""Lost Opportunity or Lead Graph""" |
|||
month_dict = {} |
|||
|
|||
# Format the start date to be used in the SQL query |
|||
start_date = get_period_start_date(period) |
|||
|
|||
if period == 'year': |
|||
num_months = 12 |
|||
elif period == 'quarter': |
|||
num_months = 3 |
|||
else: |
|||
num_months = 1 |
|||
|
|||
# Initialize the dictionary with month names and counts |
|||
for i in range(num_months): |
|||
current_month = start_date + relativedelta(months=i) |
|||
month_name = current_month.strftime('%B') |
|||
month_dict[month_name] = 0 |
|||
|
|||
# Execute the SQL query to count lost opportunities |
|||
self._cr.execute('''SELECT TO_CHAR(create_date, 'Month') AS month, |
|||
COUNT(id) |
|||
FROM crm_lead |
|||
WHERE probability = 0 |
|||
AND active = FALSE |
|||
AND create_date >= %s |
|||
GROUP BY TO_CHAR(create_date, 'Month') |
|||
ORDER BY TO_CHAR(create_date, 'Month')''', |
|||
(start_date,)) |
|||
|
|||
data = self._cr.dictfetchall() |
|||
|
|||
# Update month_dict with the results from the query |
|||
for rec in data: |
|||
month_name = rec[ |
|||
'month'].strip() # Strip the month name to remove extra spaces |
|||
if month_name in month_dict: |
|||
month_dict[month_name] = rec['count'] |
|||
|
|||
result = { |
|||
'month': list(month_dict.keys()), |
|||
'count': list(month_dict.values()) |
|||
} |
|||
|
|||
return result |
|||
|
|||
@api.model |
|||
def get_upcoming_events(self): |
|||
"""Upcoming Activities Table""" |
|||
today = fields.date.today() |
|||
session_user_id = self.env.uid |
|||
self._cr.execute('''select mail_activity.activity_type_id, |
|||
mail_activity.date_deadline, mail_activity.summary, |
|||
mail_activity.res_name,(SELECT mail_activity_type.name |
|||
FROM mail_activity_type WHERE mail_activity_type.id = |
|||
mail_activity.activity_type_id), mail_activity.user_id FROM |
|||
mail_activity WHERE res_model = 'crm.lead' AND |
|||
mail_activity.date_deadline >= '%s' and user_id = %s GROUP BY |
|||
mail_activity.activity_type_id, mail_activity.date_deadline, |
|||
mail_activity.summary,mail_activity.res_name,mail_activity.user_id |
|||
order by mail_activity.date_deadline asc''' % ( |
|||
today, session_user_id)) |
|||
data = self._cr.fetchall() |
|||
events = [[record[0], record[1], record[2], record[3], |
|||
record[4] if record[4] else '', |
|||
self.env['res.users'].browse(record[5]).name if record[ |
|||
5] else '' |
|||
] for record in data] |
|||
return { |
|||
'event': events, |
|||
'cur_lang': self.env.context.get('lang') |
|||
} |
|||
|
|||
@api.model |
|||
def total_revenue_by_sales(self, period): |
|||
"""Total expected revenue and count Pie""" |
|||
session_user_id = self.env.uid |
|||
start_date = get_period_start_date(period) |
|||
# SQL query template |
|||
query_template = """ |
|||
SELECT sum(expected_revenue) as revenue |
|||
FROM crm_lead |
|||
WHERE user_id = %s |
|||
AND type = 'opportunity' |
|||
AND active = %s |
|||
{conditions} |
|||
""" |
|||
|
|||
# Query conditions for different cases |
|||
conditions = [ |
|||
"", # Active opportunities |
|||
"AND stage_id = '4'", # Won opportunities |
|||
"AND probability = '0'", # Lost opportunities |
|||
] |
|||
|
|||
# Active status for each condition |
|||
active_status = ['true', 'false', 'false'] |
|||
|
|||
# Fetch total revenue for each condition |
|||
revenues = [] |
|||
for cond, active in zip(conditions, active_status): |
|||
self._cr.execute(query_template.format(conditions=cond), |
|||
(session_user_id, active)) |
|||
revenue = self._cr.fetchone()[0] or 0 |
|||
revenues.append(revenue) |
|||
|
|||
# Calculate expected revenue without won |
|||
exp_revenue_without_won = revenues[0] - revenues[1] |
|||
|
|||
# Prepare the data for the pie chart |
|||
revenue_pie_count = [exp_revenue_without_won, revenues[1], revenues[2]] |
|||
revenue_pie_title = ['Expected without Won', 'Won', 'Lost'] |
|||
|
|||
return [revenue_pie_count, revenue_pie_title] |
|||
|
|||
|
|||
@api.model |
|||
def get_top_sp_revenue(self,period): |
|||
"""Top 10 Salesperson revenue Table""" |
|||
user = self.env.user |
|||
start_date = get_period_start_date(period) |
|||
self._cr.execute('''SELECT user_id, id, expected_revenue, name, company_id |
|||
FROM crm_lead |
|||
WHERE create_date >= '%s' AND expected_revenue IS NOT NULL AND user_id = %s |
|||
GROUP BY user_id, id |
|||
ORDER BY expected_revenue DESC |
|||
LIMIT 10''' % (start_date,user.id,)) |
|||
data1 = self._cr.fetchall() |
|||
top_revenue = [ |
|||
[self.env['res.users'].browse(rec[0]).name, rec[1], rec[2], |
|||
rec[3], self.env['res.company'].browse(rec[4]).currency_id.symbol] |
|||
for rec in data1] |
|||
return {'top_revenue': top_revenue} |
|||
|
|||
@api.model |
|||
def get_top_country_revenue(self, period): |
|||
"""Top 10 Country Wise Revenue - Heat Map""" |
|||
company_id = self.env.company.id |
|||
self._cr.execute('''SELECT country_id, sum(expected_revenue) |
|||
FROM crm_lead |
|||
WHERE expected_revenue IS NOT NULL |
|||
AND country_id IS NOT NULL |
|||
GROUP BY country_id |
|||
ORDER BY sum(expected_revenue) DESC |
|||
LIMIT 10''') |
|||
data1 = self._cr.fetchall() |
|||
country_revenue = [[self.env['res.country'].browse(rec[0]).name, |
|||
rec[1], self.env['res.company'].browse( |
|||
company_id).currency_id.symbol] for rec in data1] |
|||
return {'country_revenue': country_revenue} |
|||
|
|||
@api.model |
|||
def get_top_country_count(self, period): |
|||
"""Top 10 Country Wise Count - Heat Map""" |
|||
self._cr.execute('''SELECT country_id, COUNT(*) |
|||
FROM crm_lead |
|||
WHERE country_id IS NOT NULL |
|||
GROUP BY country_id |
|||
ORDER BY COUNT(*) DESC |
|||
LIMIT 10''') |
|||
data1 = self._cr.fetchall() |
|||
country_count = [[self.env['res.country'].browse(rec[0]).name, rec[1]] |
|||
for rec in data1] |
|||
return {'country_count': country_count} |
|||
|
|||
@api.model |
|||
def get_recent_activities(self, kwargs): |
|||
"""Recent Activities Table""" |
|||
today = fields.Date.today() |
|||
recent_week = today - relativedelta(days=7) |
|||
current_user_id = self.env.user.id # Get the current logged-in user's ID |
|||
# Check if the current user is an administrator |
|||
is_admin = self.env.user.has_group('base.group_system') |
|||
# Build the SQL query with or without user filtering based on role |
|||
if is_admin: |
|||
self._cr.execute(''' |
|||
SELECT mail_activity.activity_type_id, |
|||
mail_activity.date_deadline, |
|||
mail_activity.summary, |
|||
mail_activity.res_name, |
|||
(SELECT mail_activity_type.name |
|||
FROM mail_activity_type |
|||
WHERE mail_activity_type.id = mail_activity.activity_type_id), |
|||
mail_activity.user_id |
|||
FROM mail_activity |
|||
WHERE res_model = 'crm.lead' |
|||
AND mail_activity.date_deadline BETWEEN %s AND %s |
|||
GROUP BY mail_activity.activity_type_id, |
|||
mail_activity.date_deadline, |
|||
mail_activity.summary, |
|||
mail_activity.res_name, |
|||
mail_activity.user_id |
|||
ORDER BY mail_activity.date_deadline DESC |
|||
''', (recent_week, today)) |
|||
else: |
|||
self._cr.execute(''' |
|||
SELECT mail_activity.activity_type_id, |
|||
mail_activity.date_deadline, |
|||
mail_activity.summary, |
|||
mail_activity.res_name, |
|||
(SELECT mail_activity_type.name |
|||
FROM mail_activity_type |
|||
WHERE mail_activity_type.id = mail_activity.activity_type_id), |
|||
mail_activity.user_id |
|||
FROM mail_activity |
|||
WHERE res_model = 'crm.lead' |
|||
AND mail_activity.date_deadline BETWEEN %s AND %s |
|||
AND mail_activity.user_id = %s |
|||
GROUP BY mail_activity.activity_type_id, |
|||
mail_activity.date_deadline, |
|||
mail_activity.summary, |
|||
mail_activity.res_name, |
|||
mail_activity.user_id |
|||
ORDER BY mail_activity.date_deadline DESC |
|||
''', (recent_week, today, current_user_id)) |
|||
|
|||
data = self._cr.fetchall() |
|||
activities = [ |
|||
[*record[:5], self.env['res.users'].browse(record[5]).name] for |
|||
record in data] |
|||
return {'activities': activities} |
@ -0,0 +1,34 @@ |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Raneesha (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class CRMSalesTeam(models.Model): |
|||
"""CRMSalesTeam model extends the base crm.team model to add a field, |
|||
crm_lead_state_id, which represents the default CRM Lead stage for |
|||
leads associated with this sales team. |
|||
""" |
|||
_inherit = 'crm.team' |
|||
|
|||
crm_lead_state_id = fields.Many2one("crm.stage", string="CRM Lead", |
|||
store=True, |
|||
help="CRM Lead stage for leads " |
|||
"associated with this sales team.") |
@ -0,0 +1,30 @@ |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Raneesha (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class ResUser(models.Model): |
|||
"""ResUser model extends the base res. Users model to add a field 'sales' |
|||
that represents the target for the salesperson.""" |
|||
_inherit = 'res.users' |
|||
|
|||
sales = fields.Float(string="Target", help="The target value for the " |
|||
"salesperson.") |
@ -0,0 +1,35 @@ |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Raneesha (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import models |
|||
|
|||
|
|||
class SalesOrder(models.Model): |
|||
"""Extends sale order for overriding action confirm function""" |
|||
_inherit = 'sale.order' |
|||
|
|||
def action_confirm(self): |
|||
"""Override the action_confirm method to change CRM Stage. |
|||
Returns: |
|||
dict: A dictionary containing the result of the original |
|||
action_confirm method.""" |
|||
res = super(SalesOrder, self).action_confirm() |
|||
self.opportunity_id.stage_id = self.team_id.crm_lead_state_id |
|||
return res |
@ -0,0 +1,63 @@ |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Raneesha (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class CampaignSmartButton(models.Model): |
|||
"""Extends the UTM Campaign model with a Smart Button to calculate and |
|||
display the Win Loss Ratio.""" |
|||
_inherit = 'utm.campaign' |
|||
|
|||
total_ratio = fields.Float(compute='_compute_ratio', |
|||
help="Total lead ratio") |
|||
|
|||
def get_ratio(self): |
|||
"""Open the Win Loss Ratio window upon clicking the Smart Button. |
|||
Returns: |
|||
dict: A dictionary specifying the action to be taken upon button |
|||
click.""" |
|||
self.ensure_one() |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'Win Loss Ratio', |
|||
'view_mode': 'kanban', |
|||
'res_model': 'crm.lead', |
|||
'domain': [['user_id', '=', self.env.uid], "|", |
|||
"&", ["active", "=", True], ["probability", '=', 100], |
|||
"&", ["active", "=", False], ["probability", '=', 0] |
|||
], |
|||
'context': "{'create': False,'records_draggable': False}" |
|||
} |
|||
|
|||
def _compute_ratio(self): |
|||
"""Compute the Win Loss Ratio based on CRM lead statistics.""" |
|||
total_won = self.env['crm.lead'].search_count( |
|||
[('active', '=', True), ('probability', '=', 100), |
|||
('user_id', '=', self.env.uid)]) |
|||
total_lose = self.env['crm.lead'].search_count( |
|||
[('active', '=', False), ('probability', '=', 0), |
|||
('user_id', '=', self.env.uid)]) |
|||
|
|||
if total_lose == 0: |
|||
ratio = 0 |
|||
else: |
|||
ratio = round(total_won / total_lose, 2) |
|||
self.total_ratio = ratio |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 628 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 495 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 738 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 926 B |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 800 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 189 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 875 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 767 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 760 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 697 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 171 KiB |
After Width: | Height: | Size: 198 KiB |
After Width: | Height: | Size: 173 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 151 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 179 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 213 KiB |
After Width: | Height: | Size: 880 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 8.4 KiB |
@ -0,0 +1,295 @@ |
|||
/* Dashboard Main Section Styles */ |
|||
.dashboard_main_section { |
|||
padding-top: 2rem; |
|||
padding-bottom: 1rem; |
|||
background-color: #f8f9fa; |
|||
} |
|||
|
|||
.section-header { |
|||
font-size: 1.5rem; |
|||
font-weight: bold; |
|||
color: #343a40; |
|||
} |
|||
|
|||
/* Period Selection Styles */ |
|||
#period_selection { |
|||
margin-top: 1rem; |
|||
width: 100%; |
|||
height: 38px; |
|||
border: 1px solid #ced4da; |
|||
border-radius: 0.25rem; |
|||
background-color: #fff; |
|||
color: #495057; |
|||
font-size: 1rem; |
|||
line-height: 1.5; |
|||
padding: 0.375rem 1.75rem 0.375rem 0.75rem; |
|||
appearance: none; |
|||
-webkit-appearance: none; |
|||
-moz-appearance: none; |
|||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E"); |
|||
background-repeat: no-repeat; |
|||
background-position: right 0.75rem center; |
|||
background-size: 16px 12px; |
|||
} |
|||
|
|||
#period_selection:focus { |
|||
outline: none; |
|||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); |
|||
} |
|||
|
|||
/* Responsive Styles */ |
|||
@media (max-width: 767px) { |
|||
.section-header { |
|||
font-size: 1.25rem; |
|||
} |
|||
|
|||
#period_selection { |
|||
margin-top: 0.5rem; |
|||
height: 32px; |
|||
font-size: 0.875rem; |
|||
padding: 0.25rem 1.5rem 0.25rem 0.5rem; |
|||
} |
|||
} |
|||
|
|||
/* Dashboard Card Section Styles */ |
|||
.dashboard_card_section { |
|||
padding-top: 1rem; |
|||
padding-bottom: 2rem; |
|||
} |
|||
|
|||
/* Dashboard Card Styles */ |
|||
.dashboard-card { |
|||
position: relative; |
|||
margin-bottom: 1rem; |
|||
padding: 1rem; |
|||
border-radius: 0.5rem; |
|||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
|||
background-color: #fff; |
|||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; |
|||
} |
|||
|
|||
.dashboard-card:hover { |
|||
transform: translateY(-4px); |
|||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); |
|||
} |
|||
|
|||
.dashboard-card__icon-container { |
|||
width: 3rem; |
|||
height: 3rem; |
|||
font-size: 1.5rem; |
|||
color: #797979; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
border-radius: 50%; |
|||
background-color: #f0f0f0; |
|||
} |
|||
|
|||
.dashboard-card__details { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
flex: 1; |
|||
padding-left: 1rem; |
|||
} |
|||
|
|||
.dashboard-card__details h3, |
|||
.dashboard-card__details h4 { |
|||
margin-bottom: 0; |
|||
color: #333; |
|||
} |
|||
|
|||
.dashboard-card__details h3 { |
|||
font-size: 1.25rem; |
|||
} |
|||
|
|||
.dashboard-card__details h4 { |
|||
font-size: 1rem; |
|||
color: #666; |
|||
} |
|||
|
|||
/* Specific Card Styles */ |
|||
.card-shadow { |
|||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.bg-mauve-light { |
|||
background-color: #f9f7ff; |
|||
} |
|||
|
|||
.text-mauve { |
|||
color: #7c4dff; |
|||
} |
|||
|
|||
/* Responsive Styles */ |
|||
@media (max-width: 767px) { |
|||
.dashboard-card { |
|||
padding: 0.75rem; |
|||
} |
|||
|
|||
.dashboard-card__icon-container { |
|||
width: 2.5rem; |
|||
height: 2.5rem; |
|||
font-size: 1.25rem; |
|||
} |
|||
|
|||
.dashboard-card__details h3 { |
|||
font-size: 1rem; |
|||
} |
|||
|
|||
.dashboard-card__details h4 { |
|||
font-size: 0.875rem; |
|||
} |
|||
} |
|||
/* Modern Minimalistic Kanban Card Style */ |
|||
/* Card Layout: Date as the Main Element */ |
|||
.upcoming_activities_div .chart-container { |
|||
padding: 20px; |
|||
background-color: #ffffff; |
|||
border-radius: 12px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|||
} |
|||
|
|||
.upcoming_activities_div h3 { |
|||
margin-bottom: 20px; |
|||
font-weight: 600; |
|||
font-size: 18px; |
|||
color: #444; |
|||
} |
|||
|
|||
.recent_activity_div h3 { |
|||
margin-bottom: 20px; |
|||
font-weight: 600; |
|||
font-size: 18px; |
|||
color: #444; |
|||
} |
|||
.recent_activity_div .chart-container { |
|||
padding: 20px; |
|||
background-color: #ffffff; |
|||
border-radius: 12px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|||
} |
|||
|
|||
.top_sp_revenue_div .chart-container { |
|||
padding: 20px; |
|||
background-color: #ffffff; |
|||
border-radius: 12px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|||
} |
|||
.top_sp_revenue_div h3 { |
|||
margin-bottom: 20px; |
|||
font-weight: 600; |
|||
font-size: 18px; |
|||
color: #444; |
|||
} |
|||
.top_country_revenue_div .chart-container { |
|||
padding: 20px; |
|||
background-color: #ffffff; |
|||
border-radius: 12px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|||
} |
|||
.top_country_revenue_div h3 { |
|||
margin-bottom: 20px; |
|||
font-weight: 600; |
|||
font-size: 18px; |
|||
color: #444; |
|||
} |
|||
.top_country_count_div .chart-container { |
|||
padding: 20px; |
|||
background-color: #ffffff; |
|||
border-radius: 12px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|||
} |
|||
.top_country_count_div h3 { |
|||
margin-bottom: 20px; |
|||
font-weight: 600; |
|||
font-size: 18px; |
|||
color: #444; |
|||
} |
|||
.crm_scroll_table { |
|||
overflow-y: auto; |
|||
max-height: 530px; |
|||
} |
|||
|
|||
.items-table { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 15px; |
|||
} |
|||
|
|||
.item-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.item-header { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 15px; |
|||
background-color: #f7f7f7; |
|||
border-radius: 10px; |
|||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); |
|||
transition: box-shadow 0.3s ease; |
|||
} |
|||
|
|||
.item-header:hover { |
|||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.date-container { |
|||
flex-shrink: 0; |
|||
background-color: #4c8bf5; |
|||
color: #ffffff; |
|||
font-size: 16px; |
|||
font-weight: 700; |
|||
padding: 10px; |
|||
border-radius: 8px; |
|||
text-align: center; |
|||
margin-right: 20px; |
|||
min-width: 70px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
} |
|||
|
|||
.date-container .day { |
|||
font-size: 24px; |
|||
font-weight: 800; |
|||
} |
|||
|
|||
.date-container .month { |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.item-content { |
|||
flex-grow: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.item-content .item-title { |
|||
font-size: 18px; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 5px; |
|||
} |
|||
|
|||
.item-content .item-summary { |
|||
font-size: 14px; |
|||
color: #666; |
|||
margin-bottom: 3px; |
|||
} |
|||
|
|||
.item-content .item-extra { |
|||
font-size: 13px; |
|||
color: #888; |
|||
} |
|||
h2 { |
|||
padding: 8px; |
|||
} |
|||
canvas{ |
|||
padding-bottom: 9px; |
|||
} |
|||
#in_ex_body_hide{ |
|||
background: white; |
|||
} |
@ -0,0 +1,560 @@ |
|||
/** @odoo-module **/ |
|||
import {registry} from "@web/core/registry"; |
|||
import {Component} from "@odoo/owl"; |
|||
import {onWillStart, onMounted, useState, useRef, useEffect} from "@odoo/owl"; |
|||
import {useService} from "@web/core/utils/hooks"; |
|||
|
|||
export class CrmDashboard extends Component { |
|||
setup() { |
|||
super.setup(...arguments); |
|||
this.orm = useService("orm"); |
|||
this.action = useService("action"); |
|||
this.Leadstage = useRef('leads_stage') |
|||
this.LeadByMonth = useRef('leads_by_month') |
|||
this.CrmActivities = useRef('crm_activities') |
|||
this.LeadByCampaign = useRef('leads_campaign') |
|||
this.LeadByMedium = useRef('leads_medium') |
|||
this.LeadBySource = useRef('leads_source') |
|||
this.LostLead = useRef('leads_lost') |
|||
this.TotalRevenue = useRef('total_revenue') |
|||
this.state = useState({ |
|||
period: 'month', |
|||
leads: null, |
|||
opportunities: null, |
|||
exp_revenue: null, |
|||
revenue: null, |
|||
win_ratio: null, |
|||
avg_close_time: null, |
|||
opportunity_ratio: null, |
|||
unassigned_leads: null, |
|||
charts: [], |
|||
upcoming_events: [], |
|||
current_lang: [], |
|||
top_sp_revenue: [], |
|||
country_count: [], |
|||
country_revenue: [], |
|||
recent_activities:[], |
|||
|
|||
}) |
|||
onWillStart(async () => { |
|||
await this.fetch_data(); |
|||
await this.UpcomingEvents(); |
|||
await this.TopSpRevenue(); |
|||
await this.TopCountryRevenue(); |
|||
await this.TopCountryCount(); |
|||
await this.RecentActivities(); |
|||
// Destroy existing chart if it exists
|
|||
|
|||
}); |
|||
|
|||
useEffect(() => { |
|||
if (this.state.charts.length > 0) { |
|||
this.state.charts.forEach(chart => { |
|||
chart.destroy(); |
|||
}); |
|||
} |
|||
if (this.state.period) { |
|||
this.fetch_data(); |
|||
this.render_leads_by_stage(); |
|||
this.render_leads_by_month(); |
|||
this.render_crm_activities(); |
|||
this.render_lead_by_campaign(); |
|||
this.render_lead_by_medium(); |
|||
this.render_lead_by_source(); |
|||
this.render_lost_lead(); |
|||
this.render_total_revenue(); |
|||
} |
|||
}, () => [this.state.period]); |
|||
} |
|||
|
|||
async fetch_data() { |
|||
var self = this |
|||
var result = await this.orm.call('crm.lead', "get_data", [this.state.period]) |
|||
this.state.leads = result['leads'] |
|||
this.state.opportunities = result['opportunities'] |
|||
this.state.exp_revenue = result['exp_revenue'] |
|||
this.state.revenue = result['revenue'] |
|||
this.state.win_ratio = result['win_ratio'] |
|||
this.state.opportunity_ratio = result['opportunity_ratio'] |
|||
this.state.avg_close_time = result['avg_close_time'] |
|||
this.state.unassigned_leads = result['unassigned_leads'] |
|||
|
|||
} |
|||
|
|||
async UpcomingEvents() { |
|||
var result = await this.orm.call('crm.lead', "get_upcoming_events", []) |
|||
this.state.upcoming_events = result['event'] |
|||
this.state.current_lang = result['cur_lang'] |
|||
} |
|||
|
|||
async TopSpRevenue() { |
|||
var result = await this.orm.call('crm.lead', "get_top_sp_revenue", [this.state.period]) |
|||
this.state.top_sp_revenue = result['top_revenue'] |
|||
// this.state.current_lang = result['cur_lang']
|
|||
} |
|||
|
|||
async TopCountryCount() { |
|||
var result = await this.orm.call('crm.lead', "get_top_country_count", [this.state.period]) |
|||
this.state.country_count = result['country_count'] |
|||
|
|||
} |
|||
|
|||
async TopCountryRevenue() { |
|||
var result = await this.orm.call('crm.lead', "get_top_country_revenue", [this.state.period]) |
|||
this.state.country_revenue = result['country_revenue'] |
|||
} |
|||
async RecentActivities() { |
|||
var result = await this.orm.call('crm.lead', "get_recent_activities", [this.state.period]) |
|||
this.state.recent_activities = result['activities'] |
|||
} |
|||
|
|||
|
|||
SetPeriods() { |
|||
var today = new Date(); |
|||
var start_date; |
|||
|
|||
if (this.state.period == 'month') { |
|||
start_date = new Date(today.getFullYear(), today.getMonth(), 1); // Start of the month
|
|||
} else if (this.state.period == 'year') { |
|||
start_date = new Date(today.getFullYear(), 0, 1); // Start of the year
|
|||
} else if (this.state.period == 'quarter') { |
|||
var startMonth = Math.floor(today.getMonth() / 3) * 3; // Start month of the quarter
|
|||
start_date = new Date(today.getFullYear(), startMonth, 1); // Start of the quarter
|
|||
} else if (this.state.period == 'week') { |
|||
var dayOfWeek = today.getDay(); |
|||
var diff = today.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1); // Adjust when day is Sunday
|
|||
start_date = new Date(today.setDate(diff)); // Start of the week (Monday)
|
|||
} |
|||
|
|||
return start_date.getFullYear() + '-' + (start_date.getMonth() + 1).toString().padStart(2, '0') + '-' + start_date.getDate().toString().padStart(2, '0'); |
|||
} |
|||
|
|||
|
|||
OnChangePeriods() { |
|||
|
|||
} |
|||
|
|||
onClickLeads() { |
|||
var date = this.SetPeriods() |
|||
this.action.doAction({ |
|||
type: "ir.actions.act_window", |
|||
name: "Leads", |
|||
res_model: 'crm.lead', |
|||
views: [[false, "kanban"], [false, "form"]], |
|||
target: "current", |
|||
domain: [['type', '=', 'lead'], ['create_date', '>=', date]] |
|||
}); |
|||
} |
|||
|
|||
onClickOpportunities() { |
|||
var date = this.SetPeriods() |
|||
this.action.doAction({ |
|||
type: "ir.actions.act_window", |
|||
name: "Opportunities", |
|||
res_model: 'crm.lead', |
|||
views: [[false, "kanban"], [false, "form"]], |
|||
target: "current", |
|||
domain: [['type', '=', 'opportunity'], ['create_date', '>=', date]] |
|||
}); |
|||
} |
|||
|
|||
onClickExpRevenue() { |
|||
var date = this.SetPeriods() |
|||
this.action.doAction({ |
|||
type: "ir.actions.act_window", |
|||
name: "Expense Revenue", |
|||
res_model: 'crm.lead', |
|||
views: [[false, "list"], [false, "form"]], |
|||
target: "current", |
|||
domain: [['type', '=', 'opportunity'], ['active', '=', true], ['create_date', '>=', date]] |
|||
}); |
|||
} |
|||
|
|||
onClickRevenue() { |
|||
var date = this.SetPeriods() |
|||
this.action.doAction({ |
|||
type: "ir.actions.act_window", |
|||
name: "Revenue", |
|||
res_model: 'crm.lead', |
|||
views: [[false, "list"], [false, "form"]], |
|||
target: "current", |
|||
domain: [['type', '=', 'opportunity'], ['active', '=', true], ['stage_id', '=', 4], ['create_date', '>=', date]] |
|||
}); |
|||
} |
|||
|
|||
onClickUnAssignedLeads() { |
|||
var date = this.SetPeriods() |
|||
this.action.doAction({ |
|||
type: "ir.actions.act_window", |
|||
name: "Unassigned Leads", |
|||
res_model: 'crm.lead', |
|||
views: [[false, "list"], [false, "form"]], |
|||
target: "current", |
|||
domain: [['user_id', '=', false], ['type', '=', 'lead'], ['create_date', '>=', date]] |
|||
}); |
|||
} |
|||
|
|||
async render_leads_by_stage() { |
|||
|
|||
var self = this; |
|||
var ctx = this.Leadstage.el; |
|||
const arrays = await this.orm.call('crm.lead', "get_lead_stage_data", [this.state.period]); |
|||
const data = { |
|||
labels: arrays[1], |
|||
datasets: [{ |
|||
label: 'Leads', |
|||
data: arrays[0], |
|||
backgroundColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
borderColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
}] |
|||
}; |
|||
|
|||
//create Chart class object
|
|||
var chart = new Chart(ctx, { |
|||
type: 'polarArea', |
|||
data: data, |
|||
|
|||
// options: options
|
|||
}); |
|||
this.state.charts.push(chart) |
|||
} |
|||
|
|||
async render_leads_by_month() { |
|||
|
|||
var self = this; |
|||
var ctx = this.LeadByMonth.el; |
|||
const arrays = await this.orm.call('crm.lead', "get_lead_by_month", []); |
|||
const data = { |
|||
labels: arrays[1], |
|||
datasets: [{ |
|||
label: 'Leads', |
|||
data: arrays[0], |
|||
backgroundColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
borderColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
}] |
|||
}; |
|||
|
|||
|
|||
//create Chart class object
|
|||
var chart = new Chart(ctx, { |
|||
type: 'doughnut', |
|||
data: data, |
|||
// options: options
|
|||
}); |
|||
this.state.charts.push(chart) |
|||
} |
|||
|
|||
async render_crm_activities() { |
|||
|
|||
var self = this; |
|||
var ctx = this.CrmActivities.el; |
|||
const arrays = await this.orm.call('crm.lead', "get_crm_activities", [this.state.period]); |
|||
const data = { |
|||
labels: arrays[1], |
|||
datasets: [{ |
|||
label: 'Activity', |
|||
data: arrays[0], |
|||
backgroundColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
borderColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
}] |
|||
}; |
|||
|
|||
|
|||
//create Chart class object
|
|||
var chart = new Chart(ctx, { |
|||
type: 'pie', |
|||
data: data, |
|||
// options: options
|
|||
}); |
|||
this.state.charts.push(chart) |
|||
} |
|||
|
|||
async render_lead_by_campaign() { |
|||
|
|||
var self = this; |
|||
var ctx = this.LeadByCampaign.el; |
|||
const arrays = await this.orm.call('crm.lead', "get_the_campaign_pie", [this.state.period]); |
|||
const data = { |
|||
labels: arrays[1], |
|||
datasets: [{ |
|||
label: 'Activity', |
|||
data: arrays[0], |
|||
backgroundColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
borderColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
}] |
|||
}; |
|||
|
|||
|
|||
//create Chart class object
|
|||
var chart = new Chart(ctx, { |
|||
type: 'pie', |
|||
data: data, |
|||
// options: options
|
|||
}); |
|||
this.state.charts.push(chart) |
|||
} |
|||
|
|||
async render_lead_by_medium() { |
|||
|
|||
var self = this; |
|||
var ctx = this.LeadByMedium.el; |
|||
const arrays = await this.orm.call('crm.lead', "get_the_medium_pie", [this.state.period]); |
|||
const data = { |
|||
labels: arrays[1], |
|||
datasets: [{ |
|||
label: 'Activity', |
|||
data: arrays[0], |
|||
backgroundColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
borderColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
}] |
|||
}; |
|||
|
|||
|
|||
//create Chart class object
|
|||
var chart = new Chart(ctx, { |
|||
type: 'pie', |
|||
data: data, |
|||
// options: options
|
|||
}); |
|||
this.state.charts.push(chart) |
|||
} |
|||
|
|||
async render_lead_by_source() { |
|||
|
|||
var self = this; |
|||
var ctx = this.LeadBySource.el; |
|||
const arrays = await this.orm.call('crm.lead', "get_the_source_pie", [this.state.period]); |
|||
const data = { |
|||
labels: arrays[1], |
|||
datasets: [{ |
|||
label: 'Activity', |
|||
data: arrays[0], |
|||
backgroundColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
borderColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
}] |
|||
}; |
|||
|
|||
|
|||
//create Chart class object
|
|||
var chart = new Chart(ctx, { |
|||
type: 'pie', |
|||
data: data, |
|||
// options: options
|
|||
}); |
|||
this.state.charts.push(chart) |
|||
} |
|||
|
|||
async render_lost_lead() { |
|||
|
|||
var self = this; |
|||
var ctx = this.LostLead.el; |
|||
const arrays = await this.orm.call('crm.lead', "get_total_lost_crm", [this.state.period]); |
|||
const data = { |
|||
labels: arrays['month'], |
|||
datasets: [{ |
|||
label: 'Activity', |
|||
data: arrays['count'], |
|||
backgroundColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
borderColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
}] |
|||
}; |
|||
|
|||
|
|||
//create Chart class object
|
|||
var chart = new Chart(ctx, { |
|||
type: 'bar', |
|||
data: data, |
|||
// options: options
|
|||
}); |
|||
this.state.charts.push(chart) |
|||
} |
|||
|
|||
async render_total_revenue() { |
|||
|
|||
var self = this; |
|||
var ctx = this.TotalRevenue.el; |
|||
const arrays = await this.orm.call('crm.lead', "total_revenue_by_sales", [this.state.period]); |
|||
const data = { |
|||
labels: arrays[1], |
|||
datasets: [{ |
|||
label: 'Activity', |
|||
data: arrays[0], |
|||
backgroundColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
borderColor: [ |
|||
"#003f5c", |
|||
"#2f4b7c", |
|||
"#f95d6a", |
|||
"#665191", |
|||
"#d45087", |
|||
"#ff7c43", |
|||
"#ffa600", |
|||
"#a05195", |
|||
"#6d5c16" |
|||
], |
|||
}] |
|||
}; |
|||
|
|||
|
|||
//create Chart class object
|
|||
var chart = new Chart(ctx, { |
|||
type: 'pie', |
|||
data: data, |
|||
// options: options
|
|||
}); |
|||
this.state.charts.push(chart) |
|||
} |
|||
} |
|||
|
|||
|
|||
CrmDashboard.template = 'CrmDashboard' |
|||
registry.category("actions").add("crm_dashboard", CrmDashboard) |