diff --git a/sales_dashboard/README.rst b/sales_dashboard/README.rst new file mode 100644 index 000000000..2cd12a685 --- /dev/null +++ b/sales_dashboard/README.rst @@ -0,0 +1,47 @@ +.. image:: https://img.shields.io/badge/license-LGPL--3-green.svg + :target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +Sales Dashboard Odoo 18 +=========================== +Dashboard for Sales module. + +Configuration +============= +No additional configurations needed. + +Company +------- +* `Cybrosys Techno Solutions `__ + +License +------- +Lesser General Public License, Version 3 (LGPL v3) +(https://www.gnu.org/licenses/lgpl-3.0-standalone.html) + +Credits +------- +* Developer : (V18) Abhinave M + 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 `__ + +Further information +=================== +HTML Description: ``__ diff --git a/sales_dashboard/__init__.py b/sales_dashboard/__init__.py new file mode 100644 index 000000000..485b18cbd --- /dev/null +++ b/sales_dashboard/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import models \ No newline at end of file diff --git a/sales_dashboard/__manifest__.py b/sales_dashboard/__manifest__.py new file mode 100644 index 000000000..5c694ea59 --- /dev/null +++ b/sales_dashboard/__manifest__.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +{ + 'name': "Sales Dashboard", + 'version': '18.0.1.0.0', + 'category': 'Sales', + 'summary': 'Detailed dashboard view for Sales module.', + 'description': """This module provides a comprehensive dashboard for the Sales module, + offering a clear and actionable overview of orders, quotations, invoicing status, + revenues, and customer behavior. It enables sales teams and managers to monitor performance, + identify trends, and make informed decisions with real-time data visibility.""", + 'author': "Cybrosys Techno Solutions", + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'depends': ['base', 'sale_management', 'web'], + 'data': [ + 'views/sales_dashboard_views.xml', + ], + 'assets': { + 'web.assets_backend': [ + 'sales_dashboard/static/src/js/sales_dashboard.js', + 'sales_dashboard/static/src/xml/sales_dashboard.xml', + 'https://cdn.jsdelivr.net/npm/chart.js', + ], + }, + 'images': [ + 'static/description/banner.jpg', + ], + 'license': 'LGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/sales_dashboard/models/__init__.py b/sales_dashboard/models/__init__.py new file mode 100644 index 000000000..0ecec4de6 --- /dev/null +++ b/sales_dashboard/models/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import sale_order \ No newline at end of file diff --git a/sales_dashboard/models/sale_order.py b/sales_dashboard/models/sale_order.py new file mode 100644 index 000000000..e0dc3ff18 --- /dev/null +++ b/sales_dashboard/models/sale_order.py @@ -0,0 +1,345 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +############################################################################# +from odoo import models, api, fields +from datetime import date, timedelta +from odoo.exceptions import UserError + + +class SaleOrder(models.Model): + """Extends sale.order model to fetch data to be displayed in the dashboard""" + _inherit = 'sale.order' + + def _get_range(self, filter_key, custom_start=None, custom_end=None): + """Manages the date range according to the filter selected""" + today = date.today() + + if filter_key == 'this_week': + start = today - timedelta(days=today.weekday()) + end = start + timedelta(days=6) + return start, min(end, today) + + elif filter_key == 'this_month': + return today.replace(day=1), today + + elif filter_key == 'this_year': + return today.replace(month=1, day=1), today + + elif filter_key == 'custom' and custom_start and custom_end: + if custom_end < custom_start: + raise UserError("Please select a valid range") + return custom_start, custom_end + + return None, None + + def _build_global_domain(self, base_domain, filters, date_field="date_order"): + """Build domain with global filter applied""" + global_filter = filters.get("global_filter", "this_week") + if global_filter == "select_period": + global_filter = "this_week" + + if global_filter == "custom": + custom_range = filters.get("custom_range", {}) + custom_start = custom_range.get("from") + custom_end = custom_range.get("to") + else: + custom_start = None + custom_end = None + + from_date, to_date = self._get_range(global_filter, custom_start, custom_end) + + domain = base_domain.copy() + if from_date: + domain.append((date_field, '>=', from_date)) + if to_date: + domain.append((date_field, '<=', to_date)) + return domain + + @api.model + def get_tile_domain(self, base_domain, filters): + """Get domain for tile clicks with global filter applied""" + filterss = self._build_global_domain(base_domain, filters) + return self._build_global_domain(base_domain, filters) + + @api.model + def get_sales_dashboard_data(self, filters=None): + """Fetches the datas to be displayed""" + filters = filters or {} + limit = int(filters.get("limit", 10)) + + def get_filter_key(specific_filter_key): + """Build and return a domain combining base_domain with global date filters.""" + global_filter = filters.get("global_filter", "this_week") + if global_filter == "custom": + return "custom" + elif global_filter == "select_period": + return filters.get(specific_filter_key, "this_week") + else: + return global_filter + + def build_domain(base_domain, specific_filter_key, date_field="date_order"): + """Attach date domain based on the filter key""" + filter_key = get_filter_key(specific_filter_key) + + if filter_key == "custom": + custom_range = filters.get("custom_range", {}) + custom_start = custom_range.get("from") + custom_end = custom_range.get("to") + else: + custom_start = None + custom_end = None + + from_date, to_date = self._get_range(filter_key, custom_start, custom_end) + + domain = base_domain.copy() + if from_date: + domain.append((date_field, '>=', from_date)) + if to_date: + domain.append((date_field, '<=', to_date)) + return domain + + team_domain = build_domain([('state', 'in', ['sale', 'done'])], "team_filter") + sales_by_team = self.read_group(team_domain, ['amount_total'], ['team_id'], limit=limit, orderby='amount_total desc') + teams = [ + {'id': rec['team_id'][0], 'name': rec['team_id'][1], 'amount': rec['amount_total']} + for rec in sales_by_team if rec['team_id'] + ] + + person_domain = build_domain([('state', 'in', ['sale', 'done'])], "person_filter") + sales_by_person = self.read_group(person_domain, ['amount_total'], ['user_id'], limit=limit, orderby='amount_total desc') + persons = [ + {'id': rec['user_id'][0], 'name': rec['user_id'][1], 'amount': rec['amount_total']} + for rec in sales_by_person if rec['user_id'] + ] + + customer_domain = build_domain([('state', 'in', ['sale', 'done'])], "customer_filter") + customers_grouped = self.read_group(customer_domain, ['amount_total'], ['partner_id'], + limit=limit, orderby='amount_total desc') + customers = [ + {'id': rec['partner_id'][0], 'name': rec['partner_id'][1], 'amount': rec['amount_total']} + for rec in customers_grouped if rec['partner_id'] + ] + + product_domain = build_domain([('order_id.state', 'in', ['sale', 'done'])], + "product_filter", "order_id.date_order") + if filters.get("product_category_id"): + product_domain.append(('product_id.categ_id', '=', filters["product_category_id"])) + top_products_grouped = self.env['sale.order.line'].read_group( + product_domain, ['product_uom_qty'], ['product_id'], + limit=limit, orderby='product_uom_qty desc' + ) + top_products = [ + {'id': rec['product_id'][0], 'name': rec['product_id'][1], 'qty': rec['product_uom_qty']} + for rec in top_products_grouped if rec['product_id'] + ] + + low_product_domain = build_domain([('order_id.state', 'in', ['sale', 'done'])], + "low_product_filter", "order_id.date_order") + if filters.get("low_product_category_id"): + low_product_domain.append(('product_id.categ_id', '=', filters["low_product_category_id"])) + low_products_grouped = self.env['sale.order.line'].read_group( + low_product_domain, ['product_uom_qty'], ['product_id'], + limit=limit, orderby='product_uom_qty asc' + ) + low_products = [ + {'id': rec['product_id'][0], 'name': rec['product_id'][1], 'qty': rec['product_uom_qty']} + for rec in low_products_grouped if rec['product_id'] + ] + + order_domain = build_domain([], "order_filter") + order_status_grouped = self.read_group(order_domain, ['id'], ['state']) + ORDER_STATUS_LABELS = { + 'draft': 'Quotation', + 'sent': 'Quotation Sent', + 'sale': 'Sales Order', + 'done': 'Locked', + 'cancel': 'Cancelled', + } + order_status = [ + {'status': ORDER_STATUS_LABELS.get(rec['state'], rec['state'].capitalize()), + 'count': rec['state_count']} + for rec in order_status_grouped + ] + + invoice_domain = build_domain([('move_type', '=', 'out_invoice')], "invoice_filter", "invoice_date") + invoice_status_grouped = self.env['account.move'].read_group(invoice_domain, ['id'], ['state']) + INVOICE_STATUS_LABELS = { + 'draft': 'Draft', + 'posted': 'Posted', + 'cancel': 'Cancelled', + } + invoice_status = [ + {'status': INVOICE_STATUS_LABELS.get(rec['state'], rec['state'].capitalize()), + 'count': rec['state_count']} + for rec in invoice_status_grouped + ] + + overdue_customers_domain =build_domain([ + ('move_type', '=', 'out_invoice'), + ('payment_state', '!=', 'paid'), + ('invoice_date_due', '<', fields.Date.today()) + ],"overdue_filter","invoice_date") + overdue_customers_grouped = self.env['account.move'].read_group( + overdue_customers_domain, ['amount_total'], ['partner_id'], orderby='amount_total desc', limit=limit + ) + overdue_customers =[ + {'id': rec['partner_id'][0], 'name': rec['partner_id'][1], 'amount': rec['amount_total']} + for rec in overdue_customers_grouped if rec['partner_id'] + ] + + categories = self.env['product.category'].search([]) + product_categories = [{'id': c.id, 'name': c.display_name} for c in categories] + + sale_orders_domain = build_domain([('state', 'in', ['sale', 'done'])], "order_tile_filter") + sale_orders = self.search_count(sale_orders_domain) + + quotations_domain = build_domain([('state', 'in', ['draft', 'sent'])], "quotation_tile_filter") + quotations = self.search_count(quotations_domain) + + orders_to_invoice_domain = build_domain([('invoice_status', '=', 'to invoice')], "to_invoice_tile_filter") + orders_to_invoice = self.search_count(orders_to_invoice_domain) + + orders_fully_invoiced_domain = build_domain([('invoice_status', '=', 'invoiced')], "invoiced_tile_filter") + orders_fully_invoiced = self.search_count(orders_fully_invoiced_domain) + + conversion_rate = round( + (sale_orders / (quotations + sale_orders) * 100) if (quotations + sale_orders) > 0 else 0) + + SaleOrder = self.env['sale.order'] + company_currency = self.env.company.currency_id + today = fields.Date.today() + month_start = today.replace(day=1) + year_start = today.replace(month=1, day=1) + + def _get_total(start_date): + """To find the total amount by grouping the orders based on the currency""" + groups = SaleOrder.read_group( + [('state', '=', 'sale'), ('date_order', '>=', start_date)], + ['amount_total:sum'], + ['currency_id'] + ) + total = 0.0 + for g in groups: + if g['currency_id']: + currency = self.env['res.currency'].browse(g['currency_id'][0]) + total += currency._convert( + g['amount_total'], + company_currency, + self.env.company, + today + ) + return total + + total_revenue_mtd = _get_total(month_start) + total_revenue_ytd = _get_total(year_start) + + groups = SaleOrder.read_group( + [('state', '=', 'sale')], + ['amount_total:sum', 'id:count'], + ['currency_id'] + ) + + total_revenue = 0.0 + total_orders = 0 + for g in groups: + if g['currency_id']: + currency = self.env['res.currency'].browse(g['currency_id'][0]) + total_revenue += currency._convert( + g['amount_total'], + company_currency, + self.env.company, + today + ) + total_orders += g.get('currency_id_count', 0) + + avg_order_value = total_revenue / total_orders if total_orders else 0 + + sales_info = { + 'sale_orders': sale_orders, + 'quotation': quotations, + 'orders_to_invoice': orders_to_invoice, + 'orders_fully_invoiced': orders_fully_invoiced, + 'conversion_rate': conversion_rate, + 'total_revenue_mtd': company_currency.format(total_revenue_mtd), + 'total_revenue_ytd': company_currency.format(total_revenue_ytd), + 'avg_order_value': company_currency.format(avg_order_value), + } + + nvrc_domain = build_domain([('state', 'in', ['sale', 'done'])], "nvrc_filter") + + date_from, date_to = None, None + for d in nvrc_domain: + if d[0] == 'date_order' and d[1] == '>=': + date_from = d[2] + elif d[0] == 'date_order' and d[1] == '<=': + date_to = d[2] + + if date_from and isinstance(date_from, str): + date_from = fields.Date.from_string(date_from) + if date_to and isinstance(date_to, str): + date_to = fields.Date.from_string(date_to) + + current_customers = self.read_group( + nvrc_domain, ['partner_id'], ['partner_id'] + ) + customer_ids = [rec['partner_id'][0] for rec in current_customers if rec['partner_id']] + + new_customers, returning_customers = [], [] + if customer_ids: + first_orders = self.read_group( + [('partner_id', 'in', customer_ids), ('state', 'in', ['sale', 'done'])], + ['partner_id', 'date_order:min'], + ['partner_id'] + ) + first_order_map = {rec['partner_id'][0]: rec['date_order'] for rec in first_orders if rec['partner_id']} + + partners = self.env['res.partner'].browse(customer_ids) + for partner in partners: + first_date = first_order_map.get(partner.id) + if first_date and date_from and first_date.date() >= date_from: + new_customers.append({'id': partner.id, 'name': partner.display_name}) + else: + returning_customers.append({'id': partner.id, 'name': partner.display_name}) + + new_vs_returning = { + 'summary': { + 'labels': ["New Customers", "Returning Customers"], + 'values': [len(new_customers), len(returning_customers)], + }, + 'details': { + 'new': new_customers[:limit], + 'returning': returning_customers[:limit], + } + } + + return { + 'sales_by_team': teams, + 'sales_by_person': persons, + 'top_customers': customers, + 'top_products': top_products, + 'lowest_products': low_products, + 'overdue_customers': overdue_customers, + 'order_status': order_status, + 'invoice_status': invoice_status, + 'product_categories': product_categories, + 'sales_info': sales_info, + 'new_vs_returning': new_vs_returning, + } \ No newline at end of file diff --git a/sales_dashboard/static/description/assets/cybro-icon.png b/sales_dashboard/static/description/assets/cybro-icon.png new file mode 100755 index 000000000..06e73e11d Binary files /dev/null and b/sales_dashboard/static/description/assets/cybro-icon.png differ diff --git a/sales_dashboard/static/description/assets/cybro-odoo.png b/sales_dashboard/static/description/assets/cybro-odoo.png new file mode 100755 index 000000000..ed02e07a4 Binary files /dev/null and b/sales_dashboard/static/description/assets/cybro-odoo.png differ diff --git a/sales_dashboard/static/description/assets/h2.png b/sales_dashboard/static/description/assets/h2.png new file mode 100755 index 000000000..0bfc4707d Binary files /dev/null and b/sales_dashboard/static/description/assets/h2.png differ diff --git a/sales_dashboard/static/description/assets/icons/arrows-repeat.svg b/sales_dashboard/static/description/assets/icons/arrows-repeat.svg new file mode 100755 index 000000000..1d7efabc5 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/arrows-repeat.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/banner-1.png b/sales_dashboard/static/description/assets/icons/banner-1.png new file mode 100755 index 000000000..c180db172 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/banner-1.png differ diff --git a/sales_dashboard/static/description/assets/icons/banner-2.svg b/sales_dashboard/static/description/assets/icons/banner-2.svg new file mode 100755 index 000000000..e606d97d9 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/banner-2.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/banner-bg.png b/sales_dashboard/static/description/assets/icons/banner-bg.png new file mode 100755 index 000000000..a8238d3c0 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/banner-bg.png differ diff --git a/sales_dashboard/static/description/assets/icons/banner-bg.svg b/sales_dashboard/static/description/assets/icons/banner-bg.svg new file mode 100755 index 000000000..b1378103e --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/banner-bg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/banner-call.svg b/sales_dashboard/static/description/assets/icons/banner-call.svg new file mode 100755 index 000000000..96c687e81 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/banner-call.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/sales_dashboard/static/description/assets/icons/banner-mail.svg b/sales_dashboard/static/description/assets/icons/banner-mail.svg new file mode 100755 index 000000000..cbf0d158d --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/banner-mail.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/sales_dashboard/static/description/assets/icons/banner-pattern.svg b/sales_dashboard/static/description/assets/icons/banner-pattern.svg new file mode 100755 index 000000000..9c1c7e101 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/banner-pattern.svg @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/banner-promo.svg b/sales_dashboard/static/description/assets/icons/banner-promo.svg new file mode 100755 index 000000000..d52791b11 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/banner-promo.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/brand-pair.svg b/sales_dashboard/static/description/assets/icons/brand-pair.svg new file mode 100755 index 000000000..d8db7fc1e --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/brand-pair.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/check.png b/sales_dashboard/static/description/assets/icons/check.png new file mode 100755 index 000000000..c8e85f51d Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/check.png differ diff --git a/sales_dashboard/static/description/assets/icons/chevron.png b/sales_dashboard/static/description/assets/icons/chevron.png new file mode 100755 index 000000000..2089293d6 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/chevron.png differ diff --git a/sales_dashboard/static/description/assets/icons/close-icon.svg b/sales_dashboard/static/description/assets/icons/close-icon.svg new file mode 100755 index 000000000..df8cce37a --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/close-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/sales_dashboard/static/description/assets/icons/cogs.png b/sales_dashboard/static/description/assets/icons/cogs.png new file mode 100755 index 000000000..95d0bad62 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/cogs.png differ diff --git a/sales_dashboard/static/description/assets/icons/collabarate-icon.svg b/sales_dashboard/static/description/assets/icons/collabarate-icon.svg new file mode 100755 index 000000000..dd4e10518 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/collabarate-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/sales_dashboard/static/description/assets/icons/consultation.png b/sales_dashboard/static/description/assets/icons/consultation.png new file mode 100755 index 000000000..8319d4baa Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/consultation.png differ diff --git a/sales_dashboard/static/description/assets/icons/cybro-logo.png b/sales_dashboard/static/description/assets/icons/cybro-logo.png new file mode 100755 index 000000000..ff4b78220 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/cybro-logo.png differ diff --git a/sales_dashboard/static/description/assets/icons/down.svg b/sales_dashboard/static/description/assets/icons/down.svg new file mode 100755 index 000000000..f21c36271 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sales_dashboard/static/description/assets/icons/ecom-black.png b/sales_dashboard/static/description/assets/icons/ecom-black.png new file mode 100755 index 000000000..a9385ff13 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/ecom-black.png differ diff --git a/sales_dashboard/static/description/assets/icons/education-black.png b/sales_dashboard/static/description/assets/icons/education-black.png new file mode 100755 index 000000000..3eb09b27b Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/education-black.png differ diff --git a/sales_dashboard/static/description/assets/icons/faq.png b/sales_dashboard/static/description/assets/icons/faq.png new file mode 100755 index 000000000..4250b5b81 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/faq.png differ diff --git a/sales_dashboard/static/description/assets/icons/feature-icon.svg b/sales_dashboard/static/description/assets/icons/feature-icon.svg new file mode 100755 index 000000000..fa0ea6850 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/feature-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/feature.png b/sales_dashboard/static/description/assets/icons/feature.png new file mode 100755 index 000000000..ac7a785c0 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/feature.png differ diff --git a/sales_dashboard/static/description/assets/icons/gear.svg b/sales_dashboard/static/description/assets/icons/gear.svg new file mode 100755 index 000000000..0cc66b6ea --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/gear.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/hero.gif b/sales_dashboard/static/description/assets/icons/hero.gif new file mode 100755 index 000000000..380654dfe Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/hero.gif differ diff --git a/sales_dashboard/static/description/assets/icons/hire-odoo.svg b/sales_dashboard/static/description/assets/icons/hire-odoo.svg new file mode 100755 index 000000000..e1ac089b0 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/hire-odoo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/hotel-black.png b/sales_dashboard/static/description/assets/icons/hotel-black.png new file mode 100755 index 000000000..130f613be Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/hotel-black.png differ diff --git a/sales_dashboard/static/description/assets/icons/license.png b/sales_dashboard/static/description/assets/icons/license.png new file mode 100755 index 000000000..a5869797e Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/license.png differ diff --git a/sales_dashboard/static/description/assets/icons/life-ring-icon.svg b/sales_dashboard/static/description/assets/icons/life-ring-icon.svg new file mode 100755 index 000000000..3ae6e1d89 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/life-ring-icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/lifebuoy.png b/sales_dashboard/static/description/assets/icons/lifebuoy.png new file mode 100755 index 000000000..658d56ccc Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/lifebuoy.png differ diff --git a/sales_dashboard/static/description/assets/icons/mail.svg b/sales_dashboard/static/description/assets/icons/mail.svg new file mode 100755 index 000000000..1eedde695 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/mail.svg @@ -0,0 +1,3 @@ + + + diff --git a/sales_dashboard/static/description/assets/icons/manufacturing-black.png b/sales_dashboard/static/description/assets/icons/manufacturing-black.png new file mode 100755 index 000000000..697eb0e9f Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/manufacturing-black.png differ diff --git a/sales_dashboard/static/description/assets/icons/notes.png b/sales_dashboard/static/description/assets/icons/notes.png new file mode 100755 index 000000000..ee5e95404 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/notes.png differ diff --git a/sales_dashboard/static/description/assets/icons/notification icon.svg b/sales_dashboard/static/description/assets/icons/notification icon.svg new file mode 100755 index 000000000..053189973 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/notification icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/odoo-consultancy.svg b/sales_dashboard/static/description/assets/icons/odoo-consultancy.svg new file mode 100755 index 000000000..e05f65bde --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/odoo-consultancy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/sales_dashboard/static/description/assets/icons/odoo-licencing.svg b/sales_dashboard/static/description/assets/icons/odoo-licencing.svg new file mode 100755 index 000000000..2606c88b0 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/odoo-licencing.svg @@ -0,0 +1,3 @@ + + + diff --git a/sales_dashboard/static/description/assets/icons/odoo-logo.png b/sales_dashboard/static/description/assets/icons/odoo-logo.png new file mode 100755 index 000000000..0e4d0eb5a Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/odoo-logo.png differ diff --git a/sales_dashboard/static/description/assets/icons/patter.svg b/sales_dashboard/static/description/assets/icons/patter.svg new file mode 100755 index 000000000..25c9c0a8f --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/patter.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/pattern1.png b/sales_dashboard/static/description/assets/icons/pattern1.png new file mode 100755 index 000000000..09ab0fb2d Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/pattern1.png differ diff --git a/sales_dashboard/static/description/assets/icons/pos-black.png b/sales_dashboard/static/description/assets/icons/pos-black.png new file mode 100755 index 000000000..97c0f90c1 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/pos-black.png differ diff --git a/sales_dashboard/static/description/assets/icons/puzzle-piece-icon.svg b/sales_dashboard/static/description/assets/icons/puzzle-piece-icon.svg new file mode 100755 index 000000000..3e9ad9373 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/puzzle-piece-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/puzzle.png b/sales_dashboard/static/description/assets/icons/puzzle.png new file mode 100755 index 000000000..65cf854e7 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/puzzle.png differ diff --git a/sales_dashboard/static/description/assets/icons/replace-icon.svg b/sales_dashboard/static/description/assets/icons/replace-icon.svg new file mode 100755 index 000000000..d0e3a7af1 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/replace-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/restaurant-black.png b/sales_dashboard/static/description/assets/icons/restaurant-black.png new file mode 100755 index 000000000..4a35eb939 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/restaurant-black.png differ diff --git a/sales_dashboard/static/description/assets/icons/screenshot-main.png b/sales_dashboard/static/description/assets/icons/screenshot-main.png new file mode 100755 index 000000000..575f8e676 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/screenshot-main.png differ diff --git a/sales_dashboard/static/description/assets/icons/screenshot.png b/sales_dashboard/static/description/assets/icons/screenshot.png new file mode 100755 index 000000000..cef272529 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/screenshot.png differ diff --git a/sales_dashboard/static/description/assets/icons/service-black.png b/sales_dashboard/static/description/assets/icons/service-black.png new file mode 100755 index 000000000..301ab51cb Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/service-black.png differ diff --git a/sales_dashboard/static/description/assets/icons/skype-fill.svg b/sales_dashboard/static/description/assets/icons/skype-fill.svg new file mode 100755 index 000000000..c17423639 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/skype-fill.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/skype.png b/sales_dashboard/static/description/assets/icons/skype.png new file mode 100755 index 000000000..51b409fb3 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/skype.png differ diff --git a/sales_dashboard/static/description/assets/icons/skype.svg b/sales_dashboard/static/description/assets/icons/skype.svg new file mode 100755 index 000000000..df3dad39b --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/skype.svg @@ -0,0 +1,3 @@ + + + diff --git a/sales_dashboard/static/description/assets/icons/star-1.svg b/sales_dashboard/static/description/assets/icons/star-1.svg new file mode 100755 index 000000000..7e55ab162 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/star-1.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/star-2.svg b/sales_dashboard/static/description/assets/icons/star-2.svg new file mode 100755 index 000000000..5ae9f507a --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/star-2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/support.png b/sales_dashboard/static/description/assets/icons/support.png new file mode 100755 index 000000000..4f18b8b82 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/support.png differ diff --git a/sales_dashboard/static/description/assets/icons/test-1 - Copy.png b/sales_dashboard/static/description/assets/icons/test-1 - Copy.png new file mode 100755 index 000000000..f6a902663 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/test-1 - Copy.png differ diff --git a/sales_dashboard/static/description/assets/icons/test-1.png b/sales_dashboard/static/description/assets/icons/test-1.png new file mode 100755 index 000000000..0908add2b Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/test-1.png differ diff --git a/sales_dashboard/static/description/assets/icons/test-2.png b/sales_dashboard/static/description/assets/icons/test-2.png new file mode 100755 index 000000000..4671fe91e Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/test-2.png differ diff --git a/sales_dashboard/static/description/assets/icons/trading-black.png b/sales_dashboard/static/description/assets/icons/trading-black.png new file mode 100755 index 000000000..9398ba2f1 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/trading-black.png differ diff --git a/sales_dashboard/static/description/assets/icons/training.png b/sales_dashboard/static/description/assets/icons/training.png new file mode 100755 index 000000000..884ca024d Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/training.png differ diff --git a/sales_dashboard/static/description/assets/icons/translate.svg b/sales_dashboard/static/description/assets/icons/translate.svg new file mode 100755 index 000000000..af9c8a1aa --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/translate.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/update.png b/sales_dashboard/static/description/assets/icons/update.png new file mode 100755 index 000000000..ecbc5a01a Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/update.png differ diff --git a/sales_dashboard/static/description/assets/icons/user.png b/sales_dashboard/static/description/assets/icons/user.png new file mode 100755 index 000000000..6ffb23d9f Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/user.png differ diff --git a/sales_dashboard/static/description/assets/icons/video.png b/sales_dashboard/static/description/assets/icons/video.png new file mode 100755 index 000000000..576705b17 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/video.png differ diff --git a/sales_dashboard/static/description/assets/icons/whatsapp.png b/sales_dashboard/static/description/assets/icons/whatsapp.png new file mode 100755 index 000000000..d513a5356 Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/whatsapp.png differ diff --git a/sales_dashboard/static/description/assets/icons/wrench-icon.svg b/sales_dashboard/static/description/assets/icons/wrench-icon.svg new file mode 100755 index 000000000..174b5a465 --- /dev/null +++ b/sales_dashboard/static/description/assets/icons/wrench-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sales_dashboard/static/description/assets/icons/wrench.png b/sales_dashboard/static/description/assets/icons/wrench.png new file mode 100755 index 000000000..6c04dea0f Binary files /dev/null and b/sales_dashboard/static/description/assets/icons/wrench.png differ diff --git a/sales_dashboard/static/description/assets/modules/1.jpg b/sales_dashboard/static/description/assets/modules/1.jpg new file mode 100644 index 000000000..3cb15fe01 Binary files /dev/null and b/sales_dashboard/static/description/assets/modules/1.jpg differ diff --git a/sales_dashboard/static/description/assets/modules/2.jpg b/sales_dashboard/static/description/assets/modules/2.jpg new file mode 100644 index 000000000..662cadcc3 Binary files /dev/null and b/sales_dashboard/static/description/assets/modules/2.jpg differ diff --git a/sales_dashboard/static/description/assets/modules/3.jpg b/sales_dashboard/static/description/assets/modules/3.jpg new file mode 100644 index 000000000..717a00443 Binary files /dev/null and b/sales_dashboard/static/description/assets/modules/3.jpg differ diff --git a/sales_dashboard/static/description/assets/modules/4.png b/sales_dashboard/static/description/assets/modules/4.png new file mode 100644 index 000000000..00ebf54ad Binary files /dev/null and b/sales_dashboard/static/description/assets/modules/4.png differ diff --git a/sales_dashboard/static/description/assets/modules/5.jpg b/sales_dashboard/static/description/assets/modules/5.jpg new file mode 100644 index 000000000..7c67e2eec Binary files /dev/null and b/sales_dashboard/static/description/assets/modules/5.jpg differ diff --git a/sales_dashboard/static/description/assets/modules/6.gif b/sales_dashboard/static/description/assets/modules/6.gif new file mode 100644 index 000000000..a35ece8df Binary files /dev/null and b/sales_dashboard/static/description/assets/modules/6.gif differ diff --git a/sales_dashboard/static/description/assets/screenshots/customers.png b/sales_dashboard/static/description/assets/screenshots/customers.png new file mode 100644 index 000000000..7bde096c0 Binary files /dev/null and b/sales_dashboard/static/description/assets/screenshots/customers.png differ diff --git a/sales_dashboard/static/description/assets/screenshots/filters.png b/sales_dashboard/static/description/assets/screenshots/filters.png new file mode 100644 index 000000000..78d10ccad Binary files /dev/null and b/sales_dashboard/static/description/assets/screenshots/filters.png differ diff --git a/sales_dashboard/static/description/assets/screenshots/hero.gif b/sales_dashboard/static/description/assets/screenshots/hero.gif new file mode 100644 index 000000000..b34315e3f Binary files /dev/null and b/sales_dashboard/static/description/assets/screenshots/hero.gif differ diff --git a/sales_dashboard/static/description/assets/screenshots/orderinvoicesstatus.png b/sales_dashboard/static/description/assets/screenshots/orderinvoicesstatus.png new file mode 100644 index 000000000..993cdaee8 Binary files /dev/null and b/sales_dashboard/static/description/assets/screenshots/orderinvoicesstatus.png differ diff --git a/sales_dashboard/static/description/assets/screenshots/overduenvrc.png b/sales_dashboard/static/description/assets/screenshots/overduenvrc.png new file mode 100644 index 000000000..2bd1e7b08 Binary files /dev/null and b/sales_dashboard/static/description/assets/screenshots/overduenvrc.png differ diff --git a/sales_dashboard/static/description/assets/screenshots/products.png b/sales_dashboard/static/description/assets/screenshots/products.png new file mode 100644 index 000000000..1337a12f6 Binary files /dev/null and b/sales_dashboard/static/description/assets/screenshots/products.png differ diff --git a/sales_dashboard/static/description/assets/screenshots/teamperson.png b/sales_dashboard/static/description/assets/screenshots/teamperson.png new file mode 100644 index 000000000..dccc2280c Binary files /dev/null and b/sales_dashboard/static/description/assets/screenshots/teamperson.png differ diff --git a/sales_dashboard/static/description/assets/screenshots/tileswithfilters.png b/sales_dashboard/static/description/assets/screenshots/tileswithfilters.png new file mode 100644 index 000000000..406c122a9 Binary files /dev/null and b/sales_dashboard/static/description/assets/screenshots/tileswithfilters.png differ diff --git a/sales_dashboard/static/description/assets/y18.jpg b/sales_dashboard/static/description/assets/y18.jpg new file mode 100755 index 000000000..eea1714f2 Binary files /dev/null and b/sales_dashboard/static/description/assets/y18.jpg differ diff --git a/sales_dashboard/static/description/banner.jpg b/sales_dashboard/static/description/banner.jpg new file mode 100644 index 000000000..72eda168c Binary files /dev/null and b/sales_dashboard/static/description/banner.jpg differ diff --git a/sales_dashboard/static/description/icon.png b/sales_dashboard/static/description/icon.png new file mode 100644 index 000000000..2d0db7d32 Binary files /dev/null and b/sales_dashboard/static/description/icon.png differ diff --git a/sales_dashboard/static/description/index.html b/sales_dashboard/static/description/index.html new file mode 100644 index 000000000..640726836 --- /dev/null +++ b/sales_dashboard/static/description/index.html @@ -0,0 +1,1106 @@ + + + + + + Sales Dashboard + + + + + + + + + + +
+
+ + + +
+
+ Community +
+
+ Enterprise +
+
+ Odoo.sh +
+
+
+ +
+
+
+
+

+ A Module For Visualizing Sales Metrics +

+

Sales Dashboard +

+
+
+ +
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+

Key + Highlights

+
+
+
+
+ +
+
+ KPI Tiles +
+

+ Key Performance Indicator tiles

+
+
+
+
+
+ +
+
+ Analysis Charts +
+

+ Perform analysis on Sales by team & person, Top/bottom performers, Order/Invoice status, Overdue/Customer Behaviour +

+
+
+
+
+
+ +
+
+ Powerful Filters +
+

+ Apply time-based filters and limit record counts for focused insights. +

+
+
+
+
+ +
+
+
+ Sales Dashboard +

+ Ready to take control of your sales pipeline?
+ Monitor KPIs, boost conversions, and drive revenue growth. +

+ +
+
+ +
+
+
+ + + + +
+
+ +
+
+
+
+ acc_bg +
+ +
+
+
+
+

+ KPI + + Tiles +

+
+
+

+ Quick tiles to monitor total sales orders, open quotations, orders pending invoicing, and fully invoiced sales. + View month-to-date (MTD) and year-to-date (YTD) revenue in company currency. +

+
+
+
+ +
+
+
+
+
+
+
+
+

+ Sales Team & Person + + Analysis +

+
+
+

+ Breakdown of revenue contribution by sales teams and individual salespeople. +

+
+
+
+ +
+
+
+
+
+
+
+
+
+

+ Top & Bottom + + Performers +

+
+
+

+ Identify top customers, top-selling products, and low-performing products in one glance. +

+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+

+ Overdue Customers & Customer Behavior +

+
+
+

+ Spot customers with overdue invoices.Distinguish between new and returning customers to track growth and loyalty trends. +

+
+
+
+ +
+
+
+
+
+
+
+
+
+

+ Order & Invoice + + Status +

+
+
+

+ At-a-glance view of order pipeline stages and invoice states (draft, posted, cancelled). +

+
+
+
+ +
+
+
+
+
+
+
+
+
+

+ Powerful + + + Filters +

+
+
+

+ Apply time-based filters (this week, month, year, or custom ranges) and limit record counts (top 5, top 10, etc.) for focused insights. +

+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+

+ KPI Tiles

+
+ +
+
+
+
+
+
+ +
+

+ Sales Team & Person Analysis +

+
+
+
+
+
+
+
+ +
+

+ Top & Bottom Performers Analysis +

+
+ +
+
+
+
+
+
+ +
+

+ Overdue Customers & Customer Behavior Analysis

+
+
+
+
+
+
+
+ +
+

+ Order & Invoice Status Analysis +

+
+
+
+
+
+
+
+ +
+

+ Comes with powerful filters +

+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+

+ A customizable dashboard for monitoring sales KPIs in Odoo 18. +

+
+
+ +
+ +
+

+ Yes, the dashboard allows you to apply filters to existing KPIs and set record limits (e.g., top 5, top 10) for a more focused sales view. +

+
+
+ +
+ +
+

+ No, KPIs auto-load from sales data. Developers can extend with custom KPIs. +

+
+
+
+ +
+
+
+
+
+
+ +
+
+

+ Latest Release 18.0.1.0.0 +

+ + 4th September, 2025 + +
+
+
+
+
+ Add +
+
+
+
    +
  • + Initial Commit +
  • + +
+
+
+
+
+
+
+
+
+
+ + + +
+

+ Related Products +

+ +
+ + +
+

+ Our Services

+ +
+ +
+
+ .... +
+
+ +
+ + +
+
+ + + + + + diff --git a/sales_dashboard/static/src/js/sales_dashboard.js b/sales_dashboard/static/src/js/sales_dashboard.js new file mode 100644 index 000000000..5789fa379 --- /dev/null +++ b/sales_dashboard/static/src/js/sales_dashboard.js @@ -0,0 +1,337 @@ +/** @odoo-module **/ +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { Component, useState, onMounted } from "@odoo/owl"; + +const actionRegistry = registry.category("actions"); + + +class SalesDashboard extends Component { + setup() { + this.orm = useService("orm"); + this.actionService = useService('action') + this.charts = {}; + this.state = useState({ + data: { + sales_info: { + sale_orders: 0, + quotation: 0, + orders_to_invoice: 0, + orders_fully_invoiced: 0, + } + }, + filters: { + global_filter: "select_period", + custom_range: { from: null, to: null }, + limit: "10", + team_filter: "this_week", + person_filter: "this_week", + product_filter: "this_week", + low_product_filter: "this_week", + customer_filter: "this_week", + order_filter: "this_week", + invoice_filter: "this_week", + }, + }); + onMounted(() => this._fetch_data()); + } + + async _fetch_data() { + const result = await this.orm.call( + "sale.order", + "get_sales_dashboard_data", + [], + { filters: this.state.filters } + ); + + this.state.data = result; + this._render_charts(); + } + + + goToRecord(model, id) { + window.location.href = `/web#model=${model}&id=${id}&view_type=form`; + } + async viewOrders(){ + const domain = await this.orm.call( + "sale.order", + "get_tile_domain", + [], + { + base_domain: [['state', 'in', ['sale', 'done']]], + filters: this.state.filters + } + ); + + this.actionService.doAction({ + type: "ir.actions.act_window", + name: "Sale Orders", + res_model: "sale.order", + domain, + views: [[false, "list"], [false, "form"]] + }) + } + + async viewQuotations(){ + const domain = await this.orm.call( + "sale.order", + "get_tile_domain", + [], + { + base_domain: [['state', 'in', ['draft','sent']]], + filters: this.state.filters + } + ); + + this.actionService.doAction({ + type: "ir.actions.act_window", + name: "Quotations", + res_model: "sale.order", + domain, + views: [[false, "list"], [false, "form"]] + }) + } + + async viewToInvoiceOrders(){ + const domain = await this.orm.call( + "sale.order", + "get_tile_domain", + [], + { + base_domain: [['invoice_status', '=', 'to invoice']], + filters: this.state.filters + } + ); + + this.actionService.doAction({ + type: "ir.actions.act_window", + name: "To Invoice", + res_model: "sale.order", + domain, + views: [[false, "list"], [false, "form"]] + }) + } + + async viewFullyInvoicedOrders(){ + const domain = await this.orm.call( + "sale.order", + "get_tile_domain", + [], + { + base_domain: [['invoice_status', '=', 'invoiced']], + filters: this.state.filters + } + ); + + this.actionService.doAction({ + type: "ir.actions.act_window", + name: "Fully Invoiced", + res_model: "sale.order", + domain, + views: [[false, "list"], [false, "form"]] + }) + } + + _render_charts() { + const d = this.state.data; + if (!d.sales_by_team) return; + Object.values(this.charts).forEach(chart => chart?.destroy()); + this.charts = {}; + + const pie = (id, labels, data, bg, chartKey) => { + const ctx = document.getElementById(id); + if (!ctx) return; + this.charts[chartKey] = new Chart(ctx, { + type: "pie", + data: { + labels, + datasets: [{ data, backgroundColor: bg }], + }, + options: { + responsive: true, + plugins: { legend: { position: 'bottom' } } + } + }); + }; + const bar = (id, labels, data, bg, chartKey) => { + const ctx = document.getElementById(id); + if (!ctx) return; + this.charts[chartKey] = new Chart(ctx, { + type: "bar", + data: { + labels, + datasets: [{ label: "", data, backgroundColor: bg }], + }, + options: { + responsive: true, + scales: { y: { beginAtZero: true } }, + plugins: { legend: { display: false } } + } + }); + }; + + pie("salesByTeamChart", + d.sales_by_team.map(x => x.name), + d.sales_by_team.map(x => x.amount), + ["#007bff", "#28a745", "#ffc107", "#dc3545", "#ff7e00"], + "team"); + + pie("salesByPersonChart", + d.sales_by_person.map(x => x.name), + d.sales_by_person.map(x => x.amount), + ["#17a2b8", "#ffc107", "#6c757d", "#6610f2"], + "person"); + + bar("topProductsChart", + d.top_products.map(x => x.name), + d.top_products.map(x => x.qty), + "#28a745", + "top_products"); + + bar("lowestProductsChart", + d.lowest_products.map(x => x.name), + d.lowest_products.map(x => x.qty), + "#dc3545", + "lowest_products"); + + pie("orderStatusChart", + d.order_status.map(x => x.status), + d.order_status.map(x => x.count), + ["#6c757d", "#17a2b8", "#28a745", "#ffc107"], + "order_status"); + + pie("invoiceStatusChart", + d.invoice_status.map(x => x.status), + d.invoice_status.map(x => x.count), + ["#343a40", "#28a745", "#ffc107"], + "invoice_status"); + + bar( + "overdueCustomersChart", + d.overdue_customers.map(x => x.name), + d.overdue_customers.map(x => x.amount), + d.overdue_customers.map(x => + x.amount > 10000 ? "red" : + x.amount > 5000 ? "orange" : + "yellow"), + "overdue_customers"); + + pie("newVsReturningChart", + d.new_vs_returning.summary.labels, + d.new_vs_returning.summary.values, + ["#28a745", "#ffc107"], + "new_vs_returning"); + } + + onChangeGlobalFilter(ev) { + this.state.filters.global_filter = ev.target.value; + + if (this.state.filters.global_filter === "select_period") { + this.state.filters = { + global_filter: "select_period", + custom_range: { from: null, to: null }, + limit: "10", + team_filter: "this_week", + person_filter: "this_week", + product_filter: "this_week", + low_product_filter: "this_week", + customer_filter: "this_week", + order_filter: "this_week", + invoice_filter: "this_week", + overdue_filter: "this_week", + nvrc_filter: "this_week", + }; + + this.render(); + this._fetch_data(); + return; + } + + if (this.state.filters.global_filter !== "custom") { + this.state.filters.custom_range = { from: null, to: null }; + } + + this._fetch_data(); +} + + onChangeCustomFrom(ev) { + this.state.filters.custom_range.from = ev.target.value; + this._maybeFetchCustom(); + } + + onChangeCustomTo(ev) { + this.state.filters.custom_range.to = ev.target.value; + this._maybeFetchCustom(); + } + + _maybeFetchCustom() { + if (this.state.filters.global_filter === "custom" && + this.state.filters.custom_range.from && + this.state.filters.custom_range.to) { + this._fetch_data(); + } + } + + onChangeTeamFilter(ev) { + if (this.state.filters.global_filter === "custom") return; + this.state.filters.team_filter = ev.target.value; + this._fetch_data(); + } + onChangePersonFilter(ev) { + if (this.state.filters.global_filter === "custom") return; + this.state.filters.person_filter = ev.target.value; + this._fetch_data(); + } + onChangeCustomerFilter(ev) { + if (this.state.filters.global_filter === "custom") return; + this.state.filters.customer_filter = ev.target.value; + this._fetch_data(); + } + onChangeOrderFilter(ev) { + if (this.state.filters.global_filter === "custom") return; + this.state.filters.order_filter = ev.target.value; + this._fetch_data(); + } + + onChangeInvoiceFilter(ev) { + if (this.state.filters.global_filter === "custom") return; + this.state.filters.invoice_filter = ev.target.value; + this._fetch_data(); + } + onChangeProductFilter(ev) { + if (this.state.filters.global_filter === "custom") return; + this.state.filters.product_filter = ev.target.value; + this._fetch_data(); + } + onChangeProductCategory(ev) { + const val = ev.target.value; + this.state.filters.product_category_id = val ? parseInt(val) : null; + this._fetch_data(); + } + onChangeLowProductFilter(ev) { + if (this.state.filters.global_filter === "custom") return; + this.state.filters.low_product_filter = ev.target.value; + this._fetch_data(); + } + onChangeLowProductCategory(ev) { + const val = ev.target.value; + this.state.filters.low_product_category_id = val ? parseInt(val) : null; + this._fetch_data(); + } + onChangeOverdueFilter(ev) { + if (this.state.filters.global_filter === "custom") return; + this.state.filters.overdue_filter = ev.target.value; + this._fetch_data(); + } + onChangeNvRFilter(ev) { + if (this.state.filters.global_filter === "custom") return; + this.state.filters.nvrc_filter = ev.target.value; + this._fetch_data(); + } + onChangeGlobalLimit(ev) { + this.state.filters.limit = parseInt(ev.target.value); + this._fetch_data(); + } +} +SalesDashboard.template = "sales_dashboard.SalesDashboardTemplate"; +registry.category("actions").add("sales_dashboard", SalesDashboard); \ No newline at end of file diff --git a/sales_dashboard/static/src/xml/sales_dashboard.xml b/sales_dashboard/static/src/xml/sales_dashboard.xml new file mode 100644 index 000000000..9931e838c --- /dev/null +++ b/sales_dashboard/static/src/xml/sales_dashboard.xml @@ -0,0 +1,563 @@ + + + +
+

📊 Sales Dashboard

+
+ + + + + + + +
+ + +
+
+ +
+ + +
+
+
+ +
+
+

Sale Orders

+
+ +
+
+
+
+ + +
+
+
+ +
+
+

Quotations

+
+ +
+
+
+
+ + +
+
+
+ +
+
+

Orders to Invoice

+
+ +
+
+
+
+ + +
+
+
+ +
+
+

Fully Invoiced

+
+ +
+
+
+
+
+ + +
+
+
+
+ +
+
+

Order Conversion Rate

+
+ % +
+
+
+
+ +
+
+
+ +
+
+

Total Revenue MTD

+
+ +
+
+
+
+ +
+
+
+ +
+
+

Total Revenue YTD

+
+ +
+
+
+
+ +
+
+
+ +
+
+

Average Order Value

+
+ +
+
+
+
+
+ +
+ +
+
+
Sales by Team
+
+ + + +
+
+ + + + + + + + + + + + + + +
TeamAmount
+ +
+
+
+
+ +
+
+
Sales by Person
+
+ + + +
+
+ + + + + + + + + + + +
SalespersonAmount
+ +
+
+
+
+
+ +
+ +
+
+
Top Selling Products
+
+
+ + + + +
+
+
+ + + + + + + + + + + + + + +
ProductQuantity Sold
+ +
+
+
+
+ +
+
+
Least Selling Products
+
+
+ + + + +
+
+
+ + + + + + + + + + + +
ProductQuantity Sold
+ +
+
+
+
+
+ +
+
+
+
Top Customers
+
+ + + +
+
+ + + + + + + + + + +
CustomerAmount
+ +
+
+
+
+
+ +
+ +
+
+
Order Status
+
+ + + +
+
+ + + + + + + + + + + +
StatusCount
+ +
+
+
+
+ +
+
+
Invoice Status
+
+ + + +
+
+ + + + + + + + + + + +
StatusCount
+ +
+
+
+
+
+ +
+ +
+
+
Overdue Customers
+
+ + + +
+
+ + + + + + + + + + + +
CustomerAmount
+ +
+
+
+
+ +
+
+
New vs Returning Customers
+
+ + + +
+
+ + +
+
New Customers
+ + + + + + + + +
Customer
+
+ + +
Returning Customers
+ + + + + + + + +
Customer
+
+
+
+
+
+
+
+
+
diff --git a/sales_dashboard/views/sales_dashboard_views.xml b/sales_dashboard/views/sales_dashboard_views.xml new file mode 100644 index 000000000..28151ad5f --- /dev/null +++ b/sales_dashboard/views/sales_dashboard_views.xml @@ -0,0 +1,9 @@ + + + + Dashboard + sales_dashboard + + + + \ No newline at end of file